4. Gadget API 详解:核心结构体
USB Gadget 驱动开发的核心在于理解三个基础结构体:usb_gadget(Gadget 实例)、usb_ep(端点抽象)和 usb_request(请求描述)。它们是 UDC(USB Device Controller)驱动与 Gadget 功能驱动之间的桥梁,贯穿数据收发的整个生命周期。
面试核心提示:面试官常通过这三个结构体的关系考察候选人对 USB 设备侧数据流、端点生命周期管理以及 DMA 缓冲区处理的理解深度。
4.1 usb_gadget:Gadget 设备实例
usb_gadget 结构体代表一个 USB 设备控制器(UDC)实例,是 Gadget 驱动框架的顶层抽象。它包含了设备控制器的硬件能力、当前状态、以及指向各功能驱动的回调接口。
| 字段 | 类型 | 说明 |
|---|---|---|
ops |
const struct usb_gadget_ops * |
UDC 硬件操作函数集(如 udc_start、pullup) |
ep0 |
struct usb_ep * |
端点 0(控制端点)的抽象 |
speed |
enum usb_device_speed |
当前协商的 USB 速度(HS/FS/SS) |
max_speed |
enum usb_device_speed |
UDC 硬件支持的最大速度 |
state |
enum usb_device_state |
USB 设备状态机(如 USB_STATE_CONFIGURED) |
name |
const char * |
UDC 控制器名称(如 "dwc3") |
sg_supported |
unsigned |
是否支持 Scatter-Gather DMA |
quirk_ep_out_aligned_size |
unsigned |
端点 OUT 传输是否需要对齐 |
面试考点:注意
usb_gadget_ops 中的 udc_start 和 udc_stop 回调,它们是 UDC 驱动注册和注销 Gadget 功能驱动的入口。面试中常问:“当主机发送 SET_CONFIGURATION 时,Gadget 框架如何通过 usb_gadget 激活对应的功能驱动?”
usb_gadget 与 UDC 的关系
下图展示了 usb_gadget 在 UDC 驱动与 Gadget 功能驱动之间的桥梁作用:
4.2 usb_ep:端点抽象
usb_ep 结构体是对 UDC 硬件端点的软件抽象。每个物理端点(包括端点 0)在驱动中对应一个 usb_ep 实例。功能驱动通过 usb_ep 提供的 API 发起数据传输。
| 字段 | 类型 | 说明 |
|---|---|---|
name |
const char * |
端点名称(如 "ep1in"、"ep2out") |
ops |
const struct usb_ep_ops * |
端点操作函数集(enable、disable、queue 等) |
maxpacket |
unsigned |
端点支持的最大包大小(由描述符和速度决定) |
max_streams |
unsigned |
SuperSpeed 下支持的流数量(USB 3.0 流能力) |
mult |
unsigned |
高带宽模式下的微帧内事务数(HS 高带宽 ISO) |
maxburst |
unsigned |
SuperSpeed 突发传输长度 |
driver_data |
void * |
功能驱动私有数据指针 |
address |
unsigned |
端点地址(方向 + 编号,如 0x81 表示 EP1 IN) |
端点操作函数集 usb_ep_ops
每个 UDC 驱动必须实现 usb_ep_ops 中的核心回调,功能驱动通过它们控制端点:
- enable / disable:激活或停用端点,通常在配置描述符解析后调用。
- queue:将一个
usb_request提交到端点传输队列,这是数据收发的核心入口。 - dequeue:从队列中移除一个未完成的请求。
- alloc_request / free_request:分配/释放与端点关联的
usb_request结构体。 - set_halt / clear_halt:设置/清除端点的 STALL 条件,用于协议错误处理。
- set_wedge:设置“楔入”状态,使端点持续返回 STALL 直到收到 CLEAR_FEATURE。
常见面试陷阱:面试官可能会问:“为什么
usb_ep_ops 中的 queue 回调是异步的?” 答案:USB 传输由硬件控制器完成,queue 只是将请求加入硬件 DMA 队列,传输完成时通过中断回调通知,因此不能阻塞等待。
端点生命周期与状态转换
4.3 usb_request:数据传输请求
usb_request 是 USB 数据传输的最小工作单元。功能驱动通过分配 usb_request、填充数据缓冲区、设置回调函数,然后调用 usb_ep_queue() 将请求提交给 UDC 硬件执行。
| 字段 | 类型 | 说明 |
|---|---|---|
buf |
void * |
数据缓冲区指针(DMA 安全地址) |
length |
unsigned |
请求的缓冲区长度(字节) |
actual |
unsigned |
实际传输的字节数(由 UDC 在完成时更新) |
status |
int |
传输状态(0 成功,负值为错误码) |
complete |
void (*)(struct usb_ep *, struct usb_request *) |
传输完成回调函数 |
context |
void * |
回调上下文指针(功能驱动私有数据) |
sg_list |
struct scatterlist * |
Scatter-Gather 链表(用于零拷贝 DMA) |
num_sgs |
unsigned |
SG 链表中的条目数 |
no_interrupt |
unsigned |
是否禁止传输完成中断(用于批量短包优化) |
short_not_ok |
unsigned |
是否不允许短包(批量传输中用于检测错误) |
zero |
unsigned |
是否需要发送 ZLP(零长度包)以结束传输 |
请求生命周期与数据流
4.4 三个结构体的协作关系
在 Gadget 驱动中,usb_gadget、usb_ep 和 usb_request 形成清晰的层级协作关系:
- usb_gadget 作为顶层容器,持有 ep0 引用和 UDC 操作函数集。
- 功能驱动通过
usb_gadget的ops枚举可用端点,获取usb_ep实例。 - 功能驱动在
usb_ep上分配usb_request,填充数据后调用usb_ep_queue()。 - UDC 硬件完成传输后,调用
usb_request的complete回调,通知功能驱动处理结果。
面试高频题:“请描述从功能驱动发送一个 USB 数据包到主机侧接收的完整路径。” 标准回答应包含:功能驱动 → usb_ep_queue() → UDC 驱动 → DMA 描述符 → USB 总线 → 主机控制器。其中
usb_request 的 buf 和 length 是 DMA 传输的直接参数。
协作关系总览图
4.5 面试避坑指南
| 常见错误 | 正确做法 | 面试考察点 |
|---|---|---|
在中断上下文使用 GFP_KERNEL 分配请求 |
使用 GFP_ATOMIC |
内存分配标志与上下文匹配 |
复用 usb_request 时未重置 actual 和 status |
在重新提交前清零或重新初始化 | 请求生命周期管理 |
在 complete 回调中直接调用 usb_ep_queue 提交同一请求 |
确保不会形成递归或死锁,必要时使用工作队列 | 并发与同步机制 |
忽略 short_not_ok 和 zero 标志 |
批量传输中正确设置以处理短包和 ZLP | USB 协议细节理解 |
使用栈地址作为 buf |
使用 kmalloc 或 DMA 池分配 |
DMA 缓冲区要求 |
面试加分项:能够解释
usb_request 中的 sg_list 如何与 usb_gadget 的 sg_supported 配合实现零拷贝传输。当 UDC 支持 Scatter-Gather 时,功能驱动可以避免数据拷贝,直接传递分散的物理页给硬件。