1 USB设备驱动架构
这里说的USB设备驱动指的是从主机角度来看,怎样访问被插入的USB设备,而不是指USB设备内部本身运行的固件程序。Linux内核中实现了几类通用的USB设备驱动,主要包括下面几类:
(1)音频设备类 (2)通信设备类 (3)HID(人机接口)设备类 (4)显示设备类 (5)海量存储设备类 (6)电源设备类 (7)打印设备类
一般的通用Linux设备(如U盘、USB鼠标、USB键盘等)都不需要工程师再编写驱动,而工程师需要编写的是特定厂商、特定芯片的驱动,而且往往也可以参考已经在内核中提供的驱动模版。
Linux内核为各类USB设备分配了相应的设备号,如ACM USB调制解调器的主设备号166(默认设备名/dev/ttyACMn)、USB打印机的主设备号为180,次设备号为0~15(默认设备名/dev/lpn)、USB串口的主设备号为188(默认设备名/dev/ttyUSBn)等。
在debugfs下,/sys/kernel/debug/usb/devices包含了USB的设备信息,在Ubuntu上插入一个U盘后,我们在/sys/kernel/debug/usb/devices中可以看到类似信息:
在sysfs文件系统中,包含了USB相关信息的描述,但只限于接口级别。USB设备和USB接口在sysfs中均表示为单独的USB设备,其目录命名规则如下:
根集线器-集线器端口号(-集线器端口号):[配置.接口]
执行命令“tree /sys/bus/usb”可查看目录的树形结构,如下:
2 USB设备驱动架构
在Linux内核中,使用usb_driver结构体描述一个USB设备驱动。
1 struct usb_driver { 2 const char *name; 3 4 int (*probe) (struct usb_interface *intf, 5 const struct usb_device_id *id); 6 7 void (*disconnect) (struct usb_interface *intf); 8 9 int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code, 10 void *buf); 11 12 int (*suspend) (struct usb_interface *intf, pm_message_t message); 13 int (*resume) (struct usb_interface *intf); 14 int (*reset_resume)(struct usb_interface *intf); 15 16 int (*pre_reset)(struct usb_interface *intf); 17 int (*post_reset)(struct usb_interface *intf); 18 19 const struct usb_device_id *id_table; 20 21 struct usb_dynids dynids; 22 struct usbdrv_wrap drvwrap; 23 unsigned int no_dynamic_id:1; 24 unsigned int supports_autosuspend:1; 25 unsigned int disable_hub_initiated_lpm:1; 26 unsigned int soft_unbind:1; 27 };
在编写新的USB设备驱动时,主要应该完成的工作是probe()和disconnect()函数,即探测和断开函数,它们分别在设备被插入和拔出的时候调用,用于初始化和释放软硬件资源。对usb_driver的注册和注销可通过下面两个函数完成:
函数原形 |
#define usb_register(driver) usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) void usb_deregister(struct usb_driver *driver); |
usb_driver结构体中的id_table成员描述了这个USB驱动所支持的USB设备列表,它指向了一个usb_device_id数组,usb_device_id结构体包含有USB设备的制造商ID、产品ID、产品版本、设备类、接口类等信息及其要匹配标志成员match_flags(标明要与哪些成员匹配,包含DEV_LO、DEV_HI、DEV_CLASS、DEV_SUBCLASS、DEV_PROTOCOL、INT_CLASS、INT_SUBCLASS、INT_PROTOCOL)。
可以借助下面一组宏来生成usb_device_id结构体的实例:
函数原形 |
USB_DEVICE(vendor, product); |
该宏根据制造商ID和产品ID生成一个usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支持与制造商ID、产品ID匹配的设备。
函数原形 |
USB_DEVICE_VER(vendor, product, lo, hi); |
该宏根据制造商ID、产品ID、产品版本的最小值和最大值生成一个usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支持与制造商ID、产品ID匹配和lo~hi范围内版本的设备。
函数原形 |
USB_DEVICE_INFO(class, subclass, protocol); |
该宏用于创建一个匹配设备指定类型的usb_device_id结构体实例。
函数原形 |
USB_INTERFACE_INFO(vendor, subclass, protocol); |
该宏用于创建一个匹配接口指定类型usb_device_id结构体实例。
下面提供usb_device_id结构体数组实例:
1 /* 本驱动支持的USB设备列表 */ 2 3 /* 实例1 */ 4 static struct usb_device_id id_table[] = { 5 { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, 6 { }, 7 }; 8 MODULE_DEVICE_TABLE(usb, id_table); 9 10 /* 实例2 */ 11 static struct usb_device_id id_table[] = { 12 { 13 .idVendor = 0x10D2, 14 .match_flags = USB_DEVICE_ID_MATCH_VENDOR, 15 }, 16 { }, 17 } 18 MODULE_DEVICE_TABLE(usb, id_table);
当USB核心检测到某个设备的属性和某个驱动程序的usb_device_id结构体所携带的信息一致时,这个驱动程序的probe()函数就会被执行(如果这个USB驱动是个模块的话,相关的.ko还应被Linux自动加载)。拔掉设备或者卸载驱动模块后,USB核心就执行disconnect()函数来响应这个动作。
上述的usb_driver结构体是USB设备驱动和USB相关的部分,但USB是一个总线,USB设备驱动的主体还是USB设备本身所属类型的驱动,如字符设备等。因此USB设备驱动包含作为总线上挂接设备的驱动和本身所属设备类型的驱动两部分。
尽管USB本身所属类型驱动和与其挂不挂在USB总线上没什么关系,但是据此在访问方式上却有很大的变化,例如,对于USB接口的字符设备而言,尽管仍然是write()、read()、ioctl()这些函数,但是在这些函数中,贯穿始终的是称为URB的USB请求块。