这里所说的设备驱动是指从主机角度来看,怎么访问被插入的USB设备,而不是指USB设备内部本身运行的固件程序。USB 设备内的固件称为“设备用户固件” , “设备用户固件”完成设备内部的控制任务,并在 USB 传输中对接收到的设备请求做出解释并予以正确响应。
Linux 系统实现了几类通用的 USB 设备驱动,划分为如下几个设备类。
音频设备类。
通信设备类。
HID(人机接口)设备类。
显示设备类。
海量存储设备类。
电源设备类。
打印设备类。
集线器设备类。
一般的通用的 Linux 设备(如 U 盘、USB 鼠标、USB 键盘等)都不需要工程师再编写驱动,需要编写的是特定厂商、特定芯片的驱动,而且往往也可以参考内核中已提供的驱动的模板。
Linux 内核为各类 USB 设备分配了相应的设备号,如 ACM USB 调制解调器的主设备号为 166(默认设备名/dev/ttyACMn) 、USB 打印机的主设备号为 180,次设备号为 0~15(默认设备名/dev/lpn) 、USB 串口的主设备号为 188(默认设备名/dev/ttyUSBn)等。内核中提供了 USB 设备文件系统(usbdevfs,Linux 2.6 改为 usbfs,即 USB 文件系统) ,它和/proc 类似,都是动态产生的。通过在/etc/fstab 文件中添加如下一行:
none /proc/bus/usb usbfs defaults
或者输入命令:
mount -t usbfs none /proc/bus/usb
可以实现 USB 设备文件系统的挂载。
正如 tty_driver、pci_driver 等,在 Linux 内核中,使用 usb_driver 结构体描述一个USB 设备驱动
1 struct usb_driver {
2 const char *name; /* 驱动名称 */
4 int (*probe) (struct usb_interface *intf,
5 const struct usb_device_id *id); /*探测函数*/
7 void (*disconnect) (struct usb_interface *intf); /*断开函数*/
9 int (*ioctl) (struct usb_interface *intf, unsigned int code,
10 void *buf); /* I/O 控制函数 */
12 int (*suspend) (struct usb_interface *intf, pm_message_t
message);/*挂起函数*/
13 int (*resume) (struct usb_interface *intf); /* 恢复函数 */
15 void (*pre_reset) (struct usb_interface *intf);
16 void (*post_reset) (struct usb_interface *intf);
18 const struct usb_device_id *id_table;/* usb_device_id 表指针 */
20 struct usb_dynids dynids;
21 struct usbdrv_wrap drvwrap;
22 unsigned int no_dynamic_id:1;
23 unsigned int supports_autosuspend:1;
24 };
在编写新的 USB 设备驱动时, 主要应该完成的工作是 probe()和 disconnect()函数,即探测和断开函数,它们分别在设备被插入和拔出的时候被调用,用于初始化和释放软硬件资源。对 usb_driver 的注册和注销通过这两个函数完成:
int usb_register(struct usb_driver *new_driver)
void usb_deregister(struct usb_driver *driver);
usb_driver 结构体中的 id_table成员描述了这个 USB驱动所支持的 USB设备列表,
它指向一个 usb_device_id 数组, usb_device_id 结构体用于包含 USB 设备的制造商 ID、
产品 ID、产品版本、设备类、接口类等信息及其要匹配标志成员 match-Flash(标明
要与哪些成员匹配) 。可以借助下面一组宏来生成 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(class, 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 { .idVendor = 0x10D2, .match_flags = USB_DEVICE_ID_MATCH_VENDOR, },
13 { },
14 };
15 MODULE_DEVICE_TABLE (usb, id_table);
上述 usb_driver 结构体中的函数是 USB 设备驱动中 USB 相关的部分,而 USB 只是一个总线, 真正的 USB 设备驱动的主体工作仍然是 USB 设备本身所属类型的驱动,如字符设备、tty 设备、块设备、输入设备等。因此 USB 设备驱动包含其作为总线上挂在设备的驱动和本身所属设备类型的驱动两部分。与 platform_driver 类似,usb_driver 起到了“牵线”的作用,即在 probe()里注册相应的字符、tty 等设备,在 disconnect()注销相应的字符、tty 等设备,而原先对设备的注册和注销一般直接发生在模块加载和卸载函数中。尽管 USB 本身所属设备驱动的结构与其不挂在 USB 总线上时完全相同,但是在访问方式上却发生了很大的变化, 例如, 对于字符设备而言, 尽管仍然是 write()、 read()、ioctl()这些函数,但是在这些函数中,与 USB 设备通信时不再是 I/O 内存和 I/O 端口的访问,而贯穿始终的是称为 URB 的 USB 请求块。
USB 请求块(URB)
1.urb 结构体
USB 请求块(USB request block,urb)是 USB 设备驱动中用来描述与 USB 设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的 sk_buff 结构体,是 USB 主机与设备通信的“电波” 。
urb 结构体
1 struct urb
2 {
3 /* 私有的:只能由 USB 核心和主机控制器访问的字段 */
4 struct kref kref; /*urb 引用计数 */
5 spinlock_t lock; /* urb 锁 */
6 void *hcpriv; /* 主机控制器私有数据 */
7 int bandwidth; /* INT/ISO 请求的带宽 */
8 atomic_t use_count; /* 并发传输计数 */
9 u8 reject; /* 传输将失败*/
10
11 /* 公共的: 可以被驱动使用的字段 */
12 struct list_head urb_list; /* 链表头*/
13 struct usb_device *dev; /* 关联的 USB 设备 */
14 unsigned int pipe; /* 管道信息 */
15 int status; /* URB 的当前状态 */
16 unsigned int transfer_flags; /* URB_SHORT_NOT_OK | ...*/
17 void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */
18 dma_addr_t transfer_dma; /*用来以 DMA 方式向设备传输数据的缓冲区 */
19 int transfer_buffer_length;/*transfer_buffer 或 transfer_dma 指向
缓冲区的大小 */
20
21 int actual_length; /* URB 结束后,发送或接收数据的实际长度 */
22 unsigned char *setup_packet; /* 指向控制 URB 的设置数据包的指针*/
23 dma_addr_t setup_dma; /*控制 URB 的设置数据包的 DMA 缓冲区*/
24 int start_frame; /*等时传输中用于设置或返回初始帧*/
25 int number_of_packets; /*等时传输中等时缓冲区数据 */
26 int interval; /* URB 被轮询到的时间间隔(对中断和等时 urb 有效) */
27 int error_count; /* 等时传输错误数量 */
28 void *context; /* completion 函数上下文 */
29 usb_complete_t complete; /* 当 URB 被完全传输或发生错误时,被调用 */
30 struct usb_iso_packet_descriptor iso_frame_desc[0];
31 /*单个 URB 一次可定义多个等时传输时,描述各个等时传输 */
32 };
当 transfer_flags 标志中的 URB_NO_TRANSFER_DMA_MAP 被置位时,USB 核心将使用 transfer_dma 指向的缓冲区而非 transfer_buffer 指向的缓冲区, 意味着即将传输 DMA 缓冲区。
当 transfer_flags 标志中的 URB_NO_SETUP_DMA_MAP 被置位时,对于有 DMA缓冲区的控制 urb 而言,USB 核心将使用 setup_dma 指向的缓冲区而非 setup_packet指向的缓冲区。
2.urb 处理流程
USB 设备中的每个端点都处理一个 urb 队列,在队列被清空之前,一个 urb 的典
型生命周期如下:
(1)被一个 USB 设备驱动创建。
创建 urb 结构体的函数为:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
iso_packets 是这个 urb 应当包含的等时数据包的数目,若为 0 表示不创建等时数
据包。mem_flags 参数是分配内存的标志,和 kmalloc()函数的分配标志参数含义相同。
如果分配成功,该函数返回一个 urb 结构体指针,否则返回 0。
urb 结构体在驱动中不能静态创建,因为这可能破坏 USB 核心给 urb 使用的引用
计数方法。
usb_alloc_urb()的“反函数”为:
void usb_free_urb(struct urb *urb);
该函数用于释放由 usb_alloc_urb()分配的 urb 结构体。
(2)初始化,被安排给一个特定 USB 设备的特定端点。
对于中断 urb,使用 usb_fill_int_urb()函数来初始化 urb,如下所示:
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, void *transfer_buffer,
int buffer_length, usb_complete_t complete,
void *context, int interval);
urb 参数指向要被初始化的 urb 的指针; dev 指向这个 urb 要被发送到的 USB 设备;pipe 是这个 urb 要被发送到的 USB 设备的特定端点;transfer_buffer 是指向发送数据或接收数据的缓冲区的指针,和 urb 一样, 它也不能是静态缓冲区, 必须使用 kmalloc()来分配;buffer_length 是 transfer_buffer 指针所指向缓冲区的大小;complete 指针指向当这个 urb 完成时被调用的完成处理函数;context 是完成处理函数的“上下文” ;interval 是这个 urb 应当被调度的间隔。上述函数参数中的 pipe 使用 usb_sndintpipe()或 usb_rcvintpipe()创建。对于批量 urb,使用 usb_fill_bulk_urb()函数来初始化 urb,如下所示:
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, void *transfer_buffer,
int buffer_length, usb_complete_t complete,
void *context);
除了没有对应于调度间隔的 interval 参数以外,该函数的参数和 usb_fill_int_urb()函数的参数含义相同。上述函数参数中的 pipe 使用 usb_sndbulkpipe()或者 usb_rcvbulkpipe()函数来创建。对于控制 urb,使usb_fill_control_urb()函数来初始化 urb,如下所示:
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, unsigned char *setup_packet,
void *transfer_buffer, int buffer_length,
usb_complete_t complete, void *context);
除了增加了新的 setup_packet 参数以外,该函数的参数和 usb_fill_bulk_urb()函数的参数含义相同。setup_packet 参数指向即将被发送到端点的设置数据包。上述函数参数中的 pipe 使用 usb_sndctrlpipe()或 usb_rcvictrlpipe()函数来创建。等时 urb 没有像中断、控制和批量 urb 的初始化函数,我们只能手动地初始化 urb,而后才能提交给 USB 核心。下面给出了初始化等时 urb 的例子,它来自drivers/usb/media/usbvideo.c 文件。
1 for (i = 0; i < USBVIDEO_NUMSBUF; i++)
2 {
3 int j, k;
4 struct urb *urb = uvd->sbuf[i].urb;
5 urb->dev = dev;
6 urb->context = uvd;
7 urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp);/*端口*/
8 urb->interval = 1;
9 urb->transfer_flags = URB_ISO_ASAP; /*urb 被调度*/
10 urb->transfer_buffer = uvd->sbuf[i].data;/*传输 buffer*/
11 urb->complete = usbvideo_IsocIrq; /* 完成函数 */
12 urb->number_of_packets = FRAMES_PER_DESC; /*urb 中的等时传输数量*/
13 urb->transfer_buffer_length = uvd->iso_packet_len
*FRAMES_PER_DESC;
14 for (j = k = 0; j < FRAMES_PER_DESC; j++, k += uvd->iso_packet_len)
15 {
16 urb->iso_frame_desc[j].offset = k;
17 urb->iso_frame_desc[j].length = uvd->iso_packet_len;
18 }
19 }
(3)被 USB 设备驱动提交给 USB 核心。
在完成第(1) 、 (2)步的创建和初始化 urb 后,urb 便可以提交给 USB 核心,通过
usb_submit_urb()函数来完成,如下所示:
int usb_submit_urb(struct urb *urb, int mem_flags);
urb 参数是指向 urb 的指针, mem_flags 参数与传递给 kmalloc()函数参数的意义相同,它用于告知 USB 核心如何在此时分配内存缓冲区。在提交 urb 到 USB 核心后,直到完成函数被调用之前,不要访问 urb 中的任何成员。usb_submit_urb()在原子上下文和进程上下文中都可以被调用,mem_flags 变量需根据调用环境进行相应的设置,如下所示。
GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及 urb完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将 current->state修改为非 TASK_ RUNNING 时,应使用此标志。 GFP_NOIO:在存储设备的块 I/O 和错误处理路径中,应使用此标志;GFP_KERNEL:如果没有任何理由使用 GFP_ATOMIC 和 GFP_NOIO,就使用 GFP_ KERNEL。如果 usb_submit_urb()调用成功,即 urb 的控制权被移交给 USB 核心,该函数返回 0;否则,返回错误号。
(4)提交由 USB 核心指定的 USB 主机控制器驱动。
(5)被 USB 主机控制器处理,进行一次到 USB 设备的传送。第(4)~(5)步由 USB 核心和主机控制器完成,不受 USB 设备驱动的控制。
(6)当 urb 完成,USB 主机控制器驱动通知 USB 设备驱动。在如下 3 种情况下,urb 将结束,urb 完成函数将被调用。l urb 被成功发送给设备,并且设备返回正确的确认。如果 urb->status 为 0,意味着对于一个输出 urb,数据被成功发送;对于一个输入 urb,请求的数据被成功收到。如果发送数据到设备或从设备接收数据时发生了错误,urb->status 将记录错误值。urb 被从 USB 核心“去除连接” ,这发生在驱动通过 usb_unlink_urb()或usb_kill_urb()函数取消 urb,或 urb 虽已提交,而 USB 设备被拔出的情况下。usb_unlink_urb()和 usb_kill_urb()这两个函数用于取消已提交的 urb,其参数为要被 取 消 的 urb 指 针 。 对 usb_unlink_urb() 而 言 , 如 果 urb 结 构 体 中 的URB_ASYNC_UNLINK (即异步 unlink) 的标志被置位, 则对该 urb 的 usb_unlink_urb()调用将立即返回,具体的 unlink 动作将在后台进行。否则,此函数一直等到 urb 被解开链接或结束时才返回。usb_kill_urb()会彻底终止 urb 的生命周期,它通常在设备的disconnect()函数中被调用。
当 urb 生命结束时(处理完成或被解除链接) ,通过 urb 结构体的 status 成员可以获知其原因,如 0 表示传输成功, -ENOENT 表示被 usb_kill_urb()杀死, -ECONNRESET表示被 usb_unlink_urb()杀死,-EPROTO 表示传输中发生了 bitstuff 错误或者硬件未能及时收到响应数据包,-ENODEV 表示 USB 设备已被移除,-EXDEV 表示等时传输仅完成了一部分等。