USB 请求块 (URB)¶
- 修订:
2000-12-05
- 再次:
2002-07-06
- 再次:
2005-09-19
- 再次:
2017-03-29
注意
USB 子系统现在在 Linux-USB 主机端 API 部分有一个重要的章节,该章节是从当前源代码生成的。这个特定的文档文件并不完整,可能不会更新到最新版本;除了快速概述之外,不要依赖它。
基本概念或“什么是 URB?”¶
新驱动程序的基本思想是消息传递,消息本身称为 USB 请求块,简称 URB。
URB 包含执行任何 USB 事务并将数据和状态返回的所有相关信息。
URB 的执行本质上是一个异步操作,即调用
usb_submit_urb()
会在成功将请求的操作排队后立即返回。可以使用
usb_unlink_urb()
随时取消一个 URB 的传输。每个 URB 都有一个完成处理程序,该处理程序在操作成功完成或取消后调用。URB 还包含一个上下文指针,用于将信息传递给完成处理程序。
设备的每个端点在逻辑上都支持一个请求队列。您可以填充该队列,以便在您的驱动程序处理另一个请求的完成时,USB 硬件仍然可以将数据传输到端点。这最大限度地利用了 USB 带宽,并支持在使用周期性传输模式时无缝地将数据流式传输到(或从)设备。
URB 结构¶
struct urb
中的一些字段是
struct urb
{
// (IN) device and pipe specify the endpoint queue
struct usb_device *dev; // pointer to associated USB device
unsigned int pipe; // endpoint information
unsigned int transfer_flags; // URB_ISO_ASAP, URB_SHORT_NOT_OK, etc.
// (IN) all urbs need completion routines
void *context; // context for completion routine
usb_complete_t complete; // pointer to completion routine
// (OUT) status after each completion
int status; // returned status
// (IN) buffer used for data transfers
void *transfer_buffer; // associated data buffer
u32 transfer_buffer_length; // data buffer length
int number_of_packets; // size of iso_frame_desc
// (OUT) sometimes only part of CTRL/BULK/INTR transfer_buffer is used
u32 actual_length; // actual data buffer length
// (IN) setup stage for CTRL (pass a struct usb_ctrlrequest)
unsigned char *setup_packet; // setup packet (control only)
// Only for PERIODIC transfers (ISO, INTERRUPT)
// (IN/OUT) start_frame is set unless URB_ISO_ASAP isn't set
int start_frame; // start frame
int interval; // polling interval
// ISO only: packets are only "best effort"; each can have errors
int error_count; // number of errors
struct usb_iso_packet_descriptor iso_frame_desc[0];
};
您的驱动程序必须使用其声明的接口中相应端点描述符中的值来创建“管道”值。
如何获取 URB?¶
通过调用 usb_alloc_urb()
分配 URB
struct urb *usb_alloc_urb(int isoframes, int mem_flags)
返回值是指向已分配 URB 的指针,如果分配失败,则返回 0。参数 isoframes 指定您要计划的同步传输帧的数量。对于 CTRL/BULK/INT,请使用 0。mem_flags 参数保存标准内存分配标志,允许您控制(包括其他内容)底层代码是否可以阻塞。
要释放 URB,请使用 usb_free_urb()
void usb_free_urb(struct urb *urb)
您可以释放您已提交但尚未在完成回调中返回给您的 urb。当它不再使用时,它将自动被释放。
需要填充什么?¶
根据事务的类型,在 linux/usb.h
中定义了一些内联函数来简化初始化,例如 usb_fill_control_urb()
、usb_fill_bulk_urb()
和 usb_fill_int_urb()
。通常,它们需要 usb 设备指针、管道(来自 usb.h 的常用格式)、传输缓冲区、所需的传输长度、完成处理程序及其上下文。看看一些现有的驱动程序,看看它们是如何使用的。
标志
对于 ISO,有两种启动行为:指定的 start_frame 或 ASAP。
对于 ASAP,在 transfer_flags 中设置
URB_ISO_ASAP
。
如果不能容忍短数据包,请在 transfer_flags 中设置 URB_SHORT_NOT_OK
。
如何提交 URB?¶
只需调用 usb_submit_urb()
int usb_submit_urb(struct urb *urb, int mem_flags)
mem_flags
参数(例如 GFP_ATOMIC
)控制内存分配,例如,当内存紧张时,较低级别是否可能阻塞。
它会立即返回,要么返回状态 0(请求已排队),要么返回一些错误代码,通常由以下原因引起
内存不足 (
-ENOMEM
)设备已拔出 (
-ENODEV
)端点停顿 (
-EPIPE
)排队的 ISO 传输过多 (
-EAGAIN
)请求的 ISO 帧过多 (
-EFBIG
)INT 间隔无效 (
-EINVAL
)INT 的数据包不止一个 (
-EINVAL
)
提交后,urb->status
为 -EINPROGRESS
;但是,除了在完成回调中之外,您永远不应该查看该值。
对于同步端点,您的完成处理程序应使用多缓冲,使用 URB_ISO_ASAP
标志将 URB(重新)提交到同一端点,以获得无缝的 ISO 流式传输。
如何取消已运行的 URB?¶
有两种方法可以取消您已提交但尚未返回到您的驱动程序的 URB。对于异步取消,请调用 usb_unlink_urb()
int usb_unlink_urb(struct urb *urb)
它会从内部列表中删除该 urb 并释放所有已分配的 HW 描述符。状态会更改以反映取消链接。请注意,当 usb_unlink_urb()
返回时,URB 通常不会完成;您仍然必须等待调用完成处理程序。
要同步取消 URB,请调用 usb_kill_urb()
void usb_kill_urb(struct urb *urb)
它会执行 usb_unlink_urb()
所做的所有操作,此外,它还会等待,直到 URB 返回并且完成处理程序完成。它还会将 URB 标记为暂时不可用,以便如果完成处理程序或任何其他人尝试重新提交它,他们将收到 -EPERM
错误。因此,您可以确信,当 usb_kill_urb()
返回时,URB 完全处于空闲状态。
有一个生命周期问题需要考虑。URB 可能随时完成,并且完成处理程序可能会释放 URB。如果当 usb_unlink_urb()
或 usb_kill_urb()
正在运行时发生这种情况,将导致内存访问冲突。驱动程序负责避免这种情况,这通常意味着需要某种锁来防止 URB 在仍在使用时被释放。
另一方面,由于 usb_unlink_urb 可能会最终调用完成处理程序,因此处理程序不得使用在调用 usb_unlink_urb 时持有的任何锁。解决此问题的一般方法是在持有锁时增加 URB 的引用计数,然后释放锁并调用 usb_unlink_urb 或 usb_kill_urb,然后再减少 URB 的引用计数。您可以通过调用 :c:func`usb_get_urb` 来增加引用计数
struct urb *usb_get_urb(struct urb *urb)
(忽略返回值;它与参数相同)并通过调用 usb_free_urb()
来减少引用计数。当然,如果完成处理程序没有释放 URB 的风险,则所有这些都不是必要的。
完成处理程序呢?¶
处理程序的类型如下
typedef void (*usb_complete_t)(struct urb *)
也就是说,它获取导致完成调用的 URB。在完成处理程序中,您应该查看 urb->status
以检测任何 USB 错误。由于上下文参数包含在 URB 中,您可以将信息传递给完成处理程序。
请注意,即使报告了错误(或取消链接),也可能已传输数据。这是因为 USB 传输是分组的;可能需要 16 个数据包来传输您的 1KB 缓冲区,并且在调用完成之前,其中 10 个可能已成功传输。
警告
永远不要在完成处理程序中休眠。
这些通常在原子上下文中调用。
在当前内核中,完成处理程序在禁用本地中断的情况下运行,但将来会更改此行为,因此不要假设本地 IRQ 始终在完成处理程序内部禁用。
如何进行等时(ISO)传输?¶
除了批量传输中存在的字段之外,对于 ISO,您还必须设置 urb->interval
以说明传输的频率;通常每帧一次(对于高速设备,每微帧一次)。实际使用的间隔将是 2 的幂,不大于您指定的值。您可以使用 usb_fill_int_urb()
宏来填充大多数 ISO 传输字段。
对于 ISO 传输,您还必须为每个要调度的包填充一个 usb_iso_packet_descriptor
结构,该结构由 usb_alloc_urb()
在 URB 的末尾分配。
usb_submit_urb()
调用将 urb->interval
修改为小于或等于请求的间隔值的已实现间隔值。如果使用 URB_ISO_ASAP
调度,则 urb->start_frame
也会更新。
对于每个条目,您必须指定此帧的数据偏移量(基地址是 transfer_buffer),以及您想要写入/期望读取的长度。完成后,actual_length 包含实际传输的长度,status 包含此帧的 ISO 传输的最终状态。允许逐帧指定不同的长度(例如,用于音频同步/自适应传输速率)。您还可以使用长度 0 来省略一个或多个帧(条带化)。
对于调度,您可以选择自己的起始帧或 URB_ISO_ASAP
。如前所述,如果您始终保持至少一个 URB 排队,并且您的完成程序保持(重新)提交后面的 URB,您将获得平滑的 ISO 流(如果 USB 带宽利用率允许)。
如果您指定自己的起始帧,请确保它比当前帧提前几帧。如果您要将 ISO 数据与某些其他事件流同步,您可能需要此模型。
如何启动中断(INT)传输?¶
中断传输与等时传输一样是周期性的,并且以 2 的幂次(1、2、4 等)单位间隔发生。单位是全速和低速设备的帧,以及高速设备的微帧。您可以使用 usb_fill_int_urb()
宏来填充 INT 传输字段。
usb_submit_urb()
调用将 urb->interval
修改为小于或等于请求的间隔值的已实现间隔值。
在 Linux 2.6 中,与早期版本不同,中断 URB 完成时不会自动重新启动。当调用完成处理程序时,它们会像其他 URB 一样结束。如果您希望重新启动中断 URB,则完成处理程序必须重新提交它。s