Trung tâm đào tạo thiết kế vi mạch Semicon


  • ĐĂNG KÝ TÀI KHOẢN ĐỂ TRUY CẬP NHIỀU TÀI LIỆU HƠN!
  • Create an account
    *
    *
    *
    *
    *
    Fields marked with an asterisk (*) are required.
semicon_lab.jpg

Qui trình viết usb device driver trên Linux

E-mail Print PDF

Giới thiệu

- USB là một chuẩn giao tiếp đang được sử dụng rộng rãi không chỉ đối với máy tính PC mà còn được lựa chọn ngày càng nhiều trong giao tiếp trên các hệ thống nhúng. Bài viết này trình bày tóm tắt và tổng quát cách viết một driver cho thiết bị usb trên Linux.

- Trước khi tìm hiểu qui trình này, người đọc nên tham khảo thêm bài viết về chuẩn giao tiếp USBhttp://semiconvn.com/home/hoc-thiet-ke-vi-mach/bai-hc-vi-mch/9338-giao-thuc-usb.html

Mô hình phân lớp hệ thống usb trên Linux


Hệ thống USB trên Linux được phân làm nhiều tầng. Tầng USB Device Drivers nằm giữa 2 tầng: USB Core và các hệ thống con khác nhau của nhân (Kernel Subsystems) như: hệ thống con thiết bị kiểu khối (Block), hệ thống con thiết bị kiểu kí tự (Char), hệ thống con thiết bị đầu vào (Input)... USB Core do nhân hệ điều hành Linux cung cấp, với các giao diện lập trình (Application Programming Interface – API) nhằm hỗ trợ các thiết bị USB và USB Host Controller. Mục tiêu của USB Core là trừu tượng hóa tất cả các phần cứng và các thành phần phụ thuộc thiết bị. Các USB Device Driver sẽ sử dụng lại các dịch vụ của tầng USB Core mà không cần quan tâm tới các nền tảng phần cứng khác nhau. USB Device Driver cũng cần xuất ra các API lên tầng trên. Tùy theo thiết bị có thể được xếp vào các lớp con như lớp vào dữ liệu (Input), lớp âm thanh (Sound), lớp bộ nhớ (memory) mà Driver có thể xuất ra các API khác nhau. Các ứng dụng người dùng khi muốn giao tiếp với thiết bị phần cứng sẽ sử dụng các API này để yêu cầu USB Device Driver thực hiện

Qui trình viết usb device driver trên Linux

1. Tìm hiểu về thiết bị usb muốn giao tiếp (usb device)

- Đầu tiên cần có thông tin về Firmware trên thiết bị. Các thông tin cần thiết bao gồm: idVendor, idProduct, số lượng Configuration, số lượng Interface trong từng Configuration, số lượng và loại Endpoint trong từng Interface.

-Nếu chúng ta là người phát triển firmware trên thiết bị thì hiển nhiên biết các thông tin này. Hoặc có thể tìm trong tài liệu đi kèm thiết bị. Hoặc cũng có một vài công cụ giúp xác định thông tin về thiết bị usb như USB Snoopy, USB Control Center (của Cypress) trên Windows, hoặc lệnh lsusb trên Linux.

Trên hệ điều hành Linux ta chỉ việc kết nối thiết bị tới máy tính, chạy lệnh lsusb trên terminal, tất cả các thiết bị USB đang kết nối với máy tính sẽ được liệt kê ra. Từ đó ta có thể biết được idVendor và idProduct của thiết bị. Tiếp tục, gõ lệnh lsusb –vd <idVendor>:<idProduct> để hiển thị các thông tin về cấu hình USB của thiết bị.

2. Khai báo danh sách các thiết bị có thể được điều khiển bởi Driver

- Khi viết một USB Driver người lập trình viên cần chỉ định xem Driver này sẽ được sử dụng cho các thiết bị hoặc các lớp thiết bị nào. Cấu trúc usb_device_id cung cấp một kiểu thiết bị, nó có thể là một thiết bị cụ thể cũng có thể là một lớp thiết bị. Có một số macro để khởi tạo cấu trúc này.

- Ví dụ sử dụng macro USB_DEVICE (vendor, product) để tạo ra một cấu trúc usb_device_id với IDvendor và IDproduct.

Sau khi đã tạo ra một cấu trúc usb_device_id ta cần phải khai báo cấu trúc này với USB Core, để làm việc này ta sử dụng macro

MODULE_DEVICE_TABLE(usb, usb_device_id[]);

trong đó usb_device_id[] là một mảng các cấu trúc usb_device_id đã khởi tạo trước đó.

Ví dụ:

static struct usb_device_id mydev_table[] = {

     { USB_DEVICE (0x2179, 0x1989) }, { }

};

MODULE_DEVICE_TABLE(usb, mydev_table);


3. Khai báo cấu trúc dữ liệu liên quan tới thiết bị

 

- Trong quá trình thăm dò thiết bị, có rất nhiều thông tin về thiết bị ta truy nhập được và cần phải lưu lại để sử dụng về sau. Các thông tin cần thiết bao gồm: thiết bị cụ thể (được xác định bởi cấu trúc usb_device), thông tin các Configuration, Interface, Endpoint của thiết bị. Tùy theo đặc điểm của từng phần cứng cụ thể mà ta định nghĩa một cấu trúc dữ liệu phù hợp.

Ví dụ: Thiết bị có 1 Configuration, Configuration đó có 1 Interface, Interface có 2 Endpoint là Bulk IN và Bulk OUT, ta có thể định nghĩa một cấu trúc dữ liệu như sau:

//cấu trúc dữ liệu lưu trữ các thông tin về thiết bị,

//endpoint, bộ đệm

struct usb_mydevice {

//con trỏ tới cấu trúc mô tả thiết bị

     struct usb_device         *udev;

     struct usb_interface      *interface;

//bộ đệm dữ liệu vào

     unsigned char              *bulk_in_buffer;

//kích thước bộ đệm dữ liệu vào

     size_t                    bulk_in_size;

//địa chỉ 2 Endpoint Bulk IN và OUT

     __u8                      bulk_in_endpointAddr;

     __u8                      bulk_out_endpointAddr;

};

4. Đăng kí và hủy đăng kí USB Device Driver

- Để tầng USB Core có thể nhận ra Driver, lập trình viên cần phải đăng kí Driver đó. Sử dụng hàm sau để làm việc này:

usb_register(struct usb_driver &);

 

- Hàm này thường được gọi trong hàm khởi tạo mô-đun Driver đang viết.Tham số cần truyền cho hàm này là một con trỏ tới cấu trúc usb_driver. Cấu trúc này bao gồm các thông tin về Driver đang viết. Cấu trúc này được định nghĩa như sau:

 

struct usb_driver {

     const char * name;

     int (* probe) (struct usb_interface *intf,

const struct usb_device_id *id);

     void (* disconnect) (struct usb_interface *intf);

     int (* ioctl) (struct usb_interface *intf,

unsigned int code, void *buf);

     int (* suspend) (struct usb_interface *intf,

pm_message_t message);

     int (* resume) (struct usb_interface *intf);

     int (* reset_resume) (struct usb_interface *intf);

     int (* pre_reset) (struct usb_interface *intf);

     int (* post_reset) (struct usb_interface *intf);

     const struct usb_device_id * id_table;

     struct usb_dynids dynids;

     struct usbdrv_wrap drvwrap;

     unsigned int no_dynamic_id:1;

     unsigned int supports_autosuspend:1;

};

 

- Các thông tin quan trọng gồm:

const char* name : tên của Driver

const struct usb_device_id* id_table : con trỏ tới bảng chứa các thiết bị sẽ được điều khiển bởi Driver này và đã được khai báo bằng macro MODULE_DEVICE_TABLE().

int (*probe) (struct usb_interface *intf, const struct usb_device_id* id) : đây là một tham số rất quan trọng. Tham số này là một con trỏ tới một hàm (hàm thăm dò), hàm này sẽ được gọi khi thiết bị được kết nối tới hệ thống. Trong hàm này ta sẽ thực hiện các công việc quan trọng như xác định các Endpoint, cấp phát bộ nhớ…

void (*disconnect) (struct usb_interface* intf) : Một con trỏ tới một hàm (hàm ngắt kết nối), hàm này sẽ được gọi khi thiết bị được gỡ bỏ ra khỏi hệ thống. Trong hàm này lập trình viên cần phải thực hiện công tác dọn dẹp hệ thống như giải phóng bộ nhớ, hủy các công việc đang dở dang…

Để hủy đăng kí một USB Device Driver ra khỏi hệ thống, ta sử dụng hàm sau:

usb_deregister(struct usb_driver &);

Hàm này thường được gọi trong hàm kết thúc mô-đun Driver đang viết.

5. Hàm thăm dò thiết bị (probe)

- Khi thiết bị mới được kết nối tới hệ thống, nếu Driver được chỉ định cho điều khiển thiết bị đó thì hàm thăm dò của Driver sẽ được gọi. USB Core truyền tới hàm thăm dò một con trỏ tới cấu trúc usb_interface mô tả Interface được chọn trên thiết bị.

Nguyên mẫu hàm thăm dò như sau:

int (*probe) (struct usb_interface *intf, const struct usb_device_id* id);

 

- Trong hàm thăm dò, Driver cần thực hiện một số công việc sau:

Lấy ra địa chỉ các Endpoint cần dùng, lấy ra kích thước các bộ đệm cho thiết bị

Cấp phát bộ đệm

 Lưu lại các thông tin (địa chỉ Endpoint, kích thước bộ đệm, địa chỉ bộ đệm…)

Đăng kí lớp thiết bị cho Driver

5.1. Truy nhập các thông tin về Endpoint

Đoạn mã sau thực hiện việc lấy các thông tin của một Endpoint Bulk IN và một Endpoint Bulk OUT:

iface_desc = interface->cur_altsetting;

for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {

     endpoint = &iface_desc->endpoint[i].desc;

 

     //bulk_in

     if (!dev->bulk_in_endpointAddr &&

usb_endpoint_is_bulk_in(endpoint)) {

           buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);

           dev->bulk_in_size = buffer_size;

           dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;

           dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);

           if (!dev->bulk_in_buffer) {

                err("Could not allocate bulk_in_buffer");

                goto error;

           }

     }

     //bulk_out

     if (!dev->bulk_out_endpointAddr &&

           usb_endpoint_is_bulk_out(endpoint)) {

           dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;

     }

}


5.2. Lưu lại các thông tin đã truy nhập và cấp phát

Để lưu trữ các dữ liệu liên quan tới thiết bị nhằm mục đích sử dụng sau, ta sử dụng hàm sau:

usb_set_intfdata(intf, dev);

Trong đó dev là con trỏ tới cấu trúc dữ liệu ta cần lưu trữ. (cấu trúc dữ liệu giao tiếp với thiết bị được người lập trình khai báo thích hợp với dữ liệu thiết bị cung cấp)

5.3. Đăng kí lớp thiết bị

- Như ta đã thấy trong mô hình phân lớp của hệ thống USB trên Linux, phía trên lớp USB Device Driver là lớp Các hệ thống con nhân Linux (hay Các lớp thiết bị), bao gồm: Char, Block, TTY, Mem… Các thiết bị USB có thể được đối xử theo một trong các hệ thống con đó, chẳng hạn như chuột, bàn phím có thể coi như thuộc lớp Input. Tức là các ứng dụng người dùng sẽ sử dụng các API lớp Input để thực hiện giao tiếp với chuột, bàn phím. Do đó Driver cũng cần đăng kí thực hiện đăng kí lớp thiết bị mà nó quản lý. Tùy theo từng lớp  có các hàm đăng kí khác nhau:

Nếu Driver muốn đối xử với thiết bị như kiểu Input, ta sử dụng hàm:

input_register_device(struct input_dev* )

Nếu Driver muốn đối xử với thiết bị như một thiết bị kiểu kí tự thông thường ta sử dụng hàm:

usb_register_dev(struct usb_interface*, struct usb_class_driver* )

- Tham số đầu tiên cần truyền cho hàm usb_register_dev là con trỏ tới cấu trúc chứa thông tin về Interface được chọn trên thiết bị, tham số thứ hai là con trỏ tới cấu trúc usb_class_driver. Cấu trúc này có một trường quan trọng là một con trỏ tới cấu trúc file_operations định nghĩa các thao tác trên thiết bị được Driver hỗ trợ. Hai cấu trúc này được định nghĩa như dưới đây:

struct file_operations {

     struct module *owner;

     loff_t (*llseek) (struct file *, loff_t, int);

     ssize_t (*read) (struct file *, char *, size_t, loff_t *);

     ssize_t (*write) (struct file *,

const char *, size_t, loff_t *);

     int (*readdir) (struct file *, void *, filldir_t);

     unsigned int (*poll) (struct file *,

struct poll_table_struct *);

     int (*ioctl) (struct inode *,

struct file *, unsigned int, unsigned long);

     int (*mmap) (struct file *, struct vm_area_struct *);

     int (*open) (struct inode *, struct file *);

     int (*flush) (struct file *);

     int (*release) (struct inode *, struct file *);

     int (*fsync) (struct file *, struct dentry *, int datasync);

     int (*fasync) (int, struct file *, int);

     int (*lock) (struct file *, int, struct file_lock *);

     ssize_t (*readv) (struct file *, const struct iovec *,

                               unsigned long, loff_t *);

     ssize_t (*writev) (struct file *, const struct iovec *,

                                unsigned long, loff_t *);

     ssize_t (*sendpage) (struct file *, struct page *, int,

                                size_t, loff_t *, int);

     unsigned long (*get_unmapped_area)(struct file *,

unsigned long, unsigned long,

unsigned long,unsigned long);

};

struct usb_class_driver {

     char * name;

     struct file_operations * fops;

     mode_t mode;

     int minor_base;

};

Trong cấu trúc file_operations các trường thông tin chính là các con trỏ hàm. Các con trỏ này trỏ tới các hàm tương ứng với các thao tác trên tệp tin như: mở, đọc,ghi, ioctl, xóa… Chúng ta cần cài đặt các hàm này để khi có hành động tương ứng trên tệp tin, hàm của chúng ta sẽ thực thi các công việc cần thiết như: chuẩn bị dữ liệu, chuyển dữ liệu từ không gian nhân sang không gian người dùng và ngược

lại, giải phóng dữ liệu…

6. Hàm ngắt kết nối thiết bị

Khi thiết bị được gỡ bỏ ra khỏi hệ thống, hàm ngắt kết nối được gọi. Nguyên mẫu hàm như sau:

void (*disconnect) (struct usb_interface* intf);

Trong hàm disconnect cần thực hiện hai công việc sau:

Hủy các dữ liệu về thiết bị đã lưu trữ từ hàm thăm dò, để làm điều này ta sẽ thiết lập dữ liệu NULL cho interface intf:

usb_set_intfdata(intf, NULL);

 Hủy đăng kí lớp thiết bị:

usb_deregister_dev(struct usb_interface* , struct usb_class_driver* );

7. Các hàm mở / đọc / ghi thiết bị

7.1. Hành động mở tệp tin thiết bị

static int mydevice_open(struct inode *inode, struct file *file);


Hành động này có tác dụng chuẩn bị cho các hành động đọc, ghi sau đó. Trong hàm này ta sử dụng hàm usb_get_intfdata() để lấy ra các thông tin liên quan tới thiết bị đã lưu trữ từ hàm probe() (bằng hàm usb_set_intfdata), và thiết lập dữ liệu này cho cấu trúc file.

 

dev = usb_get_intfdata(interface);

file->private_data = dev;

 



- Cấu trúc file (được định nghĩa trong <linux/fs.h>) là một cấu trúc rất quan trọng trong Driver. Chúng ta cần chú ý rằng đây là một cấu trúc dữ liệu trong không gian nhân và cấu trúc này sẽ không liên quan gì tới con trỏ FILE* trong thư viện của ngôn ngữ C trong không gian người dùng. Cấu trúc file thể hiện một tệp tin đang mở trong hệ thống Linux. Khi một tệp tin được mở, một thể hiện của cấu trúc này được tạo ra và liên kết với tệp tin đó. Khi bất kỳ hàm nào (đọc, ghi…) thao tác trên tệp tin, thể hiện của cấu trúc này sẽ được truyền cho hàm đó. 

7.2. Hành động đọc / ghi

A. Hành động đọc

static ssize_t mydevice_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos);

Hàm này có hai việc chính, đầu tiên là tạo ra các URB (USB request block) yêu cầu dữ liệu và xác nhận nó tới tầng USB Core, thứ hai là chuyển 

dữ liệu nhận được từ các URB sang không gian người dùng bằng hàm:

unsigned long copy_to_user(void __user * to, const void * from, unsigned long size);

B. Hành động ghi

static ssize_t my_device  write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos);

Hàm này sẽ thực hiện hai công việc ngược lại với hàm đọc. Đầu tiên nó phải chuyển dữ liệu từ không gian người dùng sang không gian nhân hệ 

điều hành, sau đó nó cần đóng gói dữ liệu này trong các URB và xác nhận các URB này tới tầng USB Core.

Để copy dữ liệu người dùng vào không gian nhân, ta sử dụng hàm:

unsigned long copy_from_user(void * to, const void __user * from, unsigned long size);

Minh họa driver cho 1 thiết bị usb 

Chúng tôi minh họa viết driver cho 1 thiết bị usb giả định là KIT AT91SAM7, KIT được phát triển firmware giao tiếp qua chuẩn usb với khả năng liên tục gửi các khối dữ liệu qua giao tiếp usb.

Driver được biên dịch và cài đặt trên KIT FriendlyARM (micro2440) hoặc PC Linux. Chương trình Qt trên tầng ứng dụng sử dụng driver này để nhận dữ liệu gửi lên từ thiết bị và vẽ đồ thị các mảng dữ liệu này.

Video kết quả minh họa:

Nguồn https://sites.google.com


Bạn có đam mê ngành thiết kế vi mạch và bạn muốn có mức lương 1000 usd cùng lúc bạn

đang muốn tìm một Trung tâm để học vậy hãy đến với ngành vi mạch tại SEMICON

  HotLine: 0972 800 931 Ms Duyên

 

 

 

 

 

Last Updated ( Thursday, 22 October 2015 09:13 )  

Related Articles

Chat Zalo