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子系统各层的关系:
3.2 URB生命周期
一个URB从创建到销毁,经历以下五个阶段。理解每个阶段的状态转换,有助于定位URB提交失败的具体环节。
- 创建与初始化:使用
usb_alloc_urb()分配URB,然后通过usb_fill_control_urb()、usb_fill_bulk_urb()、usb_fill_int_urb()等辅助函数填充URB字段。 - 提交:调用
usb_submit_urb()将URB提交给USB核心层。此时URB进入待处理队列,等待主机控制器调度。 - 调度与传输:主机控制器驱动(HCD)将URB转换为硬件事务描述符(TD),通过DMA或PIO方式与USB设备进行数据交换。
- 完成回调:传输结束后(成功或失败),HCD回调USB核心层,核心层调用URB的
complete回调函数。驱动在回调中检查urb->status和urb->actual_length。 - 释放:驱动调用
usb_free_urb()释放URB内存。如果URB需要重用,可以调用usb_unlink_urb()或usb_kill_urb()取消未完成的URB后再重新提交。
常见陷阱: 在中断上下文或持有自旋锁时调用
usb_submit_urb() 可能导致调度死锁。URB提交失败的错误码 -ENOMEM 通常意味着内存不足或DMA缓冲区分配失败,此时应检查 transfer_buffer 是否使用 kmalloc 或DMA一致性内存。
下面用SVG流程图展示URB的完整生命周期:
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的传输行为:
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驱动的基础。