学习目标:分析USB驱动源码结构。
一、Windows下USB驱动理论问题
1. 当usb设备接入PC时,右下角弹出"发现AAA",并弹出对话框,提示安装驱动程序。没有驱动程序,Windows是怎样知道是AAA设备?
--> Windows有USB的总线驱动程序,接入USB设备后,"总线驱动程序"就会知道该设备是"AAA",提示安装的是”AAA的设备驱动程序"。这里USB总线驱动程序负责:识别USB设备, 给USB设备找到对应的驱动程序。
USB总线驱动程序的作用:(1) 识别USB设备 (2)查找并安装对应的设备驱动程序 (3)提供USB读写函数。
2. Windows是怎样识别出该USB设备的种类/名称?
--> PC和USB设备都得遵守一些规范。USB总线驱动程序会发出某些命令想获取设备信息(描述符),USB设备必须返回"描述符"给PC。
3. PC机是怎样分辨USB设备?
--> 每一个USB设备接入PC时,USB总线驱动程序都会给它分配一个编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址);新接入的USB设备的默认编号是0,在未分配新编号前,PC使用0编号和它通信。
4. 为什么一接入USB设备,PC机就能发现它?
--> 由于硬件接口连接问题提示的。USB接口只有4条线: 5V,GND,D-,D+。USB设备接入PC,会把PC USB口的D-或D+拉高,从而通知有新设备接入。
注意:
(1)USB是主从结构的,所有的传输都是由USB主机方发起的。例如,USB键盘按下立刻产生数据,但是它没有能力通知PC机来读数据,只能等待PC机来读。
(2)USB在数据传输中,以端点为对象的,其有传输类型,传输方向。每个端点只有一个方向的功能(除了端点0控制传输,既能输出也能输入外),例如把数据从端点1读出,向端点2写入数据。


usb 1-1: configuration #1 chosen from 1 choice
scsi0 : SCSI emulation for USB Mass Storage devices
scsi 0:0:0:0: Direct-Access HTC Android Phone 0100 PQ: 0 ANSI: 2
sd 0:0:0:0: [sda] Attached SCSI removable disk
拔掉后会打印:
usb 1-1: USB disconnect, address 2
drivers/usb/core/hub.c:2186: "%s %s speed %sUSB device using %s and address %d\n",
dev_info (&udev->dev,
"%s %s speed %sUSB device using %s and address %d\n",
(udev->config) ? "reset" : "new", speed, type,
udev->bus->controller->driver->name, udev->devnum);
2) 接下来在source insight中查看源码调用:
hub_thread-->
hub_events()-->
hub_port_connect_change()-->
调用 hub_port_init()
在hub_thread()函数中:可知hub_events() 的调用,需要等待中断khubd_wait被唤醒;
static int hub_thread(void *__unused)
{
do {
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 ;
}
3) 搜索“中断khubd_wait”,可以得到它是在drivers/usb/core/Hub.c --> kick_khubd()函数中:
static void kick_khubd(struct usb_hub *hub)
{
unsigned long flags;
/* Suppress autosuspend until khubd runs */
to_usb_interface(hub->intfdev)->pm_usage_cnt = ; spin_lock_irqsave(&hub_event_lock, flags);
if (list_empty(&hub->event_list)) {
list_add_tail(&hub->event_list, &hub_event_list);
wake_up(&khubd_wait);
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}
4) 继续搜索kick_khubd(),可知该函数被hub_irq()调用,因此当USB设备插入后,D+ 或者D-会被拉高,从而使USB控制器产生usb_irq中断。
5) 在hub_port_connect_change()实现了设备注册、端口连接、创建USB设备等功能。
static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange)
{
udev = usb_alloc_dev(hdev, hdev->bus, port1); //注册usb_device,放在usb总线上
device_initialize(&dev->dev); // 初始化usb_device
dev->dev.bus = &usb_bus_type; // 设置usb_device的成员device->bus等于usb_bus总线
dev->dev.type = &usb_device_type; // 设置usb_device的成员device->type等于usb_device_type
return dev; // 返回一个usb_device结构体
usb_set_device_state(udev, USB_STATE_POWERED); //设置注册的USB设备的状态标识
choose_address(udev); /*分配一个地址编号 */
/* usb 1-1: new full speed USB device using s3c2410-ohci and address 3 **初始化端口,与USB设备建立连接*/
hub_port_init(hub, udev, port1, i)
retval = hub_set_address(udev); //(1)设置地址,告诉USB设备新的地址编号
retval = usb_get_device_descriptor(udev, 8); //(2)获得USB设备描述符前8个字节 status = usb_new_device(udev); //创建USB设备,与USB驱动连接
err = usb_get_configuration(udev); // 把所有的描述符都读出来,并解析
-->usb_parse_configuration
-->device_add // 把device放入usb_bus_type的dev链表,
// 从usb_bus_type的driver链表里取出usb_driver,
// 把usb_interface和usb_driver的id_table比较
// 如果能匹配,调用usb_driver的probe
}
小结
1)USB驱动程序源码的执行流程为:
2)usb_bus_type是一个全局变量, 和我们之前学的platform平台总线原理、结构体、设备与驱动节点、函数调用等相似, 属于USB总线, 是Linux中bus的一种. 每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB设备驱动联系起来.
3)在分配一个地址编号时,USB接口最大能接127个设备,连续插拔两次USB键盘,也可以看出,如下图所示:
4)usb_get_device_descriptor()函数主要是获取目标设备描述符前8个字节,为什么先只开始读取8个字节?是因为开始时还不知道对方所支持的信包容量,这8个字节是每个设备都有的,后面再根据设备的数据,通过usb_get_device_descriptor()重读一次目标设备的设备描述结构.
5)那么USB驱动的id_table又该如何定义?
例如:参考/drivers/hid/usbhid/usbmouse.c (鼠标驱动)
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//(1)USB_INTERFACE_CLASS_HID 为设置匹配USB的接口类型为HID类, 因为USB_INTERFACE_CLASS_HID=0x03,HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0X03
//(2)USB_INTERFACE_SUBCLASS_BOOT 为设置匹配USB的接口子类型为启动设备
//(3)USB_INTERFACE_PROTOCOL_MOUSE 设置匹配USB的接口协议为USB鼠标的协议,等于2;当.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议
{ } /* Terminating entry */
}; MODULE_DEVICE_TABLE (usb, usb_mouse_id_table); static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
源码分析完后,就可以自己写USB设备驱动程序了。
参考:https://www.cnblogs.com/lifexy/p/7631900.html