Edit online

USB Core

4 Feb 2024
Read time: 6 minute(s)

Layer


image1

由前几节可知 USB 将 Device 进一步细分成了 3 个层级: Configuration 配置、 Interface 接口、 Endpoint 端点。

USB Core 为其中两个层次提供了 Device + Driver 的设备驱动模型,这两个层次分别是 USB Device LayerUSB Interface Layer 层,一个 USB Device 包含一个或多个 USB Interface。其中:

  • USB Device Layer 层。这一层的 DeviceHub 创建, Hub 本身也是一种 USB Device 。这一层的 Driver 完成的功能非常简单,基本就是帮 USB Device 创建其包含的所有子 USB InterfaceDevice ,大部分场景下都是使用 usb_generic_driver

  • USB Interface Layer 层。这一层的 Device 由上一级 USB Device 在驱动 probe() 时创建。而这一层的 Driver 就是普通的业务 Usb 驱动,即 Usb 协议中所说的 Client Software

URB (USB Request Block)


image2

USB Core 除了提供上一节描述的设备驱动模型以外,另一个重要的作用就是要给 USB Interface Driver 提供读写 USB 数据的 API,这一任务是围绕着 USB Request Block 来完成的。

USB Interface Driver 适配成功以后,会从配置信息中获取到当前 Interface 包含了多少个 Endpoint ,以及每个 Endpoint 的地址、传输类型、最大包长等其他信息。 Endpoint 是 USB 总线传输中最小的 寻址单位 ,Interface Driver 利用对几个 Endpoint 的读写来驱动具体的设备功能。

对某个 Endpoint 发起一次读写操作,具体工作使用 struct urb 数据结构来承担。

以下是一个对 Endpoint 0 使用 urb 发起读写的一个简单实例:

static int usb_internal_control_msg(struct usb_device *usb_dev,
                    unsigned int pipe,
                    struct usb_ctrlrequest *cmd,
                    void *data, int len, int timeout)
{
    struct urb *urb;
    int retv;
    int length;

    /* (1) 分配一个 urb 内存空间 */
    urb = usb_alloc_urb(0, GFP_NOIO);
    if (!urb)
        return -ENOMEM;

    /* (2) 填充 urb 内容,最核心的有 3 方面:
            1、总线地址:Device Num + Endpoint Num
            2、数据:data + len
            3、回调函数:usb_api_blocking_completion
    */
    usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,
                len, usb_api_blocking_completion, NULL);

    /* (3) 发送 urb 请求,并且等待请求完成 */
    retv = usb_start_wait_urb(urb, timeout, &length);
    if (retv < 0)
        return retv;
    else
        return length;
}

↓

static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length)
{
    struct api_context ctx;
    unsigned long expire;
    int retval;

    init_completion(&ctx.done);
    urb->context = &ctx;
    urb->actual_length = 0;
    /* (3.1) 把 urb 请求挂载到 hcd 的队列当中 */
    retval = usb_submit_urb(urb, GFP_NOIO);
    if (unlikely(retval))
        goto out;

    expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;
    /* (3.2) 当 urb 执行完成后,首先会调用 urb 的回调函数,然后会发送 completion 信号解除这里的阻塞 */
    if (!wait_for_completion_timeout(&ctx.done, expire)) {
        usb_kill_urb(urb);
        retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status);

        dev_dbg(&urb->dev->dev,
            "%s timed out on ep%d%s len=%u/%u\n",
            current->comm,
            usb_endpoint_num(&urb->ep->desc),
            usb_urb_dir_in(urb) ? "in" : "out",
            urb->actual_length,
            urb->transfer_buffer_length);
    } else
        retval = ctx.status;
out:
    if (actual_length)
        *actual_length = urb->actual_length;

    usb_free_urb(urb);
    return retval;
}

Normal Device urb_enqueue

对普通的 Usb device 来说,urb 最后会提交到 Host Controller 的收发队列上面,由 HC 完成实际的 USB 传输:

usb_submit_urb() → usb_hcd_submit_urb():

int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{

    /* (1) 如果是 roothub 走特殊的路径 */
    if (is_root_hub(urb->dev)) {
        status = rh_urb_enqueue(hcd, urb);
    /* (2) 如果是普通 device 调用对应的 hcd 的 urb_enqueue() 函数 */
    } else {
        status = map_urb_for_dma(hcd, urb, mem_flags);
        if (likely(status == 0)) {
            status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
            if (unlikely(status))
                unmap_urb_for_dma(hcd, urb);
        }
    }

}

7.12.5.1.2.2. Roothub Device urb_enqueue

特别需要注意的是 roothub 它是一个虚拟的 usb device,实际上它并不在 usb 总线上而是在 host 内部,所以相应的 urb 需要特殊处理,而不能使用 hcd 把数据发送到 Usb 总线上去。

usb_submit_urb() → usb_hcd_submit_urb() → rh_urb_enqueue():

static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
{
    /* (1) 对于 int 类型的数据,被挂载到 hcd->status_urb 指针上面
            通常 roothub 驱动用这个 urb 来查询 roothub 的端口状态
    */
    if (usb_endpoint_xfer_int(&urb->ep->desc))
        return rh_queue_status (hcd, urb);

    /* (2) 对于 control 类型的数据,是想读取 roothub ep0 上的配置信息
            使用软件来模拟这类操作的响应
    */
    if (usb_endpoint_xfer_control(&urb->ep->desc))
        return rh_call_control (hcd, urb);
    return -EINVAL;
}

|→

static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
{

    /* (1.1) 将 urb 挂载到对应的 ep 链表中 */
    retval = usb_hcd_link_urb_to_ep(hcd, urb);
    if (retval)
        goto done;

    /* (1.2) 将 urb 赋值给 hcd->status_urb
            在 hcd 驱动中,会通过这些接口来通知 roothub 的端口状态变化
    */
    hcd->status_urb = urb;
    urb->hcpriv = hcd;      /* indicate it's queued */
    if (!hcd->uses_new_polling)
        mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));

}

|→

static int rh_call_control (struct usb_hcd *hcd, struct urb *urb)
{
    /* (2.1) 软件模拟对 roothub 配置读写的响应 */
}