Edit online

USB Hub Driver

4 Feb 2024
Read time: 6 minute(s)

image3

普通的 Usb Device 通过内部的 Interface 提供各种业务功能。而 Hub 这类特殊的 Usb Device 功能就一种,那就是监控端口的状态变化:

  • 在端口上有设备 attach 时,创建新的 usb device,给其适配驱动。如果是 hub device,子 usb 驱动会进一步扫描端口。

  • 在端口上有设备 deattach 时,移除掉对应的 usb device。如果是 hub device 进一步移除其所有的子 usb device。

Hub 也是标准的 Usb Device,它也是标准的流程被上一级设备发现后 创建 Usb Device创建 Usb Interface ,然后被 Usb Hub Interface Driver 给适配到。系统中只有一个 Hub 驱动:
static const struct usb_device_id hub_id_table[] = {
    { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
                | USB_DEVICE_ID_MATCH_PRODUCT
                | USB_DEVICE_ID_MATCH_INT_CLASS,
    .idVendor = USB_VENDOR_SMSC,
    .idProduct = USB_PRODUCT_USB5534B,
    .bInterfaceClass = USB_CLASS_HUB,
    .driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},
    { .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 = {
    .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,
};

hub_driver 驱动启动以后,只做一件事情发送一个查询端口状态的 urb

hub_probe() → hub_configure():

static int hub_configure(struct usb_hub *hub,
    struct usb_endpoint_descriptor *endpoint)
{

    /* (1) 分配 urb */
    hub->urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!hub->urb) {
        ret = -ENOMEM;
        goto fail;
    }

    /* (2) 初始化 urb,作用就是通过 ep0 查询 hub 的端口状态
            urb 的回调函数是 hub_irq()
    */
    usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
        hub, endpoint->bInterval);

    /* (3) 发送 urb */
    hub_activate(hub, HUB_INIT);

}

↓

static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
    /*  (3.1) 提交 urb */
    status = usb_submit_urb(hub->urb, GFP_NOIO);
}

Normal Hub Port op

在普通的 hub 中,端口操作是通过标准的 urb 发起 usb ep0 读写。分为两类:

  • 通过轮询读取 Hub Class-specific Requests 配置来查询端口的状态:


    image4

  • 设置和使能端口也是通过 Hub Class-specific Requests 中相应的命令实现的:


    image5

RootHub Port op

而对于 roothub 来说,对端口的操作的 urb 都需要特殊处理 (以 EHCI 的驱动为例):

  • 端口状态的变化可以通过 HCD 触发中断再上报:
    ehci_irq() → usb_hcd_poll_rh_status() :
    
    void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
    {
    
        /* (1) 获取端口状态的变化 */
        length = hcd->driver->hub_status_data(hcd, buffer);
        if (length > 0) {
    
            /* try to complete the status urb */
            spin_lock_irqsave(&hcd_root_hub_lock, flags);
    
            /* (2) 通过回复 hcd->status_urb 来进行上报 */
            urb = hcd->status_urb;
            if (urb) {
                clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
                hcd->status_urb = NULL;
                urb->actual_length = length;
                memcpy(urb->transfer_buffer, buffer, length);
    
                usb_hcd_unlink_urb_from_ep(hcd, urb);
                usb_hcd_giveback_urb(hcd, urb, 0);
            } else {
                length = 0;
                set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
            }
            spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
        }
    
    }
    
    ↓
    
    hcd->driver->hub_status_data() → ehci_hub_status_data():
    
    static int
    ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
    {
        /* (1.1) 通过 HCD 驱动,获取 roothub 端口的状态 */
    }
    
  • 设置和使能端口需要嫁接到 HCD 驱动相关函数上实现:
    usb_hcd_submit_urb() → rh_urb_enqueue() → rh_call_control() → hcd->driver->hub_control() → ehci_hub_control():
    
    int ehci_hub_control(
        struct usb_hcd  *hcd,
        u16             typeReq,
        u16             wValue,
        u16             wIndex,
        char            *buf,
        u16             wLength
    ) {
        /* (1) 通过 HCD 驱动,设置 roothub 的端口 */
    }
    

Device Attach

hub_event() → port_event() → hub_port_connect_change() → hub_port_connect():

static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
        u16 portchange)
{

    for (i = 0; i < PORT_INIT_TRIES; i++) {

        /* (1) 给端口上新 Device 分配 `struct usb_device` 数据结构 */
        udev = usb_alloc_dev(hdev, hdev->bus, port1);
        if (!udev) {
            dev_err(&port_dev->dev,
                    "couldn't allocate usb_device\n");
            goto done;
        }

        /* (2) 给新的 Device 分配一个新的 Address */
        choose_devnum(udev);
        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);
        /* (3) 使能端口,并且调用 hub_set_address() 给 Device 配置上新分配的 Address */
        status = hub_port_init(hub, udev, port1, i);
        usb_unlock_port(port_dev);

        /* (4) 注册 `struct usb_device` */
            status = usb_new_device(udev);

    }

}

Device Deattach

hub_event() → port_event() → hub_port_connect_change() → hub_port_connect():

static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
        u16 portchange)
{

    /* (1) 移除端口上的 `struct usb_device` */
    if (udev) {
        if (hcd->usb_phy && !hdev->parent)
            usb_phy_notify_disconnect(hcd->usb_phy, udev->speed);
        usb_disconnect(&port_dev->child);
    }

}