Linux:主机USB设备驱动简析
文章目录
- 1. 前言
- 2. 分析背景
- 3. USB 总线硬件拓扑
- 4. USB 协议栈概览
-
- 4.1 Linux USB 子系统概览
- 4.2 USB外设(如U盘)固件基础
- 5. Linux USB 子系统初始化
- 6. Linux USB 主机控制器(HCD) 驱动
-
- 6.1 USB 主机控制器驱动初始化
- 6.2 USB 主机控制器设备对象注册和驱动加载
- 7. Linux USB 设备驱动加载过程
-
- 7.1 `HUB 类设备` 驱动加载过程
- 7.2 `非 HUB 类设备` 驱动加载过程
-
- 7.2.1 按 VID & PID 匹配驱动
- 7.2.2 按 interface class 匹配驱动
- 7.2.2 其它情形
- 8. 参考资料
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 分析背景
本文基于 linux-4.14.132
内核代码进行分析。
3. USB 总线硬件拓扑
其中:
1. Host 就是 USB 主机控制器(HCD),就是常说的 OHCI,UHCI,EHCI,xHCI , 内核用数据结构 struct usb_hcd 来描述。
2. RootHub 是集成到 USB 主机控制器(HCD) Host 的根集线器,根集线器上的端口用于连接USB设备:Hub或Func。
3. Func 表示USB设备,如U盘等。
一个 USB 主机控制器(HCD) 上最多可连接 127 个设备(HUB或Func),每个设备在枚举完成后会分配1个地址,地址区间为1~127,特殊地址0用于在设备枚举期间和主机控制器通信。
4. USB 协议栈概览
4.1 Linux USB 子系统概览
USB 主机(如带 Linux 系统的设备) USB Slave 设备---------------------------------------- ------------------------------
| USB设备驱动 | | |
| ^ | | |
| | | | |
| v | | |
| USB Core | | |
| ^ | | |
| | | | 如U盘、USB鼠标键盘 驱动固件 |
| v | | |
| USB主机控制器驱动(OHCI/UHCI/EHCI/xHCI) | | |
| ^ | | |
| | | | |
| v | | |
| USB主机控制器 | | |---------------------------------------- ------------------------------^ ^| || USB 总线 |-------------------------------------------------------
4.2 USB外设(如U盘)固件基础
一个 USB 外设,它包含一系列的描述符,这些描述符用于定义 USB 外设的功能特性和行为逻辑。描述符包含以下类型:
设备描述符:每个设备有且仅有1个设备描述符。它定义设备的 PID & VID,包含的配置描述符的个数信息。
配置描述符:设备支持的接口数等信息。
接口描述符:接口包含的端点数,支持的协议类别、子类别,协议类型等信息。
端点描述符:端点是USB通信的基本单位。端点描述符包含端点类别、缓冲大小、地址等信息。
......
由于 USB 协议栈的内容过于庞大,也不是本文的重点,本篇将不做展开,感兴趣的童鞋可以参考 USB 官网 相关资料。
5. Linux USB 子系统初始化
/* drivers/usb/core/usb.c */static int __init usb_init(void)
{int retval;.../ USB 调试系统初始化:* . 创建目录 /sys/kernel/debug/usb * . 创建文件 /sys/kernel/debug/usb/devices*/retval = usb_debugfs_init();if (retval)goto out;...retval = bus_register(&usb_bus_type); /* 注册 USB 总线类型 */if (retval)goto bus_register_failed;/* * USB 总线 notifier 注册:* 在 usb device 或 interface 注册、注销时,增加、移除相关的 sysfs 目录树。*/retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);if (retval)goto bus_notifier_failed;...retval = usb_hub_init(); /* 注册 USB HUB 驱动 */if (retval)goto hub_init_failed;/* 注册 USB device 通用驱动 */retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);if (!retval)goto out;...
bus_notifier_failed:bus_unregister(&usb_bus_type);
bus_register_failed:...usb_debugfs_cleanup()
out:return retval;
}subsys_initcall(usb_init);
/* drivers/usb/core/hub.c */static const struct usb_device_id hub_id_table[] = {{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR| USB_DEVICE_ID_MATCH_INT_CLASS,.idVendor = USB_VENDOR_GENESYS_LOGIC,.bInterfaceClass = USB_CLASS_HUB,.driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},{ .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,.bDeviceClass = USB_CLASS_HUB},{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,.bInterfaceClass = USB_CLASS_HUB},{ } /* Terminating entry */
};MODULE_DEVICE_TABLE(usb, hub_id_table);static struct usb_driver hub_driver = { /* USB HUB 驱动 */.name = "hub",.probe = hub_probe,.disconnect = hub_disconnect,.suspend = hub_suspend,.resume = hub_resume,.reset_resume = hub_reset_resume,.pre_reset = hub_pre_reset,.post_reset = hub_post_reset,.unlocked_ioctl = hub_ioctl,.id_table = hub_id_table,.supports_autosuspend = 1,
};int usb_hub_init(void)
{/* 注册 USB HUB 驱动 */if (usb_register(&hub_driver) < 0) {printk(KERN_ERR "%s: can't register hub driver\\n",usbcore_name);return -1;}/ The workqueue needs to be freezable to avoid interfering with* USB-PERSIST port handover. Otherwise it might see that a full-speed* device was gone before the EHCI controller had handed its port* over to the companion full-speed controller.*/hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);if (hub_wq)return 0;...
}
USB 设备通用驱动 usb_generic_driver
注册:
/* drivers/usb/core/driver.c */int usb_register_device_driver(struct usb_device_driver *new_udriver,struct module *owner)
{int retval = 0;new_udriver->drvwrap.for_devices = 1; /* 标记为 device 级别的驱动 */new_udriver->drvwrap.driver.name = new_udriver->name;new_udriver->drvwrap.driver.bus = &usb_bus_type;new_udriver->drvwrap.driver.probe = usb_probe_device;new_udriver->drvwrap.driver.remove = usb_unbind_device;new_udriver->drvwrap.driver.owner = owner;retval = driver_register(&new_udriver->drvwrap.driver);...return retval;
}
/* include/linux/usb.h *//* use a define to avoid include chaining to get THIS_MODULE & friends */
#define usb_register(driver) \\usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)/* drivers/usb/core/driver.c */
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,const char *mod_name)
{int retval = 0;...new_driver->drvwrap.for_devices = 0; /* 标记为 interface 级别驱动 */new_driver->drvwrap.driver.name = new_driver->name;new_driver->drvwrap.driver.bus = &usb_bus_type;new_driver->drvwrap.driver.probe = usb_probe_interface;new_driver->drvwrap.driver.remove = usb_unbind_interface;new_driver->drvwrap.driver.owner = owner;new_driver->drvwrap.driver.mod_name = mod_name;spin_lock_init(&new_driver->dynids.lock);INIT_LIST_HEAD(&new_driver->dynids.list);retval = driver_register(&new_driver->drvwrap.driver);...return retval;
}
上面的代码主要完成了以下3项工作:
1. USB 总线类型注册
2. USB HUB 驱动注册
3. USB 设备通用驱动注册
6. Linux USB 主机控制器(HCD) 驱动
我们以支持 USB 2.0 的 EHCI USB 主机控制器
驱动为例进行阐述,而其它 OHCI,UHCI,xHCI
的主机控制器驱动,读者可自行阅读相关代码进行分析。
6.1 USB 主机控制器驱动初始化
static int __init ehci_platform_init(void)
{/* ehci-platform: EHCI generic platform driver */pr_info("%s: " DRIVER_DESC "\\n", hcd_name);/* 初始化 EHCI 主机控制器驱动 */ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides);/* 注册 EHCI 主机控制器 platform 驱动 */return platform_driver_register(&ehci_platform_driver);
}
/* drivers/usb/host/ehci-hcd.c *//* EHCI 主机控制器驱动 */
static const struct hc_driver ehci_hc_driver = {.description = hcd_name,.product_desc = "EHCI Host Controller",.../ generic hardware linkage*/.irq = ehci_irq,.flags = HCD_MEMORY | HCD_USB2 | HCD_BH,.hcd_priv_size = sizeof(struct ehci_hcd),/ basic lifecycle operations*/.reset = ehci_setup,.start = ehci_run,.stop = ehci_stop,.shutdown = ehci_shutdown,/ managing i/o requests and associated device resources*/.urb_enqueue = ehci_urb_enqueue,.urb_dequeue = ehci_urb_dequeue,.endpoint_disable = ehci_endpoint_disable,.endpoint_reset = ehci_endpoint_reset,.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,/ scheduling support*/.get_frame_number = ehci_get_frame,/ root hub support*/.hub_status_data = ehci_hub_status_data,.hub_control = ehci_hub_control,.bus_suspend = ehci_bus_suspend,.bus_resume = ehci_bus_resume,.relinquish_port = ehci_relinquish_port,.port_handed_over = ehci_port_handed_over,/ device support*/.free_dev = ehci_remove_device,
};void ehci_init_driver(struct hc_driver *drv,const struct ehci_driver_overrides *over)
{/* Copy the generic table to drv and then apply the overrides */*drv = ehci_hc_driver;if (over) {drv->hcd_priv_size += over->extra_priv_size;if (over->reset)drv->reset = over->reset;if (over->port_power)drv->port_power = over->port_power;}
}
USB 主机控制器(HCD)驱动
用数据结构 struct hc_driver
抽象。
6.2 USB 主机控制器设备对象注册和驱动加载
看一下全志 H3 平台 EHCI
的 DTS 配置:
ehci0: usb@01c1a000 {compatible = "allwinner,sun8i-h3-ehci", "generic-ehci";reg = <0x01c1a000 0x100>;interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;clocks = <&ccu CLK_BUS_EHCI0>, <&ccu CLK_BUS_OHCI0>;resets = <&ccu RST_BUS_EHCI0>, <&ccu RST_BUS_OHCI0>;status = "okay";
};
在系统启动期间,会为该 EHCI 主机控制器
创建一个 platform_device
对象,然后在注册 EHCI 主机控制器的 platform_driver
驱动时,触发 EHCI 主机控制器
platform 设备驱动的加载:
/* drivers/usb/host/echi-platform.c */static const struct of_device_id vt8500_ehci_ids[] = {...{ .compatible = "generic-ehci", },...{}
};
MODULE_DEVICE_TABLE(of, vt8500_ehci_ids);static struct platform_driver ehci_platform_driver = {.id_table = ehci_platform_table,.probe = ehci_platform_probe,.remove = ehci_platform_remove,.shutdown = usb_hcd_platform_shutdown,.driver = {.name = "ehci-platform",.pm = &ehci_platform_pm_ops,.of_match_table = vt8500_ehci_ids,.acpi_match_table = ACPI_PTR(ehci_acpi_match),}
};/* 加载 ECHI 的 platform_driver 驱动 */
static int ehci_platform_probe(struct platform_device *dev)
{struct usb_hcd *hcd;...int err, irq, phy_num, clk = 0, rst;.../* * 解析中断配置: * interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;*/irq = platform_get_irq(dev, 0);.../* 创建 USB ECHI 主机控制器(HCD)对象 */hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev,dev_name(&dev->dev));....../* 注册 USB ECHI 主机控制器对象 */err = usb_add_hcd(hcd, irq, IRQF_SHARED);...platform_set_drvdata(dev, hcd);return err;
}
/* drivers/usb/core/hcd.c */struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,struct device *dev, const char *bus_name)
{return __usb_create_hcd(driver, dev, dev, bus_name, NULL);
}struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,struct device *sysdev, struct device *dev, const char *bus_name,struct usb_hcd *primary_hcd)
{struct usb_hcd *hcd;hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);......hcd->self.controller = dev; /* EHCI 的 platform_device */...hcd->self.bus_name = bus_name;init_timer(&hcd->rh_timer);hcd->rh_timer.function = rh_timer_func;hcd->rh_timer.data = (unsigned long) hcd;
#ifdef CONFIG_PMINIT_WORK(&hcd->wakeup_work, hcd_resume_work);
#endifhcd->driver = driver; /* 设定 EHCI 主机控制器的驱动: ehci_platform_hc_driver */hcd->speed = driver->flags & HCD_MASK;hcd->product_desc = (driver->product_desc) ? driver->product_desc :"USB Host Controller";return hcd;
}int usb_add_hcd(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags)
{int retval;struct usb_device *rhdev;.../* ehci-platform 1c1a000.usb: EHCI Host Controller */dev_info(hcd->self.controller, "%s\\n", hcd->product_desc/*ehci_hc_driver.product_desc*/);...retval = hcd_buffer_create(hcd);...retval = usb_register_bus(&hcd->self);....../* 创建 ECHI 主机控制器 ROOT HUB 设备对象 */rhdev = usb_alloc_dev(NULL, &hcd->self, 0);...hcd->self.root_hub = rhdev;...switch (hcd->speed) {...case HCD_USB2:rhdev->speed = USB_SPEED_HIGH;break;...}...if (hcd->driver->reset) {/* 配置 EHCI 主机控制器 */retval = hcd->driver->reset(hcd); /* ehci_setup() */...}/* initialize tasklets */init_giveback_urb_bh(&hcd->high_prio_bh);init_giveback_urb_bh(&hcd->low_prio_bh);...if (usb_hcd_is_primary_hcd(hcd) && irqnum) {/* 注册 ECHI 主机控制器中断处理接口 ehci_irq() */retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);...}hcd->state = HC_STATE_RUNNING;/* 启动 EHCI 主机控制器 */retval = hcd->driver->start(hcd); /* ehci_run() *//* starting here, usbcore will pay attention to this root hub *//* ECHI 主机控制器 ROOT HUB 设备对象注册 和 驱动加载 */retval = register_root_hub(hcd);...return retval;
}static int usb_register_bus(struct usb_bus *bus)
{...busnum = idr_alloc(&usb_bus_idr, bus, 1, USB_MAXBUS, GFP_KERNEL);bus->busnum = busnum;...usb_notify_add_bus(bus);/* ehci-platform 1c1a000.usb: new USB bus registered, assigned bus number 1 */dev_info (bus->controller, "new USB bus registered, assigned bus ""number %d\\n", bus->busnum);return 0;
}/* 创建 USB 设备对象 (usb_device) */
struct usb_device *usb_alloc_dev(struct usb_device *parent,struct usb_bus *bus, unsigned port1) // drivers/usb/core/usb.c
{struct usb_device *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL);...device_initialize(&dev->dev);dev->dev.bus = &usb_bus_type;dev->dev.type = &usb_device_type;dev->dev.groups = usb_device_groups;...dev->state = USB_STATE_ATTACHED;...dev->portnum = port1;dev->bus = bus;dev->parent = parent;...return dev;
}static int usb_hcd_request_irqs(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags)
{if (hcd->driver->irq) {...retval = request_irq(irqnum, &usb_hcd_irq, irqflags,hcd->irq_descr, hcd);...hcd->irq = irqnum;...} else {...}return 0;
}
来看 EHCI
主机控制器 ROOT HUB
设备对象注册和驱动加载的细节:
/* drivers/usb/core/hcd.c */static int register_root_hub(struct usb_hcd *hcd)
{struct device *parent_dev = hcd->self.controller;struct usb_device *usb_dev = hcd->self.root_hub;const int devnum = 1;int retval;usb_dev->devnum = devnum;usb_dev->bus->devnum_next = devnum + 1;...usb_set_device_state(usb_dev, USB_STATE_ADDRESS);usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);...retval = usb_new_device (usb_dev);...return retval;
}
/* drivers/usb/core/hub.c */int usb_new_device(struct usb_device *udev)
{...err = usb_enumerate_device(udev); /* Read descriptors */.../* Tell the world! *// usb usb1: New USB device found, idVendor=1d6b, idProduct=0002* usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1* usb usb1: Product: EHCI Host Controller* usb usb1: Manufacturer: Linux 4.14.111 ehci_hcd* usb usb1: SerialNumber: 1c1a000.usb*/announce_device(udev);/* * 注册 ECHI 控制器的 ROOT HUB 设备到 driver core ,* 这将触发 HUB 驱动 hub_driver 加载: 即触发 hub_probe() .*/err = device_add(&udev->dev);...(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);...return err;
}/* ECHI 主机控制器的 ROOT HUB 驱动加载 */
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{struct usb_hub *hub;.../* We found a hub */dev_info(&intf->dev, "USB hub found\\n"); /* hub 1-0:1.0: USB hub found *//* 创建 USB HUB 对象 */hub = kzalloc(sizeof(*hub), GFP_KERNEL);......hub->intfdev = &intf->dev;hub->hdev = hdev;INIT_DELAYED_WORK(&hub->leds, led_work);INIT_DELAYED_WORK(&hub->init_work, NULL);INIT_WORK(&hub->events, hub_event); /* 设置用来处理 HUB 上 USB 设备枚举过程的 work */......if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) /* 配置 HUB */return 0;...
}static int hub_configure(struct usb_hub *hub,struct usb_endpoint_descriptor *endpoint)
{hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL);...hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL);...hub->descriptor = kzalloc(sizeof(*hub->descriptor), GFP_KERNEL);.../* * 读取 HUB 设备 @hdev 的 【HUB 描述符】 到 @hub->descriptor , * 搞清楚 HUB 上有几个 USB 连接端口。*/ ret = get_hub_descriptor(hdev, hub->descriptor);...maxchild = hub->descriptor->bNbrPorts;/* * 如: hub 1-0:1.0: 1 port detected* 这日志表明,hub 1-0:1.0 上,物理上只有1个接口(1 port) hub 1-0:1.0: 6 ports detected* hub 2-0:1.0: 2 ports detected* hub 2-2:1.0: 7 ports detected*/dev_info(hub_dev, "%d port%s detected\\n", maxchild,(maxchild == 1) ? "" : "s");/* 为 HUB 上的 USB 端口创建对象 */hub->ports = kzalloc(maxchild * sizeof(struct usb_port *), GFP_KERNEL);......hub->urb = usb_alloc_urb(0, GFP_KERNEL); /* 分配用于 HUB 中断处理的 URB 对象 *//* 初始化用于 HUB 中断处理的 URB 对象: 中断 URB complete 处理回调设为 hub_irq() */usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,hub, endpoint->bInterval);...for (i = 0; i < maxchild; i++) { /* 为 HUB 上的 usb 端口创建设备对象,并注册到 driver core */ret = usb_hub_create_port_device(hub, i + 1);...}hdev->maxchild = i;...hub_activate(hub, HUB_INIT);return 0;
}static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{.../* Scan all ports that need attention */kick_hub_wq(hub);...
}static void kick_hub_wq(struct usb_hub *hub)
{if (hub->disconnected || work_pending(&hub->events))return;...if (queue_work(hub_wq, &hub->events)) /* 触发 hub_event() */return;...
}
从上面的代码看到,USB 主机控制器
用数据结构 struct usb_hcd
描述;USB HUB 设备
用数据结构 struct usb_hub
描述;USB HUB 上的端口
用数据结构 struct usb_port
描述;USB 端口上挂接的设备
用数据结构 struct usb_device
描述。
7. Linux USB 设备驱动加载过程
当设备插入到 USB 端口时,会产生中断信号,然后内核调用 HUB 的中断处理接口 hub_irq()
来处理中断信号:
/* drivers/usb/core/hub.c *//* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
{hub->nerrors = 0;/* Something happened, let hub_wq figure it out */kick_hub_wq(hub); /* 调度 work: 触发 hub_event() 进一步处理设备插入事件 */...
}static void hub_event(struct work_struct *work)
{struct usb_device *hdev;struct usb_interface *intf;struct usb_hub *hub;struct device *hub_dev;u16 hubstatus;u16 hubchange;int i, ret;hub = container_of(work, struct usb_hub, events);hdev = hub->hdev;hub_dev = hub->intfdev;intf = to_usb_interface(hub_dev);.../* deal with port status changes */for (i = 1; i <= hdev->maxchild; i++) {struct usb_port *port_dev = hub->ports[i - 1]; /* HUB 的第 @i 个端口 */if (test_bit(i, hub->event_bits)|| test_bit(i, hub->change_bits)/* 端口上发生状态变化: 设备插入、拔出 */|| test_bit(i, hub->wakeup_bits)) {...port_event(hub, i); /* 处理端口上的事件 */...}}...
}static void port_event(struct usb_hub *hub, int port1)__must_hold(&port_dev->status_lock)
{int connect_change;connect_change = test_bit(port1, hub->change_bits);clear_bit(port1, hub->event_bits);clear_bit(port1, hub->wakeup_bits);if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)return;...if (connect_change)hub_port_connect_change(hub, port1, portstatus, portchange);
}static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)__must_hold(&port_dev->status_lock
{...clear_bit(port1, hub->change_bits);...usb_unlock_port(port_dev);hub_port_connect(hub, port1, portstatus, portchange);usb_lock_port(port_dev);
}static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,u16 portchange)
{...status = 0;for (i = 0; i < SET_CONFIG_TRIES; i++) {udev = usb_alloc_dev(hdev, hdev->bus, port1);.../* 复位设备, 为设备分配地址, 获取设备描述符(usb_device_descriptor) */...status = hub_port_init(hub, udev, port1, i); .../* Run it through the hoops (find a driver, etc) */if (!status) {...status = usb_new_device(udev); /* 匹配设备到驱动,然后加载设备驱动 */...}}
}int usb_new_device(struct usb_device *udev)
{.../* 读取设备 配置描述符、字符串描述符、OTG 描述符、 quirk 配置 */err = usb_enumerate_device(udev); /* Read descriptors */....../* Tell the world! */announce_device(udev); /* usb usb2: New USB device found, idVendor=1d6b, idProduct=0002 */...err = device_add(&udev->dev); /* 添加设备到 driver core,将触发 USB 设备和驱动配对 */......return err;
}
USB 设备驱动的匹配和加载概要流程如下:
device_add()bus_add_device(dev)bus_probe_device(dev)device_initial_probe(dev)__device_attach(dev, true)bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver)driver_match_device(drv, dev) /* 配对 USB 设备 和 驱动 */driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */
这里我们假定 USB 设备驱动
比 USB 设备
先注册。对于 USB 设备
比其 设备驱动
先注册的情形,匹配和加载过程是类似的,这里不再赘述。
USB 设备驱动匹配加载过程,对于不同类别的设备,过程上存在着一定差异,下面取几个典型的类别设备驱动匹配加载过程进行分析。
7.1 HUB 类设备
驱动加载过程
前面在讲述 EHCI
主机控制器集成的 ROOT HUB
设备驱动加载过程中,没有描述 HUB 驱动和设备的匹配细节,我们将在本小节展开。对于非 ROOT HUB
和 ROOT HUB
设备的驱动加载过程,它们同样是通过 usb_new_device()
接口来加载驱动:
usb_new_device(udev)device_add(&udev->dev)...driver_match_device(drv, dev) /* 配对 USB 设备 和 驱动 */driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */
下面就来看看 HUB 类设备驱动加载的细节 :
/* drivers/base/base.h */static inline int driver_match_device(struct device_driver *drv,struct device *dev)
{/* 此处 drv->bus->match = usb_bus_type.match = usb_device_match() */return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
/* drivers/usb/core/driver.c */static int usb_device_match(struct device *dev, struct device_driver *drv)
{/* devices and interfaces are handled separately *//* devices and interfaces are handled separately */if (is_usb_device(dev)) {/* interface drivers never match devices */if (!is_usb_device_driver(drv)) /* 不是 device 级别的驱动,表示不匹配 */return 0;/* TODO: Add real matching code */return 1; /* 新插入的设备总是走这里: 先匹配到 usb_generic_driver */} else if (is_usb_interface(dev)) {/* interface 设备驱动匹配,在后面详述 */...}return 0;
}
所有新发现的 USB 设备(HUB 也是 USB 设备),首先都会先匹配到 USB 设备通用驱动 usb_generic_driver
,然后进入该驱动的 usb_probe_device()
接口:
driver_probe_device(drv, dev)really_probe(dev, drv)drv->probe(dev) = usb_probe_device()
/* drivers/usb/core/driver.c */static int usb_probe_device(struct device *dev)
{int error = 0;...if (!error)error = udriver->probe(udev); /* generic_probe() */return error;
}
/* drivers/usb/core/generic.c */static int generic_probe(struct usb_device *udev)
{int err, c;...if (udev->authorized == 0)...else {c = usb_choose_configuration(udev);if (c >= 0) {err = usb_set_configuration(udev, c);...}}...return 0;
}
/* drivers/usb/core/message.c */int usb_set_configuration(struct usb_device *dev, int configuration)
{.../* Allocate memory for new interfaces before doing anything else,* so that if we run out then nothing will have changed. */n = nintf = 0;if (cp) {/* 为 USB 设备 的 所有 interface 创建对象 */nintf = cp->desc.bNumInterfaces;new_interfaces = kmalloc(nintf * sizeof(*new_interfaces), GFP_NOIO);...for (; n < nintf; ++n) {new_interfaces[n] = kzalloc(sizeof(struct usb_interface), GFP_NOIO);...}...}.../* 初始化 USB 设备的 所有 interface 设备 */for (i = 0; i < nintf; ++i) {struct usb_interface_cache *intfc;struct usb_interface *intf;struct usb_host_interface *alt;cp->interface[i] = intf = new_interfaces[i];...alt = usb_altnum_to_altsetting(intf, 0);...intf->cur_altsetting = alt;usb_enable_interface(dev, intf, true);intf->dev.parent = &dev->dev;intf->dev.driver = NULL;intf->dev.bus = &usb_bus_type;intf->dev.type = &usb_if_device_type;...device_initialize(&intf->dev);...}kfree(new_interfaces);/* 发送 SET_CONFIGURATION 请求 */ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),USB_REQ_SET_CONFIGURATION, 0, configuration, 0,NULL, 0, USB_CTRL_SET_TIMEOUT);...dev->actconfig = cp;...usb_set_device_state(dev, USB_STATE_CONFIGURED);...for (i = 0; i < nintf; ++i) {struct usb_interface *intf = cp->interface[i];...device_enable_async_suspend(&intf->dev);/* * 添加 USB interface 设备到 driver core , * 触发 USB interface 设备驱动匹配流程 。*/ret = device_add(&intf->dev);...create_intf_ep_devs(intf);}usb_autosuspend_device(dev);return 0;
}
device_add(&udev->dev)...driver_match_device(drv, dev) /* 配对 USB 设备 和 驱动 */driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */static inline int driver_match_device(struct device_driver *drv,struct device *dev)
{/* 此处 drv->bus->match = usb_bus_type.match = usb_device_match() */return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}static int usb_device_match(struct device *dev, struct device_driver *drv)
{/* devices and interfaces are handled separately */if (is_usb_device(dev)) {/* USB interface 设备驱动匹配不走这里 */...} else if (is_usb_interface(dev)) { /* USB interface 设备驱动匹配走这里 */struct usb_interface *intf;struct usb_driver *usb_drv;const struct usb_device_id *id;/* device drivers never match interfaces */if (is_usb_device_driver(drv)) /* hub_driver 在这里成立 */return 0;intf = to_usb_interface(dev);usb_drv = to_usb_driver(drv); /* hub_driver */id = usb_match_id(intf, usb_drv->id_table);if (id)return 1;id = usb_match_dynamic_id(intf, usb_drv);if (id)return 1;}return 0;
}
按从 USB 外设(如 U盘、USB 键鼠)读取到的 设备描述符
、interface 描述符
信息,匹配到 hub_driver
的 usb_device_id
表后,将加载 HUB 类设备的驱动:
driver_probe_device(drv, dev) /* 加载 USB 设备驱动 */really_probe(dev, drv)drv->probe(dev) = usb_probe_interface()
/* drivers/usb/core/driver.c *//* called from driver core with dev locked */
static int usb_probe_interface(struct device *dev)
{struct usb_driver *driver = to_usb_driver(dev->driver);struct usb_interface *intf = to_usb_interface(dev);struct usb_device *udev = interface_to_usbdev(intf);const struct usb_device_id *id;int error = -ENODEV;......error = driver->probe(intf, id); /* 进入 HUB 驱动入口: hub_probe() */.......return error;
}
到此,HUB 类设备(包括 ROOT HUB
和 非 ROOT HUB
)驱动的加载过程完成。
7.2 非 HUB 类设备
驱动加载过程
7.2.1 按 VID & PID 匹配驱动
如 RealTek 的 R8152 USB 网卡,看它的驱动定义:
/* drivers/net/usb/r8152.c */#define REALTEK_USB_DEVICE(vend, prod) \\.match_flags = USB_DEVICE_ID_MATCH_DEVICE | \\USB_DEVICE_ID_MATCH_INT_CLASS, \\.idVendor = (vend), \\.idProduct = (prod), \\.bInterfaceClass = USB_CLASS_VENDOR_SPEC \\
}, \\
{ \\.match_flags = USB_DEVICE_ID_MATCH_INT_INFO | \\USB_DEVICE_ID_MATCH_DEVICE, \\.idVendor = (vend), \\.idProduct = (prod), \\.bInterfaceClass = USB_CLASS_COMM, \\.bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, \\.bInterfaceProtocol = USB_CDC_PROTO_NONE/* table of devices that work with this driver */
static const struct usb_device_id rtl8152_table[] = {...{REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8152)},...{}
};MODULE_DEVICE_TABLE(usb, rtl8152_table);static struct usb_driver rtl8152_driver = {.name = MODULENAME,.id_table = rtl8152_table,.probe = rtl8152_probe,.disconnect = rtl8152_disconnect,.suspend = rtl8152_suspend,.resume = rtl8152_resume,.reset_resume = rtl8152_reset_resume,.pre_reset = rtl8152_pre_reset,.post_reset = rtl8152_post_reset,.supports_autosuspend = 1,.disable_hub_initiated_lpm = 1,
};module_usb_driver(rtl8152_driver);
当 usb_device_match()
匹配过程中,读取到 R8152 网卡设备描述符的 VID & PID 信息
,匹配到驱动的 rtl8152_driver
的 ID 匹配表 rtl8152_table[]
,则加载 R8152 驱动 rtl8152_driver
,进入 rtl8152_probe()
执行。
7.2.2 按 interface class 匹配驱动
还有的 USB 设备,它们驱动的匹配表不指定 VID & PID,而是指定 interface class 信息。像这种情形,驱动的匹配是通过读取设备的 interface 描述符,然后提取其中的 interface class 信息进行匹配。
/* drivers/usb/class/usblp.c */static const struct usb_device_id usblp_ids[] = {{ USB_DEVICE_INFO(USB_CLASS_PRINTER, 1, 1) },...{ } /* Terminating entry */
};MODULE_DEVICE_TABLE(usb, usblp_ids);static struct usb_driver usblp_driver = {.name = "usblp",.probe = usblp_probe,.disconnect = usblp_disconnect,.suspend = usblp_suspend,.resume = usblp_resume,.id_table = usblp_ids,.supports_autosuspend = 1,
};module_usb_driver(usblp_driver);
这是一个 USB 打印机类别设备的通用驱动,当从打印机的 interface 描述符
提取到的 class 信息,与驱动匹配表的 class 信息 USB_CLASS_PRINTER
匹配时,将加载驱动,进入驱动接口 usblp_probe()
执行。
7.2.2 其它情形
还有更多其它驱动匹配的情形,细节呈现在 usb_device_match()
中,函数的逻辑不复杂,不在此处展开,感兴趣的读者可以自行阅读代码研究。
8. 参考资料
《Universal Serial Bus Specification》