1
TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN ĐIỆN TỬ - VIỄN THÔNG
BÁO CÁO BÀI TẬP LỚN
Môn: Hệ điều hành
Đề tài: Viết driver chuột usb cho Linux
Giáo viên hướng dẫn: Phạm Doãn Tĩnh
Sinh viên: MSSV
Trần Sơn Tùng 20102506
Trần Tiến Thành
5/2014
2
3
Lời mở đầu
Hiện nay, với sự phát triển ngày càng nhanh của hệ thống nhúng để đáp ứng lại nhu cầu
thị trường, dễ dàng có thể thấy hệ điều hành Linux xuất hiện ở hầu hết các thiết bị có độ phức
tạp cũng như khả năng đáp ứng cao. Việc tích hợp hệ điều hành lên hệ thống nhúng giúp đơn
giản hóa quá trình thiết kế sản phẩm, rút ngắn thời gian cũng như chi phí xây dựng hệ thống
vì các ứng dụng này được kế thừa sự ưu việt của một hệ điều hành nói chung và Linux nói
riêng. Đó là sự nhỏ gọn, ổn định, thực thi nhanh, đơn giản hóa và khả năng can thiệp sâu vào
phần cứng. Hơn thế nữa, cùng với cộng đồng sử dụng Linux rộng lớn trên khắp thế giới và
các phần mềm mã nguồn mở đa dạng làm cho việc phát triển hệ thống Linux nhúng trở thành
một chiến lược được các công ty lựa chọn hàng đầu.
Mặt khác, chuẩn giao tiếp USB (Universal Series Bus) đã và đang trở thành một trong
những chuẩn giao tiếp phổ biến nhất. Hiện tại, USB đã trở thành chuẩn kết nối cũng như
phương thức truyền dữ liệu thân thuộc với người dùng công nghệ nhờ vào sự thuận tiện, độ
bền và giá thành hợp lý của nó.
Do đó chúng em quyết định chọn đề tài “Viết driver chuột USB cho hệ điều hành Linux”
làm bài tập lớn cho môn học Hệ điều hành. Vì đây là một đề tài tương đối phức tạp và thời
gian thực hiện có hạn, nên nhóm chúng em chỉ dừng lại ở mức độ đi sâu phân tích mã nguồn
của hệ điều hành nhằm tìm hiểu cơ chế quản lý giao tiếp USB của Linux. Phần code được sử
Cấu trúc dữ liệu của endpoint được định nghĩa trong struct usb_host_endpoint của
Linux kernel. Trong đó thông tin thực sự của endpoint được chứa trong struct
usb_endpoint_descriptor.
Các thông số cần quan tâm:
- bEndpointAddress: địa chỉ của endpoint, trong đó có 8 bit dùng mã hóa hướng của
endpoint là IN hay OUT.
- bmAttributes: Dùng định nghĩa loại endpoint là kiểu nào trong 4 kiểu controll, bulk,
interupt hay isochorous.
- wMaxPacketSize: Kích thước tối đa gói tin mà endpoint có thể chuyển đi. Nếu gói
tin cần chuyển lớn hơn giá trị này thì nó sẽ bị chia thành các gói có kích thước tương
đương.
- bInterval: Nếu endpoint là loại interupt thì biến này sẽ xác định khoảng thời gian
giữa các lần gửi request từ host đến device (đo bằng mili giây).
b. Interface: Tập hợp các endpoint gọi là interface. Mỗi interface thể hiện một chức năng cơ
bản duy nhất của thiết bị, ví dụ như ổ flash, hay bàn phím. Một thiết bị có thể có nhiều
interface. Một interface có nhiều thiết lập, với thiết lập ban đầu được đánh số 0. Thiết lập
khác nhau có thể dùng điều khuyển endpoint theo các cách khác nhau…
Interface được định nghĩa trong Linux kernel bằng struct usb_interface.
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc;
struct usb_ss_ep_comp_descriptor ss_ep_comp;
struct list_head urb_list;
void * hcpriv;
struct ep_device * ep_dev;
unsigned char * extra;
int extralen;
int enabled;
int streams;
};
6
unsigned needs_remote_wakeup:1;
unsigned needs_altsetting0:1;
unsigned needs_binding:1;
unsigned reset_running:1;
unsigned resetting_device:1;
struct device dev;
struct device * usb_dev;
atomic_t pm_usage_cnt;
struct work_struct reset_ws;
};
7
2. Mô hình giao thức USB
a. Chuẩn tín hiệu
Chuẩn USB sử dụng 4 đường tín hiệu trong đó có 2 đường cấp nguồn DC (VBUS-5V và
GND). 2 đường còn lại là một cặp tín hiệu vi sai (D+ và D-) cho phép truyền dữ liệu. Cặp
dây tín hiệu này được nối xoắn ở bên trong nên có khả năng chống nhiễu tốt.
b. Mô hình mạng
8
Các thiết bị hoạt động theo chuẩn USB được kết nối với nhau theo đồ hình mạng hình
sao phân cấp. Trung tâm của mỗi hình sao này là các Hub. Trong đồ hình như vậy, các thiết
bị USB được chia làm 3 loại chính:
- USB Host: thiết bị đóng vai trò điều khiển toàn bộ mạng USB (có thể lên tới tối đa
126 thiết bị). Ví dụ như trên máy tính, USB Host được gắn trên mainboard. Để giao
tiếp và điều khiển các USB device, USB Host controller cần được thiết kế tích hợp
với USB RootHub (Hub mức cao nhất). Vai trò của thiết bị USB Host:
• Trao đổi dữ liệu với các USB Device
• Điều khiển USB Bus:
Quản lý các thiết bị cắm vào hay rút ra khỏi Bus USB qua quá trình điểm
danh (Enumeration)
Phân xử, quản lý luồng dữ liệu trên Bus, đảm bảo các thiết bị đều có cơ
Thực tế các Endpoint cũng như các Port trong chuẩn TCP/IP đóng vai trò như
các bộ đệm truyền/nhận dữ liệu. Nhờ việc sử dụng nhiều bộ đệm mà các quá trình
truyền thông được tiến hành song song và cho tốc độ cao hơn, bên cạnh đó giúp cho
việc phân tách các dịch vụ khác nhau. Với chuẩn USB, các thiết bị được thiết kế với
tối đa là 16 Endpoint.
II. USB driver
1. Khung chương trình của một USB driver bất kì
2. Xây dựng driver cho chuột USB
a. Thư viện
b. Khởi tạo
Cấu trúc sau đây dùng mô tả thiết bị chuột USB – là 1 input device giao tiếp bằng urb.
- Name: tên thiết bị
- Phys:
struct usb_<tên thiết bị> {
};
/* Chứa thông tin thiết bị */
static struct usb_device_id <tên thiết bị>_table [] = {
{ USB_DEVICE(ML_VENDOR_ID, ML_PRODUCT_ID) },
{ }
};
/* Chứa id thiết bị*/
static int <tên thiết bị>_open(struct inode *inode, struct file *file)
{
/* open syscall */
}
static int <tên thiết bị>_release(struct inode *inode, struct file *file)
{
/* close syscall */
}
static ssize_t <tên thiết bị>_write(struct file *file, const char __user *user_buf,
static void __exit usb_<tên thiết bị>_exit(void)
{
/* Giải phóng tài nguyên */
}
module_init(usb_<tên thiết bị>_init);
module_exit(usb_<tên thiết bị>_exit);
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
struct usb_mouse {
char name[128];
char phys[64];
struct usb_device *usbdev;
struct input_dev *dev;
struct urb *irq;
signed char *data;
dma_addr_t data_dma;
};
11
- *usbdev: con trỏ dùng để trỏ tới thiết bị đã gửi urb đi.
- *dev: con trỏ dùng mô tả 1 thiết bị đầu vào.
- *irq: con trỏ kiểu urb – USB request block. USB code dùng urb để giao tiếp với các
thiết bị USB. Urb được dùng để gửi đi cũng như nhận dữ liệu từ endpoint của thiết
bị theo phương thức không đồng bộ. Chu kì của 1 urb như sau:
• Được tạo bởi usb device driver
• Được gán vào một endpoint của driver
• Được đệ trình tới USB core bởi driver
switch (urb->status) {
case 0: /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit;
}
12
- Nếu status rơi vào trường hợp đầu tiên, nó sẽ thoát khỏi cấu trúc switch và nhảy đến
khối lệnh tiếp theo:
Các lệnh trên gửi các tín hiệu ngắt khi các nút tương ứng được bấm, hoặc lấy tọa độ
tương đối của chuột.
- Nếu status rơi vào 3 trường hợp sau thì chương trình sẽ trả về giá trị và nhảy đến
nhãn resubmit:
Ở đây urb sẽ được chuyển thành trạng thái đã đệ trình, bằng cách gọi đến hàm
int usb_submit_urb(struct urb *urb, int mem_flags); tại đây cờ mem_flags được sử
dụng để thiết lập phương thức phân bố bộ nhớ đệm cho USB core. Khi urb được đệ
trình, chúng ta không được phép truy nhập vào bất cứ một trường nào của struct urb,
cho đến khi urb được hoàn tất. Mặt khác usb_submit_urb có thể được gọi bất cứ lúc
nào, do đó cờ mem_flags được sử dụng nhằm tránh xung đột. Đối với trường hợp
usb_submit_urb được gọi trong hàm xử lý ngắt, thì mem_flags sẽ được gán bằng
GFP_ATOMIC.
Phần code bên dưới khối code sẽ in ra thông báo lỗi nếu quá trình urb bị lỗi.
d. Probe
Hàm sau đây dùng để phát hiện nếu chuột được kết nối, và thực hiện quá trình urb:
*id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct input_dev *input_dev;
int pipe, maxp;
int error = -ENOMEM;
interface = intf->cur_altsetting;
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
13
- bNumEndpoints là 1 biến của struct usb_interface_descriptor thuộc struct
usb_host_interface, có giá trị bằng số endpoint của interface đang xét.
- Nếu không có hoặc có nhiều hơn 1 endpoint, sẽ trả về lỗi –ENODEV (không tồn tại
thiết bị nào như thế - do chuột chỉ có 1 endpoint duy nhất)
- Hàm usb_endpoint_is_int_in(endpoint) kiểm tra nếu endpoint này là interupt IN-
endpoint. Nó sẽ trả về true nếu đúng, và false nếu sai. Lỗi –ENODEV sẽ được trả
về nếu điều kiện không phải là true.
- Lệnh đầu tiên sẽ đặt địa chỉ endpoint cho thiết bị chuột đang xét.
- Lệnh thứ 2 thiết lập kích thước gói tin tối đa cho endpoint
- Lệnh thứ 3 phân bố bộ nhớ thiết bị. Bộ nhớ được đặt bằng 0.
- Lệnh (1) thực hiện phân bố bộ nhớ cho thiết bị đầu vào
- Lệnh (2) thực hiện phân bố bộ nhớ đệm theo cơ chế DMA cho chuột.
- Chương trình sẽ nhảy đến fail1 nếu không nhận được dữ liệu từ chuột, hoặc thiết bị
không kết nối.
- fail1: giải phóng bộ nhớ dành cho thiết bị đầu vào cũng như chuột, và trả về lỗi
–ENOMEM.
- Bắt đầu tạo một urb mới cho chuột sử dụng
input_free_device(input_dev);
kfree(mouse);
return error;
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
goto fail2;
fail2:
usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
mouse->usbdev = dev;
mouse->dev = input_dev;
if (dev->manufacturer)
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}
if (!strlen(mouse->name))
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
input_dev->name = mouse->name;
input_dev->phys = mouse->phys;
usb_to_input_id(dev, &input_dev->id);
input_dev->dev.parent = &intf->dev;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
Hàm usb_get_intfdata có chức năng như thế, nó sẽ lấy lại những dữ liệu đã được
cấp phát trước đó bởi hàm usb_set_intfdata.
- Sau khi đã lấy lại dữ liệu, hàm usb_set_intfdata sẽ thiết lập dữ liệu về mức NULL
nhằm tránh những sai sót phát sinh trong quá trình truy nhập dữ liệu.
- Khối lệnh tiếp theo sẽ giải phóng tài nguyên cho thiết bị. Bao gồm:
• Hủy quá trình urb
• Hủy đăng kí thiết bị
• Giải phóng tài nguyên urb. Khi lệnh usb_free_urb được gọi ra, struct urb đó sẽ bị
xóa và driver sẽ không thể truy nhập urb đó được nữa.
• Giải phóng bộ nhớ và bộ đệm đã được cấp phát.
static int usb_mouse_open(struct input_dev *dev)
{
struct usb_mouse *mouse = input_get_drvdata(dev);
mouse->irq->dev = mouse->usbdev;
if (usb_submit_urb(mouse->irq, GFP_KERNEL))
return -EIO;
return 0;
}
static void usb_mouse_close(struct input_dev *dev)
{
struct usb_mouse *mouse = input_get_drvdata(dev);
usb_kill_urb(mouse->irq);
}
static void usb_mouse_disconnect(struct usb_interface *intf)
{
struct usb_mouse *mouse = usb_get_intfdata (intf);
usb_set_intfdata(intf, NULL);
if (mouse) {
usb_kill_urb(mouse->irq);
input_unregister_device(mouse->dev);