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 配置读写的响应 */
}