3. URB核心概念:urb结构体详解、URB生命周期、URB标志位

URB(USB Request Block)是Linux USB子系统中最核心的数据结构,它承载了主机与USB设备之间所有的数据传输请求。理解URB的结构、生命周期和标志位,是调试USB驱动、分析URB提交失败问题的前提。本节将逐一拆解这三个关键维度。

3.1 urb结构体详解

struct urb 定义在 <linux/usb.h> 中,是USB设备驱动与USB核心层交互的唯一数据载体。下面列出驱动开发中最常操作的字段:

字段 类型 说明
dev struct usb_device * 目标USB设备指针,URB提交前必须赋值
pipe unsigned int 端点管道信息,包含端点号、方向、传输类型
transfer_buffer void * 数据缓冲区指针,用于存放发送或接收的数据
transfer_buffer_length int 缓冲区长度(字节),决定本次传输的数据量
transfer_flags unsigned int URB标志位,控制URB行为(详见3.3节)
actual_length int 实际传输的字节数,在完成回调中读取
status int URB完成状态,0表示成功,负值为错误码
complete usb_complete_t 完成回调函数指针,URB处理结束时调用
context void * 回调上下文,驱动可传入自定义数据
interval int 中断/isochronous传输的轮询间隔(帧/微帧)
number_of_packets int 等时传输的分包数量
iso_frame_desc[] struct usb_iso_packet_descriptor 等时传输的帧描述数组
调试要点: 当URB提交失败时,首先检查 dev 和 pipe 是否有效。许多提交失败的根本原因是设备已断开(dev 悬空)或管道方向与端点不匹配。

下面通过SVG图展示URB结构体与USB子系统各层的关系:

struct urb 结构体与USB子系统关系 用户空间应用 USB设备驱动 struct urb dev / pipe / buffer USB核心层 主机控制器驱动 USB硬件控制器 系统调用 usb_fill_* usb_submit_urb urb_enqueue TD列表 关键字段作用 • dev:绑定设备 • pipe:指定端点 • transfer_flags:控制行为 • complete:完成回调

3.2 URB生命周期

一个URB从创建到销毁,经历以下五个阶段。理解每个阶段的状态转换,有助于定位URB提交失败的具体环节。

  1. 创建与初始化:使用 usb_alloc_urb() 分配URB,然后通过 usb_fill_control_urb()、usb_fill_bulk_urb()、usb_fill_int_urb() 等辅助函数填充URB字段。
  2. 提交:调用 usb_submit_urb() 将URB提交给USB核心层。此时URB进入待处理队列,等待主机控制器调度。
  3. 调度与传输:主机控制器驱动(HCD)将URB转换为硬件事务描述符(TD),通过DMA或PIO方式与USB设备进行数据交换。
  4. 完成回调:传输结束后(成功或失败),HCD回调USB核心层,核心层调用URB的 complete 回调函数。驱动在回调中检查 urb->status 和 urb->actual_length。
  5. 释放:驱动调用 usb_free_urb() 释放URB内存。如果URB需要重用,可以调用 usb_unlink_urb() 或 usb_kill_urb() 取消未完成的URB后再重新提交。
常见陷阱: 在中断上下文或持有自旋锁时调用 usb_submit_urb() 可能导致调度死锁。URB提交失败的错误码 -ENOMEM 通常意味着内存不足或DMA缓冲区分配失败,此时应检查 transfer_buffer 是否使用 kmalloc 或DMA一致性内存。

下面用SVG流程图展示URB的完整生命周期:

URB生命周期流程图 1. 创建与初始化 2. 提交 usb_submit_urb 3. HCD调度与传输 4. 完成回调 complete 5. 释放 usb_free_urb 各阶段关键操作 ① usb_alloc_urb + usb_fill_* ② 检查返回值,非0即失败 ③ 硬件传输,不可中断 ④ 检查 status 和 actual_length ⑤ 释放内存,避免泄漏 提交失败 -EINVAL 返回错误

3.3 URB标志位

urb->transfer_flags 控制URB在提交和传输过程中的行为。标志位通过位掩码组合,常用的标志位如下:

标志位 值 作用
URB_NO_TRANSFER_DMA_MAP 0x0001 告知USB核心不要对 transfer_dma 进行DMA映射,驱动自行管理DMA缓冲区
URB_NO_SETUP_DMA_MAP 0x0002 控制传输中,告知核心不要对setup packet的DMA地址进行映射
URB_ZERO_PACKET 0x0040 批量传输中,如果数据长度是端点最大包长的整数倍,自动发送一个零长度包
URB_NO_INTERRUPT 0x0080 URB完成时不触发中断,适用于轮询模式或高吞吐场景
URB_FREE_BUFFER 0x0100 URB完成时自动释放 transfer_buffer,驱动无需手动释放
URB_DIR_IN 0x0200 数据传输方向为设备到主机(读)
URB_DIR_OUT 0x0400 数据传输方向为主机到设备(写)
URB_ISO_ASAP 0x1000 等时传输中,要求URB尽快被调度,而非等待指定帧号
调试技巧: 当URB提交返回 -EINVAL 时,检查 transfer_flags 是否设置了 URB_DIR_IN 或 URB_DIR_OUT。如果未设置方向标志,USB核心无法确定数据传输方向,会直接拒绝提交。

下面用SVG图展示标志位如何影响URB的传输行为:

URB标志位对传输行为的影响 struct urb transfer_flags URB_ZERO_PACKET URB_NO_INTERRUPT URB_FREE_BUFFER URB_DIR_IN / OUT URB_ISO_ASAP URB_NO_TRANSFER_DMA_MAP 标志位组合示例 批量写:URB_DIR_OUT | URB_ZERO_PACKET 中断读:URB_DIR_IN | URB_NO_INTERRUPT(高频率轮询)

3.4 典型URB初始化代码示例

下面展示一个批量端点URB的初始化与提交流程,包含标志位的正确设置:

#include <linux/usb.h>

static struct urb *bulk_urb;
static struct usb_device *udev;
static unsigned char *buf;

/* 批量URB初始化函数 */
int setup_bulk_urb(struct usb_device *dev, unsigned int pipe)
{
    int ret;

    /* 1. 分配DMA安全缓冲区 */
    buf = kmalloc(512, GFP_KERNEL);
    if (!buf)
        return -ENOMEM;

    /* 2. 分配URB */
    bulk_urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!bulk_urb) {
        kfree(buf);
        return -ENOMEM;
    }

    /* 3. 填充URB字段 */
    usb_fill_bulk_urb(bulk_urb, dev, pipe,
                      buf, 512,
                      bulk_complete_callback,  /* 完成回调 */
                      NULL);                   /* 上下文 */

    /* 4. 设置标志位:批量写 + 自动零包 */
    bulk_urb->transfer_flags |= URB_DIR_OUT | URB_ZERO_PACKET;

    /* 5. 提交URB */
    ret = usb_submit_urb(bulk_urb, GFP_KERNEL);
    if (ret) {
        pr_err("URB submit failed: %d\n", ret);
        usb_free_urb(bulk_urb);
        kfree(buf);
        return ret;
    }

    return 0;
}

/* 完成回调函数 */
static void bulk_complete_callback(struct urb *urb)
{
    if (urb->status == 0) {
        pr_info("Bulk transfer OK, actual=%d\n", urb->actual_length);
    } else {
        pr_err("Bulk transfer failed, status=%d\n", urb->status);
    }
}
调试建议: 在完成回调中打印 urb->status 和 urb->actual_length,是定位URB传输失败最直接的手段。常见状态码:0(成功)、-ENOENT(URB被取消)、-ECONNRESET(设备断开)、-ESHUTDOWN(主机控制器挂起)。

通过本节的学习,你应该掌握了URB结构体的关键字段、URB从创建到释放的完整生命周期,以及标志位如何控制传输行为。这些知识是后续分析URB提交失败错误码和调试USB驱动的基础。