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 功能驱动之间的桥梁作用:

usb_gadget 结构体关系图 usb_gadget 结构体关系图 UDC 硬件 (DWC3 / Chipidea / …) usb_gadget ops → udc_start/stop ep0 → 控制端点 Gadget 功能驱动 (g_serial / g_ether / …) 关键说明: • usb_gadget 由 UDC 驱动在 probe 阶段创建并注册到 Gadget 框架。 • 功能驱动通过 usb_gadget_probe_driver() 绑定到 usb_gadget 实例。 • ep0 是唯一在 usb_gadget 中直接引用的端点,用于控制传输。 • speed 字段在总线复位或枚举完成后更新,功能驱动据此调整传输策略。

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 队列,传输完成时通过中断回调通知,因此不能阻塞等待。

端点生命周期与状态转换

usb_ep 端点状态转换图 usb_ep 端点状态转换图 未配置 已启用 STALL enable() set_halt() clear_halt() enable() 状态说明: • 未配置:端点未启用,不能提交请求。通常在 SET_CONFIGURATION 后由功能驱动调用 enable()。 • STALL:端点因协议错误进入停止状态,需主机发送 CLEAR_FEATURE 才能恢复。

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(零长度包)以结束传输

请求生命周期与数据流

usb_request 生命周期与数据流 usb_request 生命周期与数据流 1. 分配请求 usb_ep_alloc_request() 2. 填充数据 buf / length / complete 3. 提交队列 usb_ep_queue() 4. 完成回调 complete() 被调用 数据流关键点: • 缓冲区 buf 必须使用 kmalloc 或 DMA 安全内存,不能使用栈地址。 • actual 在回调中读取,表示硬件实际传输的字节数(可能小于 length)。 • status 为 0 表示成功;-ECONNRESET 表示端点被复位;-ESHUTDOWN 表示设备断开。 典型使用模式: __COURSEGEN_PROTECTED_BLOCK_0__

4.4 三个结构体的协作关系

在 Gadget 驱动中,usb_gadget、usb_ep 和 usb_request 形成清晰的层级协作关系:

  1. usb_gadget 作为顶层容器,持有 ep0 引用和 UDC 操作函数集。
  2. 功能驱动通过 usb_gadget 的 ops 枚举可用端点,获取 usb_ep 实例。
  3. 功能驱动在 usb_ep 上分配 usb_request,填充数据后调用 usb_ep_queue()。
  4. UDC 硬件完成传输后,调用 usb_request 的 complete 回调,通知功能驱动处理结果。
面试高频题:“请描述从功能驱动发送一个 USB 数据包到主机侧接收的完整路径。” 标准回答应包含:功能驱动 → usb_ep_queue() → UDC 驱动 → DMA 描述符 → USB 总线 → 主机控制器。其中 usb_request 的 buf 和 length 是 DMA 传输的直接参数。

协作关系总览图

三个核心结构体协作关系 usb_gadget / usb_ep / usb_request 协作关系 usb_gadget ep0 | ops | speed state | name usb_ep[] ep1in | ep2out | … ops → queue/enable usb_request buf | length | actual complete | context 枚举端点 分配/提交 协作流程: 1. 功能驱动通过 usb_gadget 获取端点列表,找到目标 usb_ep。 2. 在 usb_ep 上调用 usb_ep_alloc_request() 分配 usb_request。 3. 填充 usb_request 后调用 usb_ep_queue(),UDC 驱动执行 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 时,功能驱动可以避免数据拷贝,直接传递分散的物理页给硬件。