Kindle Fire Coupon Kindle Fire Coupon 2012 Kindle DX Coupon 2012 Kindle Fire 2 Coupon Amazon Coupon Codes 2012 Kindle DX Coupon PlayStation Vita Coupon kindle touch coupon amazon coupon code kindle touch discount coupon kindle touch coupon 2012 logitech g27 coupon 2012 amazon discount codes
Facebook RSS Reset

Hướng dẫn lập trình device driver cơ bản_Phần 2

CÁC THAO TÁC TRÊN FILE DEVICE :

/* open led file */
int leds_open(struct inode *inode, struct file *file)
{
    struct leds_device *leds_devp;
 
    /* get cdev struct */
    leds_devp = container_of(inode->i_cdev, struct leds_device, cdev);
 
    /* save cdev pointer */
    file->private_data = leds_devp;
 
    /* return OK */
    return 0;
}
 
/* close led file */
int leds_release(struct inode *inode, struct file *file)
{
    /* return OK */
    return 0;
}
 
/* read led status */
ssize_t leds_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
    struct leds_device *leds_devp = file->private_data;
 
    if (leds_devp->status == LED_ON) {
        if (copy_to_user(buf, "1", 1))
            return -EIO;
    }
    else {
        if (copy_to_user(buf, "0", 1))
            return -EIO;
    }
 
    return 1;
}
 
ssize_t leds_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
    struct leds_device *leds_devp = file->private_data;
    char kbuf = 0;
 
    if (copy_from_user(&kbuf, buf, 1)) {
        return -EFAULT;
    }
 
    if (kbuf == '1') {
        changeLedStatus(leds_devp->number, LED_ON);
        leds_devp->status = LED_ON;
    }
    else if (kbuf == '0') {
        changeLedStatus(leds_devp->number, LED_OFF);
        leds_devp->status = LED_OFF;
    }
 
    return count;
}

Hàm leds_open() được gọi để mở file . Nhiệm vụ của bạn ở đây là lưu cấu trúc leds_dev liến kết đến device (led), bởi vì ta sẽ sử dụng cấu trúc này cho function read và write. Việc này được thực hiện thông qua hàm container_of() ở dòng số 7. Con trỏ đến leds_dev được lưu trữ trong file->private_data ở dòng số 10.

Trong hàm leds_read(), ta khôi phục lại cấu trúc của led là leds_dev trong file->private_data ở dòng số 26, ta kiểm tra trạng thái của led và trả về giá trị “1” nếu led on và ngược lại “0” nếu led off. Ta thấy rằng, để trả dữ liệu lên lớp ứng dụng thì mình cần sử dụng hàm copy_to_user(). Đây là một hàm chuẩn của kernel làm nhiệm vụ gửi data đến application .

Trong hàm leds_write() ta cũng cần khôi phục cấu trúc led là leds_dev trong file->private_date ở dòng 42 và cũng sử dụng hàm chuyển tiếp copy_from_user() ở dòng 45 để đọc dữ liệu từ application. sau đó tùy thuộc vào giá trị đọc về là “0” hay “1” mà ta quyết định thay đổi trạng thái của led.

Trên đây là những thao tác cơ bản và hầu như bắt buộc phải có trong device driver. Phần tiếp theo ta đề cập đến cách truy cập và điều khiển port/IO để quản lý trạng thái các led trên mini2440. Đây là 4 led tương ứng với 4 chân GPIO trên portB .

leds_pinout
Ta cần phải config pin là output qua thanh ghi GPBCON và thay đổi trạng thái của pin qua thanh ghi GPBDAT.
MCU S3C2440 map GPB qua địa chỉ bộ nhớ vật lý là 0x56000010. Tuy nhiên, đây là một real address và bạn phải luôn làm việc với kernel thông qua các virtual address (địa chỉ ảo) .Xem ở kernel source,ta sẽ tìm thấy địa chỉ virtal address được gán với actual adress là 0xFB000010 . xem trong file “arch/arm/mach-s3c2410/include/mach/regs-gpio.h”.

120820100492212
Chúng ta cùng xem đoạn code bên dưới đây :

#define GPB_BASE 0xFB000010 
#define GPBCON GPB_BASE 
#define GPBDAT GPB_BASE 4 + 
 
# define CLEAR_PORT_MASK 0xFFFC03FF 
#define SET_WRITE_PORT_MASK 0x00015400 
 
/ * initialize led port - GPB * / 
void initLedPort ( void ) 
{ 
    void __iomem * base = ( void __iomem * ) GPBCON ;
  
    u32 port = __raw_readl ( base ) ;
  
    port = & CLEAR_PORT_MASK ; 
    port | = SET_WRITE_PORT_MASK ;
  
    __raw_writel ( port , base ) ; 
} 
 
/ * LED status change * / 
void changeLedStatus ( int led_num , int status ) 
{ 
    void __iomem * base = ( void __iomem * ) GPBDAT ; 
    u32 mask , data ;
  
    data = __raw_readl ( base ) ;
  
    mask = 0x01 << ( 4 + led_num ) ; 
 
    switch ( status ) { 
 
        case LED_ON : 
            mask = ~mask ; 
            data &= mask ; 
            break ; 
 
        case LED_OFF : 
            data |= mask ; 
            break ; 
    }
  
    __raw_writel ( data , base ) ; 
}

BIÊN DỊCH VÀ TEST DRIVER

Phần phương pháp tạo makefile mình chưa đề cập ở đây mà sẽ giành nó cho những bài viết sau. Ở đây mình sẽ đưa ra 1 makefile có sẵn như sau :

# đường dẫn đến toolchain
TOOLCHAIN := /opt/buildroot/buildroot-2010.08/output/staging/usr/bin/
# đường dẫn đến thư mục chưa kernel
KDIR := /opt/buildroot/buildroot-2010.08/output/build/linux-2.6/
PWD := $(shell pwd)
PATH := $(TOOLCHAIN):${PATH}

obj-m += leds.o

default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
@rm -Rf *.o *.ko *.mod.c modules.order Module.symvers

Sau khi biên dịch driver trên ta sẽ có được file leds.ko. Tiến hành chép file driver này xuống thư mục trên kit mini2440 theo đường dẫn sau : /lib/modules/2.6.32.2-FriendlyARM/kernel/drivers/char/
* Từng bước thao tác như sau :
– Đầu tiên ta insert driver này vào kernel ,nếu thành công sẽ có 1 thông báo xuất hiện :

$ Insmod / lib / modules / 2.6.32.2-FriendlyARM / kernel / drivers / char / leds.ko
LED driver initialized.

– Tiếp theo ta kiểm tra số định danh “major number” được kernel cung cấp cho driver sử dụng lệnh sau (ở đây kernel cung cấp cho ta 1 số định danh là 253) :
$ grep leds / proc / devices
253 leds
– Khi đã có “major number” trong tay ta tiến hành tạo tất cả các device file cần thiết với lệnh sau:
$ mknod / dev / led1 c 253 0
$ mknod / dev / LED2 c 253 1
$ mknod / dev / LED3 c 253 2
$ mknod / dev / LED4 c 253 3
$ chmod a + w / dev / led *
– Rồi ,bây giờ đến lúc test led. Để bật led sáng chỉ cần send 1 ký tự “1” . Sử dụng lệnh sau để bật led trên led1 và led3 :
$ echo “1” > / dev / led1
$ echo “1” > / dev / LED3
và khi mún tắt led thì ngược lại chỉ cần send ký tự “0” :
$ echo “0” > / dev / LED3
– Để kiểm tra trạng thái hiện tại của led ta sử dụng lệnh “cat” ,ví dụ : cat /dev/led1

– Cuối cùng để gỡ bỏ driver ra khỏi kernel , ta sử dụng lệnh rmmod như sau :
$ Rmmod leds
leds Exiting driver.

Toàn bộ code của driver :

#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/module.h"
#include "linux/kernel.h"
#include "linux/device.h"
#include "asm/uaccess.h"
#include "asm/io.h"
#include "linux/slab.h"

#define DEVICE_NAME     "leds"

#define NUM_LEDS        4

#define LED_ON          1
#define LED_OFF         0

#define GPB_BASE    0xFB000010
#define GPBCON      GPB_BASE
#define GPBDAT      GPB_BASE + 4

#define CLEAR_PORT_MASK     0xFFFC03FF
#define SET_WRITE_PORT_MASK 0x00015400

/* prototypes */
int leds_open(struct inode *inode, struct file *file);
ssize_t leds_read(struct file *file, char *buf, size_t count, loff_t *ppos);
ssize_t leds_write(struct file *file, const char *buf, size_t count, loff_t *ppos);
int leds_release(struct inode *inode, struct file *file);

/* per led structure */
struct leds_device {
    int number;    
    int status;
    struct cdev cdev;
} leds_dev[NUM_LEDS];

/* file operations structure */
static struct file_operations leds_fops = {
    .owner      = THIS_MODULE,
    .open       = leds_open,
    .release    = leds_release,
    .read       = leds_read,
    .write      = leds_write,
};

/* leds driver major number */
static dev_t leds_dev_number;

/* initialize led port - GPB */
void initLedPort(void)
{
    void __iomem *base = (void __iomem *)GPBCON;
    
    u32 port = __raw_readl(base);
        
    port &= CLEAR_PORT_MASK;
    port |= SET_WRITE_PORT_MASK;
    
    __raw_writel(port, base);
}

/* change led status */
void changeLedStatus(int led_num, int status)
{
    void __iomem *base = (void __iomem *)GPBDAT;
    u32 mask, data;
    
    data = __raw_readl(base);
    
    mask = 0x01 << (4 + led_num);
    
    switch (status) {
        
        case LED_ON:
            mask = ~mask;
            data &= mask;
            break;
            
        case LED_OFF:
            data |= mask;
            break;
    }
    
    __raw_writel(data, base);
}


/* driver initialization */
int __init leds_init(void)
{
    int ret, i;
        
    /* request device major number */
    if ((ret = alloc_chrdev_region(&leds_dev_number, 0, NUM_LEDS, DEVICE_NAME) < 0)) {
        printk(KERN_DEBUG "Error registering device!\n");
        return ret;
    }
    
    /* init leds GPIO port */
    initLedPort();
    
    /* init each led device */
    for (i = 0; i < NUM_LEDS; i++) {
        
        /* init led status */
        leds_dev[i].number = i + 1;
        leds_dev[i].status = LED_OFF;
    
        /* connect file operations to this device */
        cdev_init(&leds_dev[i].cdev, &leds_fops);
        leds_dev[i].cdev.owner = THIS_MODULE;
    
        /* connect major/minor numbers */
        if ((ret = cdev_add(&leds_dev[i].cdev, (leds_dev_number + i), 1))) {
            printk(KERN_DEBUG "Error adding device!\n");
            return ret;
        }
        
        /* init led status */
        changeLedStatus(leds_dev[i].number, LED_OFF);
    }
    
    printk("Leds driver initialized.\n");
    
    return 0;        
}

/* driver exit */
void __exit leds_exit(void)
{
    /* release major number */
    unregister_chrdev_region(leds_dev_number, NUM_LEDS);
    
    printk("Exiting leds driver.\n");
}

/* open led file */
int leds_open(struct inode *inode, struct file *file)
{
    struct leds_device *leds_devp;
    
    /* get cdev struct */
    leds_devp = container_of(inode->i_cdev, struct leds_device, cdev);
    
    /* save cdev pointer */
    file->private_data = leds_devp;
    
    /* return OK */
    return 0;
}

/* close led file */
int leds_release(struct inode *inode, struct file *file)
{
    /* return OK */
    return 0;
}

/* read led status */
ssize_t leds_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
    struct leds_device *leds_devp = file->private_data;
    
    if (leds_devp->status == LED_ON) {
        if (copy_to_user(buf, "1", 1))
            return -EIO;
    }
    else {
        if (copy_to_user(buf, "0", 1))
            return -EIO;
    }
    
    return 1;
}

ssize_t leds_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
    struct leds_device *leds_devp = file->private_data;
    char kbuf = 0;
    
    if (copy_from_user(&kbuf, buf, 1)) {
        return -EFAULT;
    }
    
    if (kbuf == '1') {
        changeLedStatus(leds_devp->number, LED_ON);
        leds_devp->status = LED_ON;
    }
    else if (kbuf == '0') {
        changeLedStatus(leds_devp->number, LED_OFF);
        leds_devp->status = LED_OFF;
    }
        
    return count;
}

module_init(leds_init);
module_exit(leds_exit);
MODULE_AUTHOR("hethongnhung.com");
MODULE_LICENSE("GPL");

file đính kèm : leds

Leave a comment

2 Responses to “Hướng dẫn lập trình device driver cơ bản_Phần 2”

  1. mig29
    January 15, 2015 at 7:35 am #

    Kdir := /opt/buildroot/buildroot-2010.08/output/build/linux-2.6/

    cho mình hỏi đường dẫn này là thư mục nằm trên kit hay thư mục nằm trên máy tính Host
    và biên dịch driver là biên dịch chéo hay biên dịch trực tiếp

  2. Pham Van Dong
    January 16, 2015 at 11:26 am #

    hi bạn,đây là đường dẫn chứa source linux kernel trên máy tính host, điều kiện là bạn phải build kernel thành công trước để thư mục này tạo ra các file thư viện phù hợp với phần cứng của mình. Do đó nếu source linux chưa build được kernel thì sẽ không build được driver.

Leave a Comment