2. USB协议基础
URB(USB Request Block)是 Linux USB 子系统中最核心的数据结构,它承载了主机与 USB 设备之间所有数据传输的请求与响应。无论是控制传输、批量传输、中断传输还是等时传输,最终都通过 struct urb 来封装。本章将深入剖析该结构的每个关键成员、传输标志位的含义、回调函数的执行机制以及缓冲区的管理策略。
2.1 struct urb 核心成员解析
struct urb 定义在 <linux/usb.h> 中,包含数十个成员。下表列出了驱动开发中最常操作的字段及其用途:
| 成员名称 | 类型 | 用途说明 |
|---|---|---|
dev |
struct usb_device * |
指向目标 USB 设备结构体,URB 提交前必须赋值 |
pipe |
unsigned int |
端点管道信息,包含端点号、方向、传输类型,通过 usb_rcvbulkpipe() 等宏构造 |
transfer_buffer |
void * |
数据缓冲区的内核虚拟地址,用于存放待发送或接收的数据 |
transfer_buffer_length |
int |
缓冲区长度(字节),决定本次传输的数据量 |
transfer_flags |
unsigned int |
传输标志位,控制 DMA 映射、短包处理、中断上下文等行为 |
actual_length |
int |
实际传输的字节数,在回调函数中读取,用于校验完整性 |
status |
int |
传输完成状态,0 表示成功,负值为错误码(如 -EPIPE、-ENOENT) |
complete |
usb_complete_t |
回调函数指针,URB 完成(成功/失败/取消)时被调用 |
context |
void * |
回调函数的私有上下文,通常指向请求对应的上层结构体 |
interval |
int |
中断/等时传输的轮询间隔(单位:帧/微帧),批量传输通常设为 0 |
number_of_packets |
int |
等时传输的包数量,批量传输固定为 1 |
iso_frame_desc[] |
struct usb_iso_packet_descriptor |
等时传输的帧描述数组,批量传输不使用 |
关键提示: 在批量传输场景中,
pipe 必须通过 usb_sndbulkpipe() 或 usb_rcvbulkpipe() 构造,number_of_packets 固定为 1,interval 被忽略。驱动开发者最需要关注的是 transfer_buffer、transfer_buffer_length、complete 和 context 四个字段。
2.2 传输标志位(transfer_flags)详解
transfer_flags 是一个位掩码,控制 URB 在内核中的处理行为。以下是批量传输中最常用的标志位:
- URB_NO_TRANSFER_DMA_MAP(0x0001):告知内核不要对
transfer_buffer进行 DMA 映射,适用于驱动已自行管理 DMA 缓冲区的场景。 - URB_ZERO_PACKET(0x0040):在批量 OUT 传输中,如果数据长度恰好是端点最大包长的整数倍,自动追加一个零长度包,用于通知设备传输结束。
- URB_SHORT_NOT_OK(0x0080):在批量 IN 传输中,如果实际接收数据少于请求长度,将
status置为-EREMOTEIO,驱动可通过此标志检测短包。 - URB_FREE_BUFFER(0x0100):URB 完成时自动释放
transfer_buffer,适用于一次性提交的场景。 - URB_NO_INTERRUPT(0x8000):禁止在 URB 完成时触发中断,适用于轮询模式或高吞吐场景。
性能陷阱: 批量传输中,如果未设置
URB_ZERO_PACKET 且数据长度恰好为端点最大包长的整数倍,设备可能一直等待后续数据,导致传输挂起。这是驱动开发中常见的隐蔽 bug。
下表总结了不同传输场景下的标志位推荐组合:
| 传输场景 | 推荐标志位组合 | 说明 |
|---|---|---|
| 批量 OUT(数据块) | URB_ZERO_PACKET |
确保整数倍包长时发送 ZLP |
| 批量 IN(流式读取) | URB_SHORT_NOT_OK |
检测设备是否提前结束传输 |
| 批量 IN(可变长度) | 0 | 允许短包,由 actual_length 判断实际数据量 |
| 高性能批量(DMA 预分配) | URB_NO_TRANSFER_DMA_MAP |
避免内核重复映射,提升吞吐 |
2.3 回调函数机制
回调函数是 URB 异步模型的核心。当 URB 完成(包括成功、失败、被取消)时,USB 核心会在适当的上下文中调用 complete 函数。回调函数的原型为:
void (*usb_complete_t)(struct urb *urb);
回调函数在以下三种情况下被触发:
- 正常完成:数据传输成功,
urb->status为 0,urb->actual_length包含实际传输字节数。 - 错误完成:硬件错误、端点停止、设备断开等,
urb->status为负值(如-EPIPE、-ESHUTDOWN)。 - 取消完成:调用
usb_unlink_urb()或usb_kill_urb()后,urb->status为-ENOENT或-ECONNRESET。
回调执行上下文: 回调函数通常在 中断上下文 中执行(除非 URB 提交时指定了
GFP_KERNEL 且系统支持)。因此,回调内部禁止调用可能导致睡眠的函数(如 mutex_lock()、kmalloc(GFP_KERNEL))。如果需要执行阻塞操作,应使用工作队列(workqueue)将任务推迟到进程上下文。
典型的回调函数实现模板:
static void bulk_complete(struct urb *urb)
{
struct my_device *dev = urb->context;
if (urb->status == 0) {
/* 传输成功,处理 urb->actual_length 字节数据 */
process_received_data(dev, urb->transfer_buffer,
urb->actual_length);
} else if (urb->status == -ENOENT) {
/* URB 被主动取消,无需处理 */
} else {
/* 错误处理:重试或上报 */
dev_err(&dev->interface->dev,
"URB failed with status %d\n", urb->status);
}
/* 如果需要持续接收,在此重新提交 URB */
if (dev->running)
submit_urb_for_read(dev);
}
2.4 缓冲区管理策略
缓冲区管理直接影响批量传输的性能和稳定性。Linux USB 子系统提供三种缓冲区管理方式:
| 管理方式 | 分配函数 | 适用场景 | 性能特点 |
|---|---|---|---|
| 内核堆分配 | kmalloc() |
小数据量、低频传输 | 简单但可能产生碎片,DMA 不安全 |
| DMA 一致性分配 | usb_alloc_coherent() |
高频批量传输、流式数据 | 缓存一致性保证,无 DMA 映射开销 |
| DMA 流式映射 | usb_buffer_map() |
已有缓冲区需临时 DMA 访问 | 灵活但需手动管理映射/取消映射 |
对于批量传输性能测试,推荐使用 DMA 一致性分配 配合 URB_NO_TRANSFER_DMA_MAP 标志位,避免每次提交时内核重复执行 DMA 映射操作,从而降低 CPU 开销。
/* 使用 usb_alloc_coherent 分配 DMA 安全缓冲区 */
dma_addr_t dma_handle;
void *buf = usb_alloc_coherent(dev->udev, BUF_SIZE,
GFP_KERNEL, &dma_handle);
if (!buf)
return -ENOMEM;
/* 初始化 URB */
usb_fill_bulk_urb(urb, dev->udev,
usb_rcvbulkpipe(dev->udev, EP_IN),
buf, BUF_SIZE, bulk_complete, dev);
urb->transfer_dma = dma_handle;
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
2.5 URB 生命周期与状态流转
下图展示了 URB 从创建到销毁的完整状态机,以及各阶段涉及的关键操作:
最佳实践: 在批量传输性能测试中,建议采用 URB 池化策略——预先分配一组 URB 和 DMA 缓冲区,循环提交和回收,避免频繁的分配/释放操作。这能显著降低延迟抖动,提升吞吐稳定性。
2.6 批量传输 URB 配置速查
以下代码片段展示了批量传输 URB 的完整配置流程,涵盖 OUT 和 IN 两个方向:
/* 批量 OUT 传输配置 */
struct urb *urb_out = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_bulk_urb(urb_out, udev,
usb_sndbulkpipe(udev, EP_OUT),
out_buf, out_len,
out_complete, out_ctx);
urb_out->transfer_flags |= URB_ZERO_PACKET;
/* 批量 IN 传输配置 */
struct urb *urb_in = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_bulk_urb(urb_in, udev,
usb_rcvbulkpipe(udev, EP_IN),
in_buf, in_len,
in_complete, in_ctx);
urb_in->transfer_flags |= URB_SHORT_NOT_OK;
/* 提交 URB */
ret = usb_submit_urb(urb_in, GFP_KERNEL);
if (ret)
dev_err(&interface->dev, "submit failed: %d\n", ret);