《Linux驱动:USB设备驱动看这一篇就够了》
文章目录
- 一,前言
- 二,USB子系统
- 2.1 USB驱动基础概念
- 2.1.1 USB版本
- 2.1.2 USB主从结构
- 2.1.3 USB的传输类型
- 2.1.4 USB设备描述符
- 2.1.4.1 设备描述符
- 2.1.4.2 配置描述符
- 2.1.4.3 接口描述符
- 2.1.4.4 端点描述符
- 2.1.4.5 字符串描述符
- 2.1.4.6 人机接口描述符
- 2.1.4.6 USB描述符的类型值
- 2.1.5 USB的数据传输对象
- 2.2 USB子系统框架
- 三,USB总线驱动程序
- 3.1 USB Core
- 3.1.1 注册 USB 总线
- 3.1.2 注册USB接口驱动
- 3.1.3 初始化USB Hub
- 3.1.3.1 khubd_wait 的唤醒
- 3.1.4 注册USB设备驱动
- 3.1.5 usb_register 和 usb_register_device_driver
- 3.1.6 总结
- 3.2 USB主机控制器驱动(HCD)
- 3.2.1 USB主机控制器-设备
- 3.2.2 USB主机控制器-驱动
- 3.2.3 USB主机控制器设备和驱动的匹配
- 3.2.4 USB主机控制器驱动的probe函数
- 3.2.4.1 usb_device_match
- 3.2.4.2 USB设备驱动的probe函数
- 四,USB设备驱动 -- USB鼠标
- 4.1 注册一个USB接口驱动
- 4.2 USB接口设备的创建
- 4.3 USB接口驱动和USB接口设备的匹配
- 4.4 创建数据传输管道
- 4.5 分配urb
- 4.6 urb数据结构初始化
- 4.7 提交USB请求块
- 4.8 总结
- 五,实现一个USB设备驱动程序
一,前言
这一篇学习分析USB设备驱动程序,主要涉及到USB驱动基本概念,USB版本对比、USB主从结构、USB设备的传输类型、USB设备描述符关系、USB的数据传输对象;详细分析了USB总线驱动框架,USB Core、USB HCD、USB总线-设备-驱动模型;最后总结了USB驱动开发中的一般流程并模拟实现一个USB设备驱动程序。
二,USB子系统
2.1 USB驱动基础概念
2.1.1 USB版本
版本 | 理论最大速率 | 花名 |
---|---|---|
USB 1.0 | 1.5Mbps | 低速(Low-Speed) |
USB 1.1 | 12Mbps | 全速(Full-Speed) |
USB 2.0 | 480Mbps | 高速(High-Speed) |
USB 3.0 | 5Gbps | 超高速(Super-Speed) |
USB 3.1 | 10Gbps | 超高速+(Super-speed+) |
USB 4.0 | 40Gbps |
2.1.2 USB主从结构
USB分为USB 主机(USB Host)和USB device(USB设备)。比如USB鼠标插入到电脑,电脑就是USB Host,鼠标就是USB device。所有的USB传输,都是从USB主机这方发起的;USB设备没有"主动"通知USB主机的能力。
2.1.3 USB的传输类型
- 控制传输:控制传输是双向传输,数据量通常比较小,主要指由USB总线驱动程序用来进行查询、配置以及给USB设备发送通用的命令。控制传输典型地用在主计算机和USB外设之间的端点0(Endpoint 0)之间的传输,但是指定供应商的控制传输可能用到其它的端点。比如:USB设备的识别过程。
- 批量传输:主要应用在数据大量传输,同时又没有带宽和间隔时间要求的情况下,进行可靠传输。比如:U盘拷贝数据。
- 中断传输:中断传输主要用于定时查询设备是否有中断数据要传输,设备的端点模式器的结构决定了它的查询频率,从1到255ms之间。这种传输方式典型的应用在少量的、分散的、不可预测数据的传输,比如,键盘和鼠标就属于这一类型。中断传输是单向的并且对于host来说只有输入的方式。
- 实时传输:实时传输提供了确定的带宽和间隔时间,它被用于时间严格并具有较强容错性的流数据传输,或者用于要求恒定的数据传输率的即时应用中。比如:USB摄像头。
2.1.4 USB设备描述符
- 一个USB设备描述符中可以有多个配置描述符,即USB设备可以有多种配置;一个配置描述符中可以有多个接口描述符,即USB设备可以支持多种功能(接口);一个接口描述符中可以有多个端点描述符。
- 一设备至少要包含设备描述符、配置描述符和接口描述符,如果USB设备没有端点描述符,则它仅仅用默认管道与主机进行数据传输。
- 接口,表示逻辑上的设备,比如USB声卡可以分为接口1-录音设备,接口2-播放设备。
- 访问设备时,即访问某个接口,接口表示逻辑设备。
- 传输数据时,即读写某个端口,端口是数据通道。
2.1.4.1 设备描述符
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {__u8 bLength; //该结构体大小__u8 bDescriptorType; //描述符类型 (此处应为0x01,即设备描述符)__le16 bcdUSB; //usb版本号 200 -> USB2.0__u8 bDeviceClass; //设备类 __u8 bDeviceSubClass; //设备类子类__u8 bDeviceProtocol; //设备协议,以上三点都是USB官方定义__u8 bMaxPacketSize0; //端点0最大包大小 (只能为8,16,32,64)__le16 idVendor; //厂家id__le16 idProduct; //产品id__le16 bcdDevice; //设备出厂编号__u8 iManufacturer; //描述厂商信息的字符串描述符的索引值__u8 iProduct; //描述产品信息的字串描述符的索引值__u8 iSerialNumber; //描述设备序列号信息的字串描述符的索引值 __u8 bNumConfigurations; //可能的配置描述符的数目
} __attribute__ ((packed));
2.1.4.2 配置描述符
struct usb_config_descriptor {__u8 bLength; //该结构体大小__u8 bDescriptorType;//描述符类型(本结构体中固定为0x02) __le16 wTotalLength; //该配置下,信息的总长度(包括配置,接口,端点和设备类及厂商定义的描述符)__u8 bNumInterfaces; //接口的个数__u8 bConfigurationValue; //Set_Configuration命令所需要的参数值,用来选定此配置__u8 iConfiguration; //描述该配置的字符串描述的索引值 __u8 bmAttributes;//供电模式的选择 __u8 bMaxPower;//设备从总线提取的最大电流
} __attribute__ ((packed));
2.1.4.3 接口描述符
配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为“功能”更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。
struct usb_interface_descriptor {__u8 bLength; //该结构体大小__u8 bDescriptorType;//接口描述符的类型编号(0x04)__u8 bInterfaceNumber; //该接口的编号 __u8 bAlternateSetting; //备用的接口描述符编号 __u8 bNumEndpoints; //该接口使用的端点数,不包括端点0 __u8 bInterfaceClass; //接口类__u8 bInterfaceSubClass; //子类__u8 bInterfaceProtocol; //协议__u8 iInterface;//描述此接口的字串描述表的索引值
} __attribute__ ((packed));
2.1.4.4 端点描述符
端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。
除了描述符中描述的端点外,每个设备必须要有一个默认的控制型端点,地址为0,它的数据传输为双向,而且没有专门的描述符,只是在设备描述符中定义了它的最大包长度。主机通过此端点向设备发送命令,获得设备的各种描述符的信息,并通过它来配置设备。
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {__u8 bLength; //端点描述符字节数大小(7个字节)__u8 bDescriptorType;//端点描述符类型编号(0x05) __u8 bEndpointAddress; //此描述表所描述的端点的地址、方向 : // bit3~bit0:端点号,bit6~bit4:保留,// bit7:方向,如果是控制端点则忽略,0-输出端点(主机到设备)1-输入端点(设备到主机)__u8 bmAttributes; // 端点特性,bit1~bit0 表示传输类型,其他位保留// 00-控制传输 01-实时传输 10-批量传输 11-中断传输__le16 wMaxPacketSize; //端点收、发的最大包大小__u8 bInterval; // 中断传输模式中主机查询端点的时间间隔。// 对于实时传输的端点此域必需为1,表示周期为1ms。对于中断传输的端点此域值的范围为1ms到255ms/* NOTE: these two are _only_ in audio endpoints. *//* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */__u8 bRefresh;__u8 bSynchAddress;
} __attribute__ ((packed));
2.1.4.5 字符串描述符
struct usb_string_descriptor {__u8 bLength; // 此描述表的字节数(bString域的数值N+2)__u8 bDescriptorType; // 字串描述表类型(此处应为0x03)__le16 wData[1]; /* UTF-16LE encoded */
} __attribute__ ((packed));
2.1.4.6 人机接口描述符
USB 设备中有一大类就是 HID 设备,即 Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘等,主要用于人与计算机进行交互。 它是 USB 协议最早支持的一种设备类。 HID 设备可以作为低速、全速、高速设备用。由于 HID 设备要求用户输入能得到及时响应,故其传输方式通常采用中断方式。
在 USB 协议中, HID 设备的定义放置在接口描述符中, USB 的设备描述符和配置描述符中不包含 HID 设备的信息。因此,对于某些特定的 HID 设备,可以定义多个接口,只有其中一个接口为 HID 设备类即可。
2.1.4.6 USB描述符的类型值
类型 | 描述符 | 类型值 |
---|---|---|
标准描述符 | 设备描述符 | 0x01 |
配置描述符 | 0x02 | |
字符串描述符 | 0x03 | |
接口描述符 | 0x04 | |
端点描述符 | 0x05 | |
类描述符 | 集线器类描述符 | 0x29 |
人机接口类描述符 | 0x21 | |
厂商定义的描述符 | 0xff |
2.1.5 USB的数据传输对象
端点,一个USB设备可以有多个端点,和主机间的数据传输称为到设备端点的数据传输。比如说,对于一个U盘,可以细分为两个端点,把数据写到U盘的端点1、从U盘的端点2读取数据。
2.2 USB子系统框架
三,USB总线驱动程序
3.1 USB Core
初始化内核USB总线及提供USB相关API,为设备驱动和HCD的交互提供桥梁。
Linux启动阶段,通过subsys_initcall会完成USB Core的加载
subsys_initcall(usb_init);
3.1.1 注册 USB 总线
USB是基于总线-驱动-设备模型的框架,其初始化阶段一个重点任务就是完成USB总线的创建。usb_bus_type提供了驱动和设备匹配的匹配函数,后面注册设备和驱动时会调用到。
retval = bus_register(&usb_bus_type);
if (retval) goto bus_register_failed;struct bus_type usb_bus_type = {.name = "usb",.match = usb_device_match,.uevent = usb_uevent,.suspend = usb_suspend,.resume = usb_resume,
};
使用bus_register接口注册USB总线,会创建出两条链表用来分别存放向USB总线注册的设备和驱动。
klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&bus->klist_drivers, NULL, NULL);
3.1.2 注册USB接口驱动
一个设备可以有多个接口,每个接口对应着不同的功能。
// 在usb总线注册USB接口驱动,该驱动被放在usb总线的驱动链表中。
retval = usb_register(&usbfs_driver);
if (retval)goto driver_register_failed;struct usb_driver usbfs_driver = {.name = "usbfs",.probe = driver_probe,.disconnect = driver_disconnect,
};
3.1.3 初始化USB Hub
初始化一个USB设备集线器,用来检测USB设备的连接和断开。
// linux-2.6.22.6/drivers/usb/core/usb.c
retval = usb_hub_init();
if (retval)goto hub_init_failed;// linux-2.6.22.6/drivers/usb/core/hub.c
int usb_hub_init(void)
{// 在usb总线注册一个hub驱动,该驱动被放在usb总线的驱动链表中。if (usb_register(&hub_driver) < 0) {printk(KERN_ERR "%s: can't register hub driver\n",usbcore_name);return -1;}// 创建一个线程,用来处理USB设备的断开、连接等事件khubd_task = kthread_run(hub_thread, NULL, "khubd");if (!IS_ERR(khubd_task))return 0;/* Fall through if kernel_thread failed */usb_deregister(&hub_driver);printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);return -1;
}static struct usb_driver hub_driver = {.name = "hub",.probe = hub_probe,.disconnect = hub_disconnect,.suspend = hub_suspend,.resume = hub_resume,.pre_reset = hub_pre_reset,.post_reset = hub_post_reset,.ioctl = hub_ioctl,.id_table = hub_id_table,.supports_autosuspend = 1,
};static int hub_thread(void *__unused)
{do {/*hub_events()是一个死循环,其任务是解析hub_event_list,来一个一个处理发生在hub上的事件,比如插入,拔出。当hub_event_list事件被处理完后,break跳出while,通过wait_event_freezable使进程进入休眠态。一旦hub_event_list上有新事件需要处理,此处khubd_wait会在事件中断中被唤醒,重新执行到此处的hub_events()来遍历执行事件,完成处理。*/hub_events();wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());try_to_freeze();} while (!kthread_should_stop() || !list_empty(&hub_event_list));pr_debug("%s: khubd exiting\n", usbcore_name);return 0;
}
3.1.3.1 khubd_wait 的唤醒
hub_probe -> hub_configure ->usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,hub, endpoint->bInterval);// usb_fill_int_urb 接口创建了一个中断类型的 USB请求控制块hub_irq -> kick_khubd(hub) ->wake_up