本文分析的是linux-5.4.3
一、Linux 下USB Hub热插拔处理
1、 Linux下USB HUB的驱动的实现和分析:
在系统初始化的时候在usb_init函数中调用usb_hub_init函数,就进入了hub的初始化。
代码路径:drivers\usb\core\hub.c
在usb_hub_init函数中完成了注册hub驱动,并且利用函数alloc_workqueue创建一个工作队列。
USB设备是热插拔,这就和PCI设备不同,PCI设备是在系统启动的时候都固定了,因此PCI设备只需要初始化进行枚举就可以了,采用递归算法即可。而USB设备需要热插拔,因此在hub_probe函数中调用hub_configure函数来配置hub,在这个函数中主要是利用函数usb_alloc_urb函数来分配一个urb,利用usb_fill_int_urb来初始化这个urb结构,包括hub的中断服务程序hub_irq的,查询的周期等。
每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq, 在该函数中置位event_bits,运行工作队列。进入hub_event函数,该函数用来处理端口变化的事件。然后通过一个for循环来检测每个端口的状态信息。利用usb_port_status获取端口信息,如果发生变化就调用hub_port_connect_change函数来配置端口等。
2、软件层次分析-初始化
这里我们先讲讲USB热插拔事件的处理工作。hub_event工作,运行于工作队列。
hub_event来检查usb port的事件通知HCD和usb core,然后做相应的处理。
驱动目录drivers/usb/*
usb/serial usb 串行设备驱动 (例如usb 3G卡、蓝牙等)
usb/storage usb 大储量磁盘驱动(u盘)
usb/host usb host usb主机控制器驱动(嵌入式otg:dwc_otg)
usb/core usb 核心一些处理代码,所有的驱动相关处理都在这里,也都注册到它里面。
usb/usb-skeleton.c 经典的usb客户驱动框架,可以参考
当然还有其他这里不再说明。
这里我们主要分析khub的工作原理: 硬件层次是hub的工作,如何和host及其设备间通信及相应事件
[usb/core/hub.c ]
int usb_hub_init(void)
{
if (usb_register(&hub_driver) < 0) { //注册usb HUB设备
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.
*/
/* 工作队列需要是可冻结的,以避免干扰usb持续的端口切换。
否则它可能会看到全速设备在EHCI控制器把端口交给全速控制器之前就消失了 */
hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0); //创建工作队列
if (hub_wq)
return 0;
}
然后进入hub的probe函数,主要是一些工作的初始化和hub的配置,我这里化简了
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
desc = intf->cur_altsetting;
hdev = interface_to_usbdev(intf);
/*
* Hubs have proper suspend/resume support, except for root hubs
* where the controller driver doesn\'t have bus_suspend and
* bus_resume methods.
*/
if (hdev->parent) { /* normal device */
usb_enable_autosuspend(hdev);
} else { /* root hub */ //根节点没有bus_suspend和bus_resume方法
const struct hc_driver *drv = bus_to_hcd(hdev->bus)->driver;
if (drv->bus_suspend && drv->bus_resume)
usb_enable_autosuspend(hdev);
}
hub = kzalloc(sizeof(*hub), GFP_KERNEL); //分配usb_hub结构体
if (!hub)
return -ENOMEM;
INIT_DELAYED_WORK(&hub->leds, led_work); //用于hub led闪烁的指示灯
INIT_DELAYED_WORK(&hub->init_work, NULL);
INIT_WORK(&hub->events, hub_event); //用于处理hub的事件
if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) //设置hub的端点0
return 0;
}
hub_configure配置hub,包括不同的hub的判断和配置,但Linux认为最多只能31个接口,里面比较复杂,这里也化简了
static int hub_configure(struct usb_hub *hub,
struct usb_endpoint_descriptor *endpoint)
{
ret = hub_hub_status(hub, &hubstatus, &hubchange); //获取hub的状态,主要是通过usb_control_msg进行通信
if (ret < 0) {
message = "can\'t get hub status";
goto fail;
}
hub->urb = usb_alloc_urb(0, GFP_KERNEL); //分配urb
if (!hub->urb) {
ret = -ENOMEM;
goto fail;
}
/* UHCI必须要知道HUB的端口的一些连接状态,因此,需要HUB周期性的上报它的端口连接状态.这个URB就是用来做这个用途的.
UHCI周期性的发送IN方向中断传输传输给HUB.HUB就会通过这个URB将端口信息发送给UHCI.那这个轮询周期是多长呢?
根据我们之前分析的UHCI的知识,它的调度周期是由endpoint的bInterval 字段所决定的.*/
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
hub, endpoint->bInterval); //填充urb,完成之后调用hub_irq函数
for (i = 0; i < maxchild; i++) {
ret = usb_hub_create_port_device(hub, i + 1); //创建hub的端点设备,比如/sys/devices/platform/soc@0/38100000.usb/xhci-hcd.0.auto/usb1/1-0:1.0/usb1-port1
if (ret < 0) {
dev_err(hub->intfdev,
"couldn\'t create port%d device.\n", i + 1);
break;
}
}
/* Update the HCD\'s internal representation of this hub before hub_wq
* starts getting port status changes for devices under the hub.
*/
//在hub_wq之前,更新这个集线器的HCD内部数据,开始为集线器下的设备获取端口状态变化。
if (hcd->driver->update_hub_device) {
ret = hcd->driver->update_hub_device(hcd, hdev,
&hub->tt, GFP_KERNEL);
if (ret < 0) {
message = "can\'t update HCD hub info";
goto fail;
}
}
hub_activate(hub, HUB_INIT);
return 0;
}
继续分析hub_activate,主要是启动hub,我们这里传入的参数是HUB_INIT
static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
/* Continue a partial initialization */
if (type == HUB_INIT2 || type == HUB_INIT3) {
device_lock(&hdev->dev);
/* Was the hub disconnected while we were waiting? */
if (hub->disconnected)
goto disconnected;
if (type == HUB_INIT2)
goto init2;
goto init3;
}
if (type == HUB_INIT) {
delay = hub_power_on_good_delay(hub); //上电后延时,使hub稳定
hub_power_on(hub, false); //对所有的端点上电,usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, NULL, 0, 1000);
INIT_DELAYED_WORK(&hub->init_work, hub_init_func2);
queue_delayed_work(system_power_efficient_wq,
&hub->init_work,
msecs_to_jiffies(delay)); //进入初始化的第二阶段hub_init_func2,也就是下面的 init2:
init2:
/*
* Check each port and set hub->change_bits to let hub_wq know
* which ports need attention.
*/
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
struct usb_port *port_dev = hub->ports[port1 - 1];
struct usb_device *udev = port_dev->child;
u16 portstatus, portchange;
portstatus = portchange = 0;
status = hub_port_status(hub, port1, &portstatus, &portchange); //获取端口的状态,
..........//后面进行一系列判断,包括usb2.0,3.0等。并且set_bit(port1, hub->change_bits);
/* If no port-status-change flags were set, we don\'t need any
* debouncing. If flags were set we can try to debounce the
* ports all at once right now, instead of letting hub_wq do them
* one at a time later on.
*
* If any port-status changes do occur during this delay, hub_wq
* will see them later and handle them normally.
*/
if (need_debounce_delay) { //用于消抖
delay = HUB_DEBOUNCE_STABLE;
/* Don\'t do a long sleep inside a workqueue routine */
if (type == HUB_INIT2) {
INIT_DELAYED_WORK(&hub->init_work, hub_init_func3); //进行第三个阶段init3:
queue_delayed_work(system_power_efficient_wq,
&hub->init_work,
msecs_to_jiffies(delay));
device_unlock(&hdev->dev);
return; /* Continues at init3: below */
}
init3:
status = usb_submit_urb(hub->urb, GFP_NOIO); //提交urb,等执行完成就会回调hub_irq
if (status < 0)
dev_err(hub->intfdev, "activate --> %d\n", status);
if (hub->has_indicators && blinkenlights) //如果有指示灯,点亮
queue_delayed_work(system_power_efficient_wq,
&hub->leds, LED_CYCLE_PERIOD);
/* Scan all ports that need attention */
kick_hub_wq(hub); //主要是queue_work(hub_wq, &hub->events),也就是把hub_event加入工作队列,开始运行
}
我们来看看hub_event,前面int2的时候有设置hub->change_bits,这里会进行处理
static void hub_event(struct work_struct *work)
{
if (hub->error) {
dev_dbg(hub_dev, "resetting for error %d\n", hub->error);
ret = usb_reset_device(hdev); //警告接口驱动程序并执行USB端口重置
}
/* deal with port status changes */
for (i = 1; i <= hdev->maxchild; i++) {
struct usb_port *port_dev = hub->ports[i - 1];
if (test_bit(i, hub->event_bits)
|| test_bit(i, hub->change_bits)
|| test_bit(i, hub->wakeup_bits)) { //如果这几个条件都满足,就port_event
/*
* The get_noresume and barrier ensure that if
* the port was in the process of resuming, we
* flush that work and keep the port active for
* the duration of the port_event(). However,
* if the port is runtime pm suspended
* (powered-off), we leave it in that state, run
* an abbreviated port_event(), and move on.
*/
pm_runtime_get_noresume(&port_dev->dev);
pm_runtime_barrier(&port_dev->dev);
usb_lock_port(port_dev);
port_event(hub, i); //处理事件
usb_unlock_port(port_dev);
pm_runtime_put_sync(&port_dev->dev);
}
}
}
我们再看看 port_event做了什么。
static void port_event(struct usb_hub *hub, int port1)
__must_hold(&port_dev->status_lock)
{
if (hub_port_status(hub, port1, &portstatus, &portchange) < 0) //确认端口改变了
return;
if (connect_change)
hub_port_connect_change(hub, port1, portstatus, portchange); //处理端口改变的情况
}
调用hub_port_connect_change再调用hub_port_connect
static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
u16 portchange)
{
/* Disconnect any existing devices under this port */
if (udev) {
if (hcd->usb_phy && !hdev->parent) //断开该端口设备的连接:如果是root hub,挂接在控制器上的,则断开该端口下的设备。
usb_phy_notify_disconnect(hcd->usb_phy, udev->speed);
usb_disconnect(&port_dev->child);
}
for (i = 0; i < SET_CONFIG_TRIES; i++) {
/* reallocate for each attempt, since references
* to the previous one can escape in various ways
*/
// 分配usb设备内存并初始化bus、type、group、设备在系统中的路径(dev->path)、ep0的属性并设置设备状态为attached。
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) {
dev_err(&port_dev->dev,
"couldn\'t allocate usb_device\n");
goto done;
}
usb_set_device_state(udev, USB_STATE_POWERED); //设置为 power状态,并设置电源等。
udev->bus_mA = hub->mA_per_port;
udev->level = hdev->level + 1;
udev->wusb = hub_is_wusb(hub);
/* Devices connected to SuperSpeed hubs are USB 3.0 or later */
if (hub_is_superspeed(hub->hdev)) //判断速度是否为高速
udev->speed = USB_SPEED_SUPER;
else
udev->speed = USB_SPEED_UNKNOWN;
choose_devnum(udev); //获取设备号,在usbfs中,设备号被用作文件名.
if (udev->devnum <= 0) {
status = -ENOTCONN; /* Don\'t retry */
goto loop;
}
/* reset (non-USB 3.0 devices) and get descriptor */
usb_lock_port(port_dev);
status = hub_port_init(hub, udev, port1, i); //初始化设备,设置地址,读取设备描述符
usb_unlock_port(port_dev);
/* Run it through the hoops (find a driver, etc) */
if (!status) {
status = usb_new_device(udev); //执行初始设备设置
if (status) {
mutex_lock(&usb_port_peer_mutex);
spin_lock_irq(&device_state_lock);
port_dev->child = NULL;
spin_unlock_irq(&device_state_lock);
mutex_unlock(&usb_port_peer_mutex);
} else {
if (hcd->usb_phy && !hdev->parent)
usb_phy_notify_connect(hcd->usb_phy,
udev->speed);
}
}
}
}
我们重点分析一下usb_new_device,初始化设备
int usb_new_device(struct usb_device *udev)
{
/* Tell the runtime-PM framework the device is active */
pm_runtime_set_active(&udev->dev);
pm_runtime_get_noresume(&udev->dev);
pm_runtime_use_autosuspend(&udev->dev);
pm_runtime_enable(&udev->dev);
/* By default, forbid autosuspend for all devices. It will be
* allowed for hubs during binding.
*/
usb_disable_autosuspend(udev);
err = usb_enumerate_device(udev); /* Read descriptors */ //读取描述符
if (err < 0)
goto fail;
dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n",
udev->devnum, udev->bus->busnum,
(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
/* export the usbdev device-node for libusb */
udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,
(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
/* Tell the world! */
announce_device(udev); //打印出来,需要内核打开CONFIG_USB_ANNOUNCE_NEW_DEVICES宏
/* Register the device. The device driver is responsible
* for configuring the device and invoking the add-device
* notifier chain (used by usbfs and possibly others).
*/
err = device_add(&udev->dev); //将设备添加到设备层次结构中
if (err) {
dev_err(&udev->dev, "can\'t device_add, error %d\n", err);
goto fail;
}
/* Create link files between child device and usb port device. */
if (udev->parent) { //创建符号链接
struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
int port1 = udev->portnum;
struct usb_port *port_dev = hub->ports[port1 - 1];
err = sysfs_create_link(&udev->dev.kobj,
&port_dev->dev.kobj, "port");
if (err)
goto fail;
err = sysfs_create_link(&port_dev->dev.kobj,
&udev->dev.kobj, "device");
if (err) {
sysfs_remove_link(&udev->dev.kobj, "port");
goto fail;
}
if (!test_and_set_bit(port1, hub->child_usage_bits))
pm_runtime_get_sync(&port_dev->dev);
}
(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev); //创建端点设备节点,/sys/devices/platform/soc@0/38100000.usb/xhci-hcd.0.auto/usb1/ep_00
}
3.软件层次分析-Hub部分热拔插
前面主要是初始化的操作,也会去识别开机的时候已经插入的设备,如果是开机之后插入的设备,又是什么流程呢
前面是每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq
static void hub_irq(struct urb *urb)
{
switch (status) {
case -ENOENT: /* synchronous unlink */
case -ECONNRESET: /* async unlink */
case -ESHUTDOWN: /* hardware going away */
return;
default: /* presumably an error */
/* Cause a hub reset after 10 consecutive errors */
dev_dbg(hub->intfdev, "transfer --> %d\n", status);
if ((++hub->nerrors < 10) || hub->error)
goto resubmit;
hub->error = status;
/* FALL THROUGH */
/* let hub_wq handle things */
case 0: /* we got data: port status changed */
bits = 0;
for (i = 0; i < urb->actual_length; ++i)
bits |= ((unsigned long) ((*hub->buffer)[i]))
<< (i*8);
hub->event_bits[0] = bits; //置位event_bits /* status change bitmask */
break;
}
hub->nerrors = 0;
/* Something happened, let hub_wq figure it out */
kick_hub_wq(hub); //
resubmit:
hub_resubmit_irq_urb(hub); //主要是queue_work(hub_wq, &hub->events),也就是把hub_event加入工作队列,开始运行,前面有分析了
}
这里我们同样贴出它的函数调用流程图,虽然是旧版本的,但是大致差不多(这里懒得自己画了,就剪切了个^^)
通过流程图我们可以清晰的明白,当usb设备插入usb接口后,hub_irq执行,启动工作队列执行hub_event工作,它检测到port状态的变化,调用hub_port_connect_change(),如果是新设备那么usb_allco_dev,然后调用usb_new_device来进行配置使usb设备可以正常工作。
二.usb驱动的probe匹配过程
前面我们分析到调用usb_new_device来进行配置使usb设备可以正常工作,我们现在分析一下具体过程。主要是找到对应的客户驱动程序和该USB设备挂钩。
usb_new_device中调用device_add,将设备添加到设备层次结构中。
大概调用流程:device_add -> bus_probe_device -> device_initial_probe -> __device_attach -> bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
struct klist_iter i;
struct device_driver *drv;
int error = 0;
if (!bus)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_drivers, &i,
start ? &start->p->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error) //遍历驱动链表
error = fn(drv, data); //这里是调用__device_attach_driver
klist_iter_exit(&i);
return error;
}
该函数调用bus_for_each_drv()来从总线上已注册的所有驱动中找出匹配的驱动程序.遍历bus上的所有驱动程序,并为每个驱动调用fn()来查看是否匹配. 这里的fn就是__device_attach_driver.这里化简了
static int __device_attach_driver(struct device_driver *drv, void *_data)
{
ret = driver_match_device(drv, dev); //这里调用drv->bus->match(dev, drv),就是usb_device_match,总线match函数,USB core部分注册了
if (ret == 0) {
/* no match */
return 0;
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
driver_deferred_probe_add(dev);
} else if (ret < 0) {
dev_dbg(dev, "Bus failed to match device: %d", ret);
return ret;
} /* ret > 0 means positive match */
return driver_probe_device(drv, dev); //尝试将设备和驱动程序绑定在一起
}
1. usb_device_match
我们分析一下就是usb_device_match,这个函数只是做一些粗略的匹配, 如果匹配成功则返回1。这个函数只是做一些粗略的匹配, 如果匹配成功则返回1, 然后由driver_probe_device来做进一步的匹配, 如果匹配失败则返回0, 并且driver_probe_device也不会在执行. 这个函数的调用保证了dev, drv 要么都是设备级别的( 即dev 代表usb 设备,drv 代表usb 设备驱动), 要么都是接口级别的( 即dev 代表usb 设备的一个interface,drv 代表usb 接口驱动).
static int usb_device_match(struct device *dev, struct device_driver *drv)
{
/* devices and interfaces are handled separately */
if (is_usb_device(dev)) {
/* interface drivers never match devices */ //是匹配USB设备的驱动,USB接口的驱动不能匹配
if (!is_usb_device_driver(drv))
return 0;
/* TODO: Add real matching code */
return 1;
} else if (is_usb_interface(dev)) { //如果是USB接口
struct usb_interface *intf;
struct usb_driver *usb_drv;
const struct usb_device_id *id;
/* device drivers never match interfaces */
//usb接口在注册driver时将for_devices设置为0,for_devices =1,表示设备驱动,for_devices = 0,表示接口驱动
if (is_usb_device_driver(drv))
return 0;
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
id = usb_match_id(intf, usb_drv->id_table); //匹配id table
if (id)
return 1;
id = usb_match_dynamic_id(intf, usb_drv); //匹配动态id table
if (id)
return 1;
}
return 0;
}
2.driver_probe_device
driver_probe_device主要是调用really_probe -> (drv->probe)
对于usb 来说这个函数的调用有2 种分支, 1: dev,drv 代表的是设备级别的, 2 dev,drv 代表的是接口级别的. 其他情况组合在usb_device_match 中被过滤掉了.
分支1: dev,drv 代表的是设备级别:
此时的drv 肯定是usb_generic_driver. 因为在当前的usb 系统中只有这个driver 是代表整个设备的驱动, 它是在usb_init 中被注册的, 而我们通常写的usb 驱动都是代表一个interface 的.
因此, 此时的drv->probe 将调用generic_probe().再到usb_set_configuration
int usb_set_configuration(struct usb_device *dev, int configuration)
{
for(I = 0; I < nintf; i++) {
struct usb_interface *intf = cp->interface[i];
device_add(&intf->dev); //又会进入匹配
}
分支2: dev,drv 代表的是interface 级别:
此时的dev 代表着一个interface, 而drv 就代表了我们自己的usb 驱动. 但是我们应当看到drv是device_driver 类型, 而我们写的usb 驱动的类型一般是usb_driver, 因此这里的probe 和我们自己写的probe 显然不是同一个. 实际上这里的drv 是我们的驱动对象里内嵌的一个子对象( 因为linux 下所以的驱动都必须用device_driver 来代表,). 那这个子对象的probe 函数是在哪里赋值的呢?
这就要看usb_register宏了,实际里面是调用usb_register_driver
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
new_driver->drvwrap.for_devices = 0;
new_driver->drvwrap.driver.name = new_driver->name;
new_driver->drvwrap.driver.bus = &usb_bus_type;
new_driver->drvwrap.driver.probe = usb_probe_interface; //这里是probe函数
new_driver->drvwrap.driver.remove = usb_unbind_interface;
new_driver->drvwrap.driver.owner = owner;
new_driver->drvwrap.driver.mod_name = mod_name;
new_driver->drvwrap.driver.dev_groups = new_driver->dev_groups;
spin_lock_init(&new_driver->dynids.lock);
INIT_LIST_HEAD(&new_driver->dynids.list);
retval = driver_register(&new_driver->drvwrap.driver);
if (retval)
goto out;
retval = usb_create_newid_files(new_driver);
if (retval)
goto out_newid;
pr_info("%s: registered new interface driver %s\n",
usbcore_name, new_driver->name); //一般log会打印这个
}
跟踪这个函数我们可以看到这里的probe 函数实际上是usb_probe_interface( 所有的usb interface 驱动都是一样的).
static int usb_probe_interface(struct device *dev)
{
struct driver = to_usb_driver(dev->driver); //dev->driver 在really_probe 中设置.
error = driver->probe(intf, id); // 这个就是我们自己写的probe 函数了.
}
driver->probe(intf, id); 这就调用到我们自己写的代码里面了,
3.流程图
大概流程图是一样的,我搬运来了