USB HUB 之热插拔

来源:互联网 发布:网络教育留学 编辑:程序博客网 时间:2024/04/29 06:15

1. usb_hub_init

    1.1  注册 hub driver.

        1.1.1  建立interupt in URB。侦测port status变化。

         1.1.2  注册usb_port_device_type 设备。

    1.2 建立内核线程 “khubd”.

        1.2.1  通过hub_events 来处理热插拔事件。

===============================================================

对于USB 来说,hub是一类很特殊的设备。USB 的即插即用功能就是通过这个实现的。那么code里面是怎么做的呢。

首先看看hub的初始化函数

int usb_hub_init(void){if (usb_register(&hub_driver) < 0) {printk(KERN_ERR "%s: can't register hub driver\n",usbcore_name);return -1;}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;}

这个函数做了两件事情:

1. 注册了 hub的驱动。

2. 建立了一个内核线程。

首先来看看hub driver,直接进去probe函数。

static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id){struct usb_host_interface *desc;struct usb_endpoint_descriptor *endpoint;struct usb_device *hdev;struct usb_hub *hub;desc = intf->cur_altsetting;hdev = interface_to_usbdev(intf);/* * Set default autosuspend delay as 0 to speedup bus suspend, * based on the below considerations: * * - Unlike other drivers, the hub driver does not rely on the *   autosuspend delay to provide enough time to handle a wakeup *   event, and the submitted status URB is just to check future *   change on hub downstream ports, so it is safe to do it. * * - The patch might cause one or more auto supend/resume for *   below very rare devices when they are plugged into hub *   first time: * *   devices having trouble initializing, and disconnect *   themselves from the bus and then reconnect a second *   or so later * *   devices just for downloading firmware, and disconnects *   themselves after completing it * *   For these quite rare devices, their drivers may change the *   autosuspend delay of their parent hub in the probe() to one *   appropriate value to avoid the subtle problem if someone *   does care it. * * - The patch may cause one or more auto suspend/resume on *   hub during running 'lsusb', but it is probably too *   infrequent to worry about. * * - Change autosuspend delay of hub can avoid unnecessary auto *   suspend timer for hub, also may decrease power consumption *   of USB bus. * * - If user has indicated to prevent autosuspend by passing *   usbcore.autosuspend = -1 then keep autosuspend disabled. */#ifdef CONFIG_PM_RUNTIMEif (hdev->dev.power.autosuspend_delay >= 0)pm_runtime_set_autosuspend_delay(&hdev->dev, 0);#endif/* * 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 */const struct hc_driver *drv = bus_to_hcd(hdev->bus)->driver;if (drv->bus_suspend && drv->bus_resume)usb_enable_autosuspend(hdev);}if (hdev->level == MAX_TOPO_LEVEL) {dev_err(&intf->dev,"Unsupported bus topology: hub nested too deep\n");return -E2BIG;}#ifdefCONFIG_USB_OTG_BLACKLIST_HUBif (hdev->parent) {dev_warn(&intf->dev, "ignoring external hub\n");return -ENODEV;}#endif/* Some hubs have a subclass of 1, which AFAICT according to the *//*  specs is not defined, but it works */if ((desc->desc.bInterfaceSubClass != 0) &&    (desc->desc.bInterfaceSubClass != 1)) {descriptor_error:dev_err (&intf->dev, "bad descriptor, ignoring hub\n");return -EIO;}/* Multiple endpoints? What kind of mutant ninja-hub is this? */if (desc->desc.bNumEndpoints != 1)goto descriptor_error;endpoint = &desc->endpoint[0].desc;/* If it's not an interrupt in endpoint, we'd better punt! */if (!usb_endpoint_is_int_in(endpoint))goto descriptor_error;/* We found a hub */dev_info (&intf->dev, "USB hub found\n");hub = kzalloc(sizeof(*hub), GFP_KERNEL);if (!hub) {dev_dbg (&intf->dev, "couldn't kmalloc hub struct\n");return -ENOMEM;}kref_init(&hub->kref);INIT_LIST_HEAD(&hub->event_list);hub->intfdev = &intf->dev;hub->hdev = hdev;INIT_DELAYED_WORK(&hub->leds, led_work);INIT_DELAYED_WORK(&hub->init_work, NULL);usb_get_intf(intf);usb_set_intfdata (intf, hub);intf->needs_remote_wakeup = 1;pm_suspend_ignore_children(&intf->dev, true);if (hdev->speed == USB_SPEED_HIGH)highspeed_hubs++;if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)hub->quirk_check_port_auto_suspend = 1;if (hub_configure(hub, endpoint) >= 0)return 0;hub_disconnect (intf);return -ENODEV;}

函数很长,但其实。大部分的工作都是做一些验证和初始化。最终真正的动作发生在这个函数里面:hub_configure

函数太长,就不贴出来了。

这个函数里面主要有两个地方和热插拔有关系。

首先它初始化了一个URB,这个URB 给一个interrupt IN的pipe建立的。

usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,hub, endpoint->bInterval);
对于一个URB来说,最关键的是其回调函数:

static void hub_irq(struct urb *urb){struct usb_hub *hub = urb->context;int status = urb->status;unsigned i;unsigned long bits;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 khubd 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;break;}hub->nerrors = 0;/* Something happened, let khubd figure it out */kick_khubd(hub);resubmit:if (hub->quiescing)return;if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0&& status != -ENODEV && status != -EPERM)dev_err (hub->intfdev, "resubmit --> %d\n", status);}

这个回调函数就是把hub返回的status存入 event_bits ,然后调用kick_khubd.

static void kick_khubd(struct usb_hub *hub){unsigned longflags;spin_lock_irqsave(&hub_event_lock, flags);if (!hub->disconnected && list_empty(&hub->event_list)) {list_add_tail(&hub->event_list, &hub_event_list);/* Suppress autosuspend until khubd runs */usb_autopm_get_interface_no_resume(to_usb_interface(hub->intfdev));wake_up(&khubd_wait);}spin_unlock_irqrestore(&hub_event_lock, flags);}
 这个kick_khubd 会把这个设备的event_list 加入到 hub_event_list 里面去。这个hub_event_list 会在后面用到。

hub_configure 做的第二件事情就是建立usb_port 设备,这种设备的类型是usb_port_device_type。

for (i = 0; i < hdev->maxchild; i++) {ret = usb_hub_create_port_device(hub, i + 1);if (ret < 0) {dev_err(hub->intfdev,"couldn't create port%d device.\n", i + 1);hdev->maxchild = i;goto fail_keep_maxchild;}}
这种设备对应的驱动是:

static bool usb_acpi_bus_match(struct device *dev){return is_usb_device(dev) || is_usb_port(dev);}static struct acpi_bus_type usb_acpi_bus = {.name = "USB",.match = usb_acpi_bus_match,.find_device = usb_acpi_find_device,};

下面该看那个内核线程了:

khubd_task = kthread_run(hub_thread, NULL, "khubd");

它的执行函数:

static int hub_thread(void *__unused){/* khubd needs to be freezable to avoid intefering 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. */set_freezable();do {hub_events();wait_event_freezable(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());} while (!kthread_should_stop() || !list_empty(&hub_event_list));pr_debug("%s: khubd exiting\n", usbcore_name);return 0;}

它通过hub_events 来处理热插拔事件。hub_events 太长就不贴出来了。

它做的事情就处理hub_event_list 里面的event。而这个list 是在前面的kick_khubd 里面去填充的。

如果有这个event是代表一个设备的插入而且这个设备也能正常工作。那么就会调用hub_port_connect_change。这个函数就会把插入的这个设备注册到驱动模型中去。然后usb device 驱动就接手了。

0 0
原创粉丝点击