4. URB提交API:usb_submit_urb 函数原型、参数含义、返回值解析
在Linux USB驱动中,usb_submit_urb 是驱动向主机控制器提交URB(USB Request Block)的核心API。该函数将驱动构造好的URB插入到主机控制器的调度队列中,触发实际的USB数据传输。理解其原型、参数和返回值是调试URB提交失败的基础。
4.1 函数原型
usb_submit_urb 定义在 <linux/usb.h> 头文件中,其原型如下:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
该函数接受两个参数:指向已初始化URB的指针 urb,以及内存分配标志 mem_flags。返回值为整数,0表示成功提交,负值表示失败原因。
4.2 参数含义详解
| 参数 | 类型 | 含义 | 关键说明 |
|---|---|---|---|
urb |
struct urb * |
指向待提交的URB结构体 | 必须已通过 usb_alloc_urb 分配,并完成 usb_fill_*_urb 系列函数或手动初始化 |
mem_flags |
gfp_t |
内存分配标志 | 决定URB提交过程中内核内存分配的行为,常见值见下表 |
4.2.1 mem_flags 常用标志
| 标志 | 适用上下文 | 行为说明 |
|---|---|---|
GFP_KERNEL |
进程上下文(可睡眠) | 允许阻塞等待内存,是驱动中最常用的标志 |
GFP_ATOMIC |
中断上下文、tasklet、自旋锁持有中 | 不睡眠,立即分配,失败则返回 -ENOMEM |
GFP_NOIO |
块设备I/O路径 | 禁止回写,避免I/O递归死锁 |
GFP_NOFS |
文件系统操作路径 | 禁止文件系统递归调用 |
⚠ 常见错误:在中断上下文中使用
GFP_KERNEL 会导致内核调度器异常,触发 BUG: scheduling while atomic 错误。务必根据调用上下文选择正确的 mem_flags。
4.3 返回值解析
usb_submit_urb 返回0表示成功。负值表示失败,常见错误码及其含义如下:
| 返回值 | 宏定义 | 含义 | 典型原因 |
|---|---|---|---|
0 |
— | 提交成功 | URB已加入主机控制器队列 |
-ENOMEM |
12 | 内存不足 | DMA缓冲区分配失败,或URB内部数据结构无法分配 |
-EBUSY |
16 | URB正在使用中 | URB已提交且未完成,或URB被标记为 URB_ASYNC_UNLINK 且正在取消 |
-EINVAL |
22 | 参数无效 | URB未正确初始化(如 dev、pipe、transfer_buffer 为空) |
-ENODEV |
19 | 设备不存在 | USB设备已断开连接或处于 USB_STATE_NOTATTACHED 状态 |
-EXDEV |
18 | 设备状态异常 | 设备未配置(未调用 usb_set_configuration)或接口未声明 |
-EAGAIN |
11 | 暂时无法提交 | 主机控制器资源暂时不足,可稍后重试 |
-EFBIG |
27 | 传输大小超限 | URB的 transfer_buffer_length 超过端点支持的最大包大小 |
-EPIPE |
32 | 端点停止(stall) | 端点因协议错误被硬件停止,需调用 usb_clear_halt 清除 |
-EMSGSIZE |
90 | 消息大小错误 | 批量传输的缓冲区未对齐到DMA边界 |
-ESHUTDOWN |
108 | 主机控制器已关闭 | 系统正在挂起或主机控制器驱动已卸载 |
💡 调试提示:当
usb_submit_urb 返回 -EINVAL 时,首先检查URB的 dev 指针是否有效、pipe 是否通过 usb_rcvbulkpipe 等宏正确构造、以及 transfer_buffer 是否非空。
4.4 URB提交流程与状态机
下图展示了URB从构造到提交完成的状态转换过程,以及 usb_submit_urb 在其中的位置:
4.5 典型调用示例
以下代码展示了一个批量输出URB的提交过程,包含必要的错误检查:
struct urb *urb;
int ret;
/* 1. 分配URB */
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
dev_err(&udev->dev, "URB allocation failed\n");
return -ENOMEM;
}
/* 2. 初始化URB(批量输出) */
usb_fill_bulk_urb(urb, udev,
usb_sndbulkpipe(udev, 1),
buffer, buffer_size,
bulk_write_complete, context);
/* 3. 提交URB */
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(&udev->dev,
"URB submission failed: %d (%s)\n",
ret, usb_error_string(ret));
usb_free_urb(urb);
return ret;
}
/* 4. 提交成功,URB由内核管理 */
return 0;
🔍 调试技巧:使用
usb_error_string(ret) 函数可以将错误码转换为可读字符串,便于在日志中输出。该函数定义在 <linux/usb.h> 中,是调试URB提交失败的首选工具。
4.6 提交失败常见原因速查表
| 错误码 | 最可能原因 | 检查点 |
|---|---|---|
-ENOMEM |
DMA缓冲区分配失败 | 检查 transfer_buffer 是否通过 kmalloc 或 usb_alloc_coherent 分配 |
-EBUSY |
URB重复提交 | 检查URB是否已提交且未完成,或未调用 usb_kill_urb 取消 |
-EINVAL |
URB初始化不完整 | 检查 dev、pipe、transfer_buffer、complete 回调是否设置 |
-ENODEV |
设备已断开 | 检查 udev->state 是否为 USB_STATE_CONFIGURED |
-EPIPE |
端点stall | 调用 usb_clear_halt 清除端点停止状态 |
-ESHUTDOWN |
主机控制器关闭 | 检查系统是否正在挂起或驱动是否已卸载 |
4.7 提交后的URB生命周期
成功提交后,URB进入内核调度队列,驱动不应再修改URB的以下字段,直到完成回调被调用:
urb->transfer_buffer和urb->transfer_dmaurb->transfer_buffer_lengthurb->setup_packet(控制传输)urb->number_of_packets(等时传输)
⚠ 重要规则:在URB完成回调中重新提交同一个URB时,必须先检查
urb->status。如果状态为 -ECONNRESET 或 -ENOENT,表示URB已被取消,不应再次提交。