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 1

           Ở 2 bài trước mình đã làm bài hướng dẫn viết chương trình điều khiển gpio theo kiểu biên dịch và chạy trực tiếp trên lớp application. Còn trong bài viết này mình sẽ giới thiệu về cách viết 1 character device driver điều khiển gpio trên mini2440. Chúng ta sử dụng để điều khiển 4 led portB từ GPB5 đến GPB8. Ta tạo 4 device file : /dev/led1 , /dev/led2, /dev/led3, /dev/led4 để quản lý trạng thái của các led. echo “1” > /dev/led1 để bật led1 và ngược lại echo “0” > /dev/led1 để tắt led1.

Đi vào phần code đầu tiên :

#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
 
/* 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;
 
/* 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)
{
    int i;
 
    /* delete devices */
    for (i = 0; i < NUM_LEDS; i++) {
        cdev_del(&leds_dev[i].cdev);
    }
 
    /* release major number */
    unregister_chrdev_region(leds_dev_number, NUM_LEDS);
 
    printk("Exiting leds driver.\n");
}
 
module_init(leds_init);
module_exit(leds_exit);
MODULE_AUTHOR("hethongnhung.com");
MODULE_LICENSE("GPL");

Giới thiệu qua về 2 hàm cơ bản và bắt buộc trong cấu trúc 1 driver là : init() và exit().

*  Giải thích hàm init() :  là hàm được thực thi đầu tiên khi thực hiện cài đặt driver vào hệ thống linux bằng lệnh shell insmod driver_name.ko . Hàm init có một vai trò quan trọng trong lập trình driver. Ban đầu hàm sẽ gọi thực thi các hảm xác định số định danh thiết bị; Cập nhật các thông số của cấu trúc tập tin, cấu trúc lệnh, đăng ký các cấu trúc vào hệ thống linux, khởi tạo các thông số điều khiển ban đầu cho các thiết bị ngoại vi mà driver điều khiên . Hàm init có kiểu dữ liệu trả về dạng int, nếu trả về static nghĩa là hàm init chi dùng riêng cho driver sở hữu.

* Giải thích hàm exit() : là hàm được thực hiện ngay trước khi driver được tháo gỡ khỏi hệ thống bằng lệnh shell rmmod device.ko. Những tác vụ bên trong hàm exit được lập trình nhằm khôi phục lại trạng thái hệ thống trước khi cài đặt driver. Chẳng hạn như giải phóng vùng nhớ, timer, vô hiệu hóa các nguồn phát sinh ngắt, …

Ở đây có 2 biến struct được khai báo là : leds_dev[] để lưu trữ thông tin cho mỗi led và leds_fops là Cấu trúc để đăng ký các hàm qua driver interface được được qui định theo chuẩn. bên trong cấu trúc leds_dev có 1 biến cấu trúc kiểu cdev (struct cdev cdev). struct cdev là kiểu cấu trúc lưu trữ thông tin của một tập tin lưu trữ trong kernel.

* Giải thích cấu trúc file_operations : Cấu trúc lệnh của character driver (file_operations) là một cấu trúc dùng để liên kết những hàm chứa các thao tác của driver điều khiển thiết bị với những hàm chuẩn trong hệ điều hành giúp giao tiếp giữa người lập trình ứng dụng với thiết bị vật lý. Cấu trúc file_operation được định nghĩa trong thư viện <linux/fs.h>. Mỗi một tập tin thiết bị được mở trong hệ điều hành linux điều được hệ điều hành dành cho một vùng nhớ mô tả cấu trúc tập tin, trong cấu trúc tập tin có rất nhiều thông tin liên quan phục vụ cho việc thao tác với tập tin đó. Một trong những thông tin này là file_operations, dùng mô tả những hàm mà driver thiết bị đang được mở.

Nhiệm vụ đầu tiên mà một driver phải làm là định nghĩa một số định danh  “major number” để liên kết với nó. Có 2 cách để xác định số định danh : xác định số định danh cố định bằng hàm register_chrdev_region() , cách thứ là cấp một cách ngẫu nhiên bởi kernel bằng hàm alloc_chrdev_region(). Tuy nhiên với cách thứ nhất chúng ta không biết trước được số đó đã được sử dụng hay chưa cho nên cách an toàn là sử dụng hàm cấp động alloc_chrdev_region().

* Giải thích hàm alloc_chrdev_region() :

#include <linux/fs.h>

int alloc_chrdev_region (dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

Hàm alloc_chrdev_region () cũng làm nhiệm vụ đăng ký định danh cho một thiết bị mới. Nhưng có một điểm khác biệt là số Major trong định danh không còn cố định nữa, số này do hệ thống linux tự động cấp vì thế sẽ không trùng với bất kỳ số định danh nào khác đã tồn tại. Bắt đầu phân tích từng tham số :

+ Tham số thứ nhất của hàm dev_t *dev :  là con trỏ kiểu dev_t dùng để lưu trữ số định danh đầu tiên trong khoảng định danh được cấp nếu hàm thực hiện thành công .

+ Tham số thứ hai  unsigned int first minor :  là số Minor đầu tiên của khoảng định danh muốn cấp .

+ Tham số thứ ba unsigned int count :  là số lượng định danh muốn cấp, tính từ số Major được cấp động và số Minor unsigned int first minor;

+ Tham số thứ tư char *name : là tên của driver thiết bị muốn đăng ký.

Trở lại ví dụ này alloc_chrdev_region(&leds_dev_number,0, NUM_LEDS, DEVICE_NAME) ta yêu cầu kernel cung cấp cho mình 4 số “minnor numbers” (NUM_LEDS) và bắt đầu từ 0 và lưu trữ “major number” trong leds_dev_number. Nếu được cung cấp thành công chúng ta có thể vào tập tin /proc/devices tìm tên thiết bị vừa cài đặt vào hệ thống, xác định số Major và số Minor động của thiết bị;

Ở dòng 51 chúng ta có 1 vòng lặp để cài đặt cho mỗi led. Ở dòng 58 ta có hàm

cdev_init(&leds_dev[i].cdev,&leds_fops);

Để thông báo cho kernel dành ra một vùng nhớ riêng lưu trữ cdev mới vừa tạo ra.

* Giải thích cấu trúc hàm cdev_init() :

void cdev_init(struct cdev *cdev, struct file_operations *fops);

Với struct cdev *cdev là con trỏ cấu trúc lưu thông tin driver đã được khai báo, struct file_operations *fops là cấu trúc tập lệnh của driver.

Ở dòng 62 ta có hàm :  cdev_add(&leds_dev[i].cdev,(leds_dev_number + i),1) . Sau khi khai báo cho kernel dành một vùng nhớ lưu trữ cấu trúc driver, công việc cuối cùng là triệu gọi hàm cdev_add () để cài đặt cấu trúc này vào kernel.

* Giải thích cấu trúc hàm cdev_add() :

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

Trong đó, struct cdev *dev là con trỏ cấu trúc driver cần cài đặt. dev_t num là số định danh thiết bị đầu tiên muốn cài đặt vào hệ thống, số định danh này được xác định phù hợp với hệ thống thông qua các hàm đặc trưng. Tham số cuối cùng, unsigned int count, là số thiết bị muốn cài đặt vào kernel. Hàm này sẽ trả về giá trị 0 nếu quá trình cài đặt driver vào kernel thành công. Ngược lại sẽ trả về mã lỗi âm. Kernel chỉ có thể gọi được những hàm trong driver khi nó được cài đặt thành công vào hệ thống.

Trong ví dụ này, khi bạn thực hiện 1 lệnh write đến /dev/led1 ( major = 253 và minnor = 0) thì kernel sẽ tìm kiếm cấu trúc cdev liên quan đến device này (giống số major và minnor) mà bạn đã định nghĩa trong hàm cdev_add() và sau đó sử dụng cấu trúc này làm thông số cho hàm write.

Leave a comment

No comments yet.

Leave a Comment