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 在其中的位置:

URB提交流程与状态机 usb_alloc_urb usb_fill_*_urb usb_submit_urb 返回 0 (成功) 返回 负值 (失败) URB完成回调 错误处理 / 重试 说明:usb_submit_urb 是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_dma
  • urb->transfer_buffer_length
  • urb->setup_packet(控制传输)
  • urb->number_of_packets(等时传输)
⚠ 重要规则:在URB完成回调中重新提交同一个URB时,必须先检查 urb->status。如果状态为 -ECONNRESET 或 -ENOENT,表示URB已被取消,不应再次提交。