2. Linux USB Gadget框架
Linux USB Gadget框架是嵌入式设备实现USB外设功能的核心软件层。它位于内核中,向上为驱动开发者提供统一的编程接口,向下通过UDC(USB Device Controller)驱动与硬件交互。理解其软件架构与核心数据结构,是进行Gadget驱动移植适配的基础。
2.1 软件架构分层
USB Gadget框架采用分层设计,从硬件到用户空间共分为四层,各层职责清晰,便于移植与扩展。
架构核心原则: 上层功能驱动(如Ethernet、Mass Storage)不直接操作硬件寄存器,而是通过Gadget API与UDC驱动交互,从而实现硬件无关性。
各层之间的交互流程如下:
- 用户空间通过
configfs或gadgetfs配置Gadget功能。 - 功能驱动注册到Gadget API,绑定端点并处理数据传输。
- Gadget API 作为中间层,管理端点生命周期和请求队列。
- UDC驱动 实现具体的硬件操作,响应中断并完成数据传输。
2.2 核心数据结构
Gadget框架的核心抽象围绕三个数据结构展开:usb_gadget、usb_ep 和 usb_request。它们分别代表USB设备控制器、端点和数据传输请求。
2.2.1 usb_gadget:设备控制器抽象
usb_gadget 结构体代表一个USB设备控制器实例,是Gadget驱动与UDC硬件之间的桥梁。它包含了控制器的能力描述、状态信息和操作函数指针。
// include/linux/usb/gadget.h
struct usb_gadget {
struct usb_gadget_driver *driver; // 当前绑定的功能驱动
struct usb_ep *ep0; // 控制端点0
struct list_head ep_list; // 所有可用端点的链表
enum usb_device_speed speed; // 当前连接速度 (USB_SPEED_HIGH等)
unsigned is_otg:1; // 是否支持OTG
unsigned is_a_peripheral:1; // 是否作为A设备
unsigned b_hnp_enable:1; // HNP使能
unsigned a_hnp_support:1; // A设备HNP支持
unsigned a_alt_hnp_support:1;
const struct usb_gadget_ops *ops; // 硬件操作函数集
struct device *dev; // 内核设备模型
// ... 其他成员
};
移植要点: 在移植UDC驱动时,需要实现
struct usb_gadget_ops 中的回调函数,如 udc_start()、udc_stop()、pullup() 等。这些函数直接操作硬件寄存器。
2.2.2 usb_ep:端点抽象
usb_ep 结构体抽象了USB协议中的端点概念。每个端点都有方向、类型、最大包长等属性。Gadget功能驱动通过 usb_ep 来配置和传输数据。
// include/linux/usb/gadget.h
struct usb_ep {
void *driver_data; // 功能驱动的私有数据
const char *name; // 端点名称 (如 "ep1in-bulk")
const struct usb_ep_ops *ops; // 端点操作函数集
struct list_head ep_list; // 链表节点
unsigned maxpacket:16; // 最大包长
unsigned max_streams:16;
unsigned mult:2;
unsigned maxburst:5;
u8 address; // 端点地址 (方向+编号)
struct usb_endpoint_descriptor *desc; // 端点描述符
};
常见移植陷阱: 某些UDC硬件不支持所有端点类型(如Isochronous)。在
ep_ops->enable() 回调中,必须检查端点描述符的 bmAttributes 字段,若不支持则返回 -EINVAL。
2.2.3 usb_request:数据传输请求
usb_request 是数据传输的基本单元。功能驱动通过分配并提交 usb_request 到端点,来发起一次数据收发操作。UDC驱动在完成传输后,会调用完成回调函数通知上层。
// include/linux/usb/gadget.h
struct usb_request {
void *buf; // 数据缓冲区
unsigned length; // 缓冲区长度
dma_addr_t dma; // DMA地址 (若使用DMA)
unsigned no_interrupt:1; // 完成时不触发中断
unsigned zero:1; // 短包时发送ZLP
unsigned short_not_ok:1; // 不允许短包
void (*complete)(struct usb_ep *ep,
struct usb_request *req); // 完成回调
void *context; // 回调上下文
struct list_head list; // 链表节点
int status; // 传输状态 (0成功, 负值错误码)
unsigned actual; // 实际传输字节数
};
2.3 核心数据结构关系
三个核心数据结构在运行时形成明确的层级关系:一个 usb_gadget 包含多个 usb_ep,每个 usb_ep 可以挂载多个 usb_request 请求。
2.4 关键操作函数集
每个核心数据结构都关联一个操作函数集(ops),这些函数是驱动移植时需要重点实现的接口。
| 数据结构 | 操作函数集 | 关键回调 | 说明 |
|---|---|---|---|
| usb_gadget | usb_gadget_ops |
udc_start, udc_stop, pullup, get_frame |
控制UDC的启动、停止、D+上拉和帧号获取 |
| usb_ep | usb_ep_ops |
enable, disable, alloc_request, free_request, queue, dequeue |
管理端点的使能、禁用和请求队列 |
| usb_request | 无独立ops | complete (回调函数指针) |
传输完成后的通知机制,由功能驱动实现 |
移植优先级: 在适配新UDC硬件时,首先实现
usb_gadget_ops 中的 udc_start 和 pullup,然后实现 usb_ep_ops 中的 enable 和 queue。这两个函数是数据传输的基础。
2.5 数据传输流程
一次典型的USB数据传输流程如下:
- 端点使能: 功能驱动调用
usb_ep_ops->enable(),配置端点类型和最大包长。 - 请求分配: 调用
usb_ep_ops->alloc_request()分配usb_request结构体。 - 请求提交: 填充
buf、length、complete后,调用usb_ep_ops->queue()将请求加入硬件队列。 - 硬件传输: UDC硬件自动处理数据收发,完成后触发中断。
- 完成回调: UDC驱动在中断处理中调用
usb_request->complete(),通知功能驱动传输结束。
// 典型的数据发送流程 (伪代码)
static void my_function_send(struct usb_ep *ep, const char *data, int len)
{
struct usb_request *req;
// 1. 分配请求
req = usb_ep_alloc_request(ep, GFP_ATOMIC);
if (!req)
return;
// 2. 填充请求
req->buf = kmemdup(data, len, GFP_ATOMIC);
req->length = len;
req->complete = my_complete_callback;
req->context = my_priv_data;
// 3. 提交请求到端点
ret = usb_ep_queue(ep, req, GFP_ATOMIC);
if (ret) {
usb_ep_free_request(ep, req);
kfree(req->buf);
}
}
// 4. 完成回调
static void my_complete_callback(struct usb_ep *ep, struct usb_request *req)
{
if (req->status == 0) {
// 传输成功,处理 req->actual 实际字节数
pr_info("Sent %d bytes successfully\n", req->actual);
} else {
// 传输失败,处理错误
pr_err("Transfer failed: %d\n", req->status);
}
kfree(req->buf);
usb_ep_free_request(ep, req);
}
性能优化提示: 在高速传输场景(如USB Video Class)中,建议使用DMA缓冲区。通过
usb_ep_alloc_request() 分配请求后,设置 req->dma 字段并调用 usb_gadget_map_request() 进行DMA映射,可显著降低CPU占用。