3. URB 生命周期:创建、填充、提交、完成、取消与回收的完整流程
URB(USB Request Block)是 USB 设备驱动与主机控制器之间的核心通信载体,其生命周期贯穿整个数据传输过程。理解 URB 的完整生命周期——从创建到回收——是编写稳定、高效 USB 驱动的基石。本章将逐一拆解每个阶段的操作细节、数据结构变化以及常见陷阱。
3.1 URB 生命周期全景图
下图展示了 URB 从创建到回收的完整状态流转路径,以及每个阶段对应的关键操作函数。
3.2 创建阶段:分配 URB 结构体
URB 的创建通过 usb_alloc_urb() 函数完成,该函数在内核堆中分配 struct urb 结构体及其内部数据缓冲区。
#include <linux/usb.h>
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
| 参数 | 说明 | 典型值 |
|---|---|---|
iso_packets |
等时传输的包数量,非等时传输设为 0 | 0(批量/中断/控制传输) |
mem_flags |
内存分配标志 | GFP_KERNEL(进程上下文)GFP_ATOMIC(中断上下文) |
GFP_KERNEL 会导致内核休眠,引发 BUG: scheduling while atomic 错误。必须使用 GFP_ATOMIC。
创建成功后,返回的 struct urb * 指针指向一块已清零的内存区域。驱动开发者需要检查返回值是否为 NULL:
struct urb *urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
dev_err(&interface->dev, "Failed to allocate URB\n");
return -ENOMEM;
}
3.3 填充阶段:配置传输参数
填充阶段是 URB 生命周期中最关键的步骤,需要设置传输类型、目标端点、数据缓冲区、回调函数和超时参数。Linux 内核提供了针对不同传输类型的专用填充函数:
| 传输类型 | 填充函数 | 关键参数 |
|---|---|---|
| 批量传输 | usb_fill_bulk_urb() |
pipe, buffer, len, complete_fn, context |
| 中断传输 | usb_fill_int_urb() |
pipe, buffer, len, complete_fn, context, interval |
| 控制传输 | usb_fill_control_urb() |
pipe, setup_packet, buffer, len, complete_fn, context |
| 等时传输 | 手动填充 urb->iso_frame_desc[] |
packet_count, offset, length, actual_length |
以批量传输为例,填充操作的典型代码模式如下:
// 分配传输缓冲区
char *buffer = kmalloc(MAX_PACKET_SIZE, GFP_KERNEL);
if (!buffer) {
usb_free_urb(urb);
return -ENOMEM;
}
// 填充 URB
usb_fill_bulk_urb(urb,
udev,
usb_sndbulkpipe(udev, BULK_OUT_EP),
buffer,
MAX_PACKET_SIZE,
bulk_write_complete,
context);
// 设置超时时间(单位:毫秒)
urb->transfer_flags |= URB_TIMEOUT;
urb->timeout = msecs_to_jiffies(5000); // 5秒超时
urb->transfer_flags:控制 URB 行为,常用标志包括 URB_TIMEOUT(启用超时)、URB_ZERO_PACKET(批量传输末尾发送零长度包)、URB_SHORT_NOT_OK(短包视为错误)。urb->timeout:以 jiffies 为单位的超时值,仅在设置 URB_TIMEOUT 标志后生效。
3.4 提交阶段:将 URB 送入主机控制器
提交 URB 使用 usb_submit_urb() 函数,该函数将 URB 加入主机控制器驱动(HCD)的调度队列。提交后,URB 的所有权从驱动转移给 USB 核心层和 HCD。
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
提交操作的返回值及其含义:
| 返回值 | 含义 | 常见原因 |
|---|---|---|
| 0 | 提交成功 | URB 已加入 HCD 队列 |
-ENOMEM |
内存不足 | HCD 内部 DMA 缓冲区分配失败 |
-EINVAL |
参数无效 | 端点号错误、URB 已提交、缓冲区未对齐 |
-ESHUTDOWN |
设备已断开 | USB 设备已被拔出或复位 |
-EPIPE |
端点被 stall | 端点处于 halt 状态,需先调用 usb_clear_halt() |
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(&interface->dev, "URB submission failed: %d\n", ret);
// 根据错误码进行恢复处理
if (ret == -EPIPE) {
usb_clear_halt(urb->dev, urb->pipe);
}
goto error_cleanup;
}
transfer_buffer、transfer_dma、setup_packet 等字段,直到回调函数被调用。HCD 可能在提交后立即开始 DMA 传输。
3.5 完成阶段:回调函数处理
当 HCD 完成数据传输(或发生错误)时,会调用 URB 的完成回调函数。回调函数在中断上下文执行,必须快速返回,不能执行可能休眠的操作。
static void bulk_write_complete(struct urb *urb)
{
struct my_context *ctx = urb->context;
// 检查传输状态
switch (urb->status) {
case 0:
// 传输成功
dev_info(&ctx->interface->dev,
"Bulk write completed, %d bytes sent\n",
urb->actual_length);
break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
// URB 被取消或设备断开,正常退出
return;
case -ETIMEDOUT:
// 超时错误
dev_err(&ctx->interface->dev, "Bulk write timed out\n");
// 触发超时恢复流程
schedule_work(&ctx->timeout_recovery_work);
return;
default:
// 其他错误
dev_err(&ctx->interface->dev,
"Bulk write error: %d\n", urb->status);
return;
}
// 传输成功,可以重新提交或释放 URB
complete(&ctx->transfer_done);
}
回调函数中 urb->status 的常见值:
| 状态值 | 含义 | 处理建议 |
|---|---|---|
| 0 | 传输成功 | 检查 actual_length 确认数据完整性 |
-ETIMEDOUT |
超时 | 执行超时恢复,可能需要复位端点或设备 |
-EPIPE |
端点 stall | 调用 usb_clear_halt() 清除 halt 状态 |
-ECONNRESET |
URB 被取消 | 正常退出,不要重新提交 |
-ENOENT |
URB 被同步取消 | 正常退出,不要重新提交 |
-ESHUTDOWN |
设备断开 | 停止所有 I/O,等待设备重新连接 |
3.6 取消阶段:终止正在进行的传输
当需要提前终止 URB 传输时(如设备断开、超时恢复、模块卸载),使用 usb_unlink_urb() 或 usb_kill_urb() 函数。
| 函数 | 行为 | 适用场景 |
|---|---|---|
usb_unlink_urb() |
异步取消,立即返回 | 中断上下文、不希望阻塞的路径 |
usb_kill_urb() |
同步取消,等待回调完成 | 进程上下文、模块卸载、设备断开处理 |
// 异步取消(不等待)
int ret = usb_unlink_urb(urb);
if (ret == 0) {
dev_info(&interface->dev, "URB unlinked successfully\n");
} else if (ret == -EINPROGRESS) {
dev_info(&interface->dev, "URB already completed\n");
}
// 同步取消(等待回调执行完毕)
usb_kill_urb(urb);
// 此时 URB 的回调函数已经执行完毕,可以安全释放资源
usb_kill_urb() 取消同一个 URB,这会导致死锁。回调函数中只能使用 usb_unlink_urb() 进行异步取消。
3.7 回收阶段:释放 URB 资源
当 URB 不再需要时,必须调用 usb_free_urb() 释放其占用的内存。回收前必须确保 URB 已经处于“完成”或“取消”状态,且回调函数已经执行完毕。
// 安全回收 URB 的标准流程
usb_kill_urb(urb); // 确保 URB 已停止
usb_free_urb(urb); // 释放 URB 结构体
kfree(transfer_buffer); // 释放传输缓冲区(如果是驱动分配的)
URB 回收的完整状态检查流程:
usb_alloc_urb() 必须对应一个 usb_free_urb()。在驱动卸载路径中,建议使用 usb_poison_urb() 替代 usb_kill_urb(),前者会阻止 URB 被重新提交,确保在驱动卸载后不会有悬空指针回调。
3.8 完整生命周期示例代码
以下代码展示了批量传输 URB 从创建到回收的完整流程,包含超时处理和错误恢复:
static int my_usb_bulk_transfer(struct usb_device *udev,
struct usb_interface *interface,
void *data, size_t len)
{
struct urb *urb;
char *buffer;
int ret;
DECLARE_COMPLETION_ONSTACK(done);
// 1. 创建 URB
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return -ENOMEM;
// 2. 分配缓冲区
buffer = kmalloc(len, GFP_KERNEL);
if (!buffer) {
usb_free_urb(urb);
return -ENOMEM;
}
memcpy(buffer, data, len);
// 3. 填充 URB
usb_fill_bulk_urb(urb, udev,
usb_sndbulkpipe(udev, BULK_OUT_EP),
buffer, len,
bulk_transfer_complete, &done);
urb->transfer_flags |= URB_TIMEOUT;
urb->timeout = msecs_to_jiffies(3000); // 3秒超时
// 4. 提交 URB
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(&interface->dev, "submit urb failed: %d\n", ret);
kfree(buffer);
usb_free_urb(urb);
return ret;
}
// 5. 等待完成或超时
if (!wait_for_completion_timeout(&done,
msecs_to_jiffies(5000))) {
// 超时,取消 URB
usb_unlink_urb(urb);
wait_for_completion(&done);
dev_err(&interface->dev, "transfer timed out\n");
ret = -ETIMEDOUT;
} else {
// 检查传输结果
if (urb->status == 0) {
ret = urb->actual_length;
} else {
dev_err(&interface->dev, "transfer error: %d\n",
urb->status);
ret = urb->status;
}
}
// 6. 回收资源
usb_kill_urb(urb); // 确保 URB 完全停止
usb_free_urb(urb);
kfree(buffer);
return ret;
}
static void bulk_transfer_complete(struct urb *urb)
{
struct completion *done = urb->context;
complete(done);
}