3. Gadget API 核心数据结构:struct usb_gadget、struct usb_ep、struct usb_request
在 Linux USB Gadget 驱动框架中,struct usb_gadget、struct usb_ep 和 struct usb_request 是三个最核心的数据结构。它们分别抽象了 Gadget 设备、端点 和 USB 请求,构成了驱动与硬件交互的基石。理解这三个结构体的作用与关键字段,是编写 Gadget 功能驱动的第一步。
核心关系:一个
usb_gadget 包含多个 usb_ep(端点),每个端点通过提交 usb_request 来完成数据的收发。三者形成“设备 → 端点 → 请求”的层级关系。
3.1 struct usb_gadget:Gadget 设备抽象
struct usb_gadget 是 USB Gadget 设备在驱动层的核心表示,它封装了硬件控制器(UDC)的通用属性和操作接口。每个 UDC 硬件对应一个 usb_gadget 实例。
关键字段解析
| 字段名 | 类型 | 作用说明 |
|---|---|---|
ops |
const struct usb_gadget_ops * |
硬件操作函数集,包括 pullup、vbus_session、udc_start 等回调 |
ep0 |
struct usb_ep * |
端点 0(控制端点)的指针,用于枚举和标准请求处理 |
ep_list |
struct list_head |
链表头,挂载所有非控制端点的 usb_ep 实例 |
speed |
enum usb_device_speed |
当前协商的 USB 速度(HS/FS/SS) |
max_speed |
enum usb_device_speed |
硬件支持的最大速度 |
state |
enum usb_device_state |
USB 设备状态机(ATTACHED/POWERED/DEFAULT/ADDRESS/CONFIGURED) |
name |
const char * |
UDC 控制器名称,如 "dwc2"、"musb-hdrc" |
dev |
struct device |
内核设备模型中的 device 结构,用于电源管理和 sysfs |
sg_supported |
unsigned |
是否支持 Scatter-Gather DMA,提升大数据传输性能 |
quirk_ep_out_aligned_size |
unsigned |
某些控制器要求 OUT 端点缓冲区对齐的特殊处理标志 |
驱动开发要点:在功能驱动中,通常通过
gadget->ep0 访问控制端点,通过 gadget->ep_list 遍历所有可用端点。不要直接修改 gadget->state,它由 UDC 核心维护。
3.2 struct usb_ep:端点抽象
struct usb_ep 代表一个 USB 端点(Endpoint),是数据传输的通道。每个端点都有方向(IN/OUT)、类型(控制/批量/中断/等时)和编号。端点 0 由 UDC 核心管理,其他端点由功能驱动申请使用。
关键字段解析
| 字段名 | 类型 | 作用说明 |
|---|---|---|
ops |
const struct usb_ep_ops * |
端点操作函数集,包括 enable、disable、alloc_request、queue、dequeue 等 |
name |
const char * |
端点名称,如 "ep1in"、"ep2out" |
caps |
struct usb_ep_caps |
端点能力描述,包含 type_iso、type_bulk、type_int、dir_in、dir_out 等标志位 |
maxpacket |
unsigned |
端点支持的最大包长度(字节),由端点描述符决定 |
max_streams |
unsigned |
USB 3.0 流能力,0 表示不支持流 |
mult |
unsigned |
高带宽模式下的微帧事务数(USB 2.0 高速等时端点) |
maxburst |
unsigned |
USB 3.0 突发传输的包数量 |
driver_data |
void * |
私有数据指针,功能驱动可挂载自定义上下文 |
address |
unsigned |
端点地址(方向+编号),如 0x81 表示端点 1 IN |
端点操作流程
- 申请端点:通过
usb_ep_autoconfig()或直接遍历gadget->ep_list匹配端点能力。 - 使能端点:调用
usb_ep_enable(),传入端点描述符配置最大包长等参数。 - 分配请求:调用
usb_ep_alloc_request()创建usb_request实例。 - 提交请求:调用
usb_ep_queue()启动数据传输。 - 完成回调:请求完成后,UDC 调用
usb_request->complete回调通知驱动。 - 释放资源:传输结束后调用
usb_ep_free_request()和usb_ep_disable()。
3.3 struct usb_request:USB 请求抽象
struct usb_request 是数据传输的基本单元,封装了一次 USB 传输所需的缓冲区、长度、方向和完成状态。功能驱动通过提交请求来触发 UDC 硬件进行实际的数据收发。
关键字段解析
| 字段名 | 类型 | 作用说明 |
|---|---|---|
buf |
void * |
数据缓冲区指针,存放待发送或接收的数据 |
length |
unsigned |
缓冲区长度(字节),驱动必须正确设置 |
actual |
unsigned |
实际传输的字节数,由 UDC 在完成时更新 |
status |
int |
传输状态:0 表示成功,负值表示错误(如 -ESHUTDOWN、-EPIPE) |
complete |
void (*)(struct usb_ep *, struct usb_request *) |
完成回调函数指针,传输结束后由 UDC 调用 |
context |
void * |
回调上下文指针,驱动可挂载私有数据供回调使用 |
sg |
struct scatterlist * |
Scatter-Gather 链表指针,用于零拷贝大数据传输 |
num_sgs |
unsigned |
Scatter-Gather 链表的段数 |
no_interrupt |
unsigned |
是否允许 UDC 合并中断,1 表示不产生完成中断(批量传输优化) |
zero |
unsigned |
是否在数据末尾自动发送 ZLP(零长度包),用于批量传输边界对齐 |
short_not_ok |
unsigned |
是否允许接收短包,1 表示短包视为错误 |
dma |
dma_addr_t |
DMA 地址,由 UDC 在映射时设置,驱动不应直接修改 |
常见陷阱:
- 请求提交后,
buf和length在完成回调返回前不可修改。 - 必须检查
status字段,即使actual == length也可能发生错误(如 USB 断开)。 - 批量端点建议设置
zero = 1,确保数据边界正确。
3.4 三者协作示例:虚拟网卡发送数据
以虚拟网卡驱动发送一个网络数据包为例,展示三个结构体的协作流程:
/* 伪代码:虚拟网卡发送一个 skb 数据包 */
static int eth_send_packet(struct usb_function *f, struct sk_buff *skb)
{
struct f_eth *eth = func_to_eth(f);
struct usb_request *req;
struct usb_ep *ep_in = eth->in_ep; /* 从 usb_gadget 获取的 IN 端点 */
int ret;
/* 1. 从端点池获取一个空闲请求 */
req = usb_ep_alloc_request(ep_in, GFP_ATOMIC);
if (!req)
return -ENOMEM;
/* 2. 设置请求参数 */
req->buf = skb->data;
req->length = skb->len;
req->complete = eth_tx_complete; /* 发送完成回调 */
req->context = skb; /* 传递 skb 给回调 */
req->zero = 1; /* 自动补零长度包 */
/* 3. 提交请求到端点 */
ret = usb_ep_queue(ep_in, req, GFP_ATOMIC);
if (ret) {
usb_ep_free_request(ep_in, req);
dev_kfree_skb_any(skb);
return ret;
}
return 0;
}
/* 发送完成回调 */
static void eth_tx_complete(struct usb_ep *ep, struct usb_request *req)
{
struct sk_buff *skb = req->context;
if (req->status != 0) {
/* 处理错误 */
dev_kfree_skb_any(skb);
} else {
/* 发送成功,释放 skb */
dev_kfree_skb_any(skb);
}
/* 释放请求,归还端点 */
usb_ep_free_request(ep, req);
}
关键点总结:
usb_gadget提供设备级上下文和端点列表。usb_ep提供数据传输通道和操作接口。usb_request封装单次传输的数据和状态。- 三者通过
usb_ep_queue()和完成回调形成异步传输模型。
3.5 数据结构对比速查表
| 数据结构 | 抽象层次 | 主要职责 | 生命周期 | 由谁管理 |
|---|---|---|---|---|
usb_gadget |
设备级 | 表示整个 UDC 控制器,管理端点列表和状态 | 与 UDC 驱动共存 | UDC 核心驱动 |
usb_ep |
端点级 | 表示单个 USB 端点,提供数据传输通道 | 从使能到禁用 | 功能驱动申请/释放 |
usb_request |
请求级 | 封装一次数据传输的缓冲区、长度和状态 | 从分配到释放 | 功能驱动管理 |
学习建议:在阅读 Gadget 功能驱动源码(如
f_ecm.c、f_rndis.c)时,重点关注这三个结构体的使用模式。理解它们的关系后,你会发现所有 Gadget 驱动的数据流都遵循“申请端点 → 分配请求 → 提交请求 → 回调处理”的统一范式。