USB Core
Layer
由前几节可知 USB 将 Device
进一步细分成了 3 个层级: Configuration
配置、
Interface
接口、 Endpoint
端点。
USB Core 为其中两个层次提供了 Device + Driver
的设备驱动模型,这两个层次分别是 USB
Device Layer
和 USB Interface Layer
层,一个 USB
Device
包含一个或多个 USB Interface
。其中:
-
USB Device Layer
层。这一层的Device
由Hub
创建,Hub
本身也是一种USB Device
;这一层的Driver
完成的功能非常简单,基本就是帮USB Device
创建其包含的所有子USB Interface
的Device
,大部分场景下都是使用usb_generic_driver
。 -
USB Interface Layer
层。这一层的Device
由上一级USB Device
在驱动 probe() 时创建;而这一层的Driver
就是普通的业务 Usb 驱动,即 Usb 协议中所说的Client Software
。
URB (USB Request Block)
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 配置读写的响应 */ }