Linux USB Gadget 驱动开发实战:从 DTS 配置到功能注册的完整指南
1. 嵌入式 USB 设备开发概述
在嵌入式系统开发中,USB Gadget 驱动扮演着关键角色,它允许设备作为 USB 从设备(如存储设备、串口设备等)与主机通信。与传统的 USB 主机驱动不同,Gadget 驱动需要开发者同时关注硬件控制器配置和功能实现两个层面。
现代 Linux 内核为 USB Gadget 开发提供了两种主要模式:
- 传统模式(Legacy):需要重新编译内核来配置功能
- ConfigFS 模式:通过文件系统动态配置,无需重新编译内核
对于嵌入式开发者而言,理解以下核心概念至关重要:
- UDC (USB Device Controller):硬件控制器,如 DWC2
- Gadget API:内核提供的端点分配和数据传输接口
- Function 驱动:实现具体设备功能(如 ACM、MSC)
// 典型 USB Gadget 驱动模块初始化示例 #include <linux/module.h> #include <linux/usb/composite.h> static struct usb_composite_driver my_gadget_driver = { .name = "my_udc_gadget", .dev = &device_desc, .strings = dev_strings, .max_speed = USB_SPEED_HIGH, }; module_usb_composite_driver(my_gadget_driver);2. 硬件准备与 DTS 配置
2.1 UDC 硬件选型
常见的嵌入式 UDC 控制器包括:
- DWC2:Synopsys DesignWare 核心,广泛用于 SoC
- MUSB:TI 的 Dual-Role 控制器
- UDC 核心:如 fsl-usb2-udc
在选择硬件时需要考虑:
- 支持的 USB 版本(1.1/2.0/3.0)
- 端点数量和类型
- DMA 支持情况
- 供电模式(自供电/总线供电)
2.2 DTS 配置详解
完整的 DWC2 控制器配置示例:
usbd: usb@10200000 { compatible = "snps,dwc2"; reg = <0x10200000 0x1000>; interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>; clocks = <&ccu CLK_USBD>, <&ccu CLK_USB_PHY0>; clock-names = "otg"; resets = <&rst RESET_USBD>, <&rst RESET_USBPHY0>; reset-names = "dwc2", "dwc2-ecc"; /* FIFO 大小配置 */ g-rx-fifo-size = <380>; g-np-tx-fifo-size = <600>; g-tx-fifo-size = <8 8>; /* 设备模式配置 */ dr_mode = "peripheral"; status = "okay"; };关键参数说明:
| 参数 | 描述 | 推荐值 |
|---|---|---|
| g-rx-fifo-size | 接收 FIFO 大小 | 根据端点数量调整 |
| g-np-tx-fifo-size | 非周期传输 FIFO | 最大包大小的 2-3 倍 |
| g-tx-fifo-size | 各端点 FIFO | 根据实际需求分配 |
| dr_mode | 工作模式 | peripheral/otg/host |
2.3 硬件初始化流程
UDC 驱动的 probe 函数主要完成以下工作:
- 时钟和电源管理初始化
- 寄存器映射和中断配置
- 端点资源分配
- 注册 Gadget 设备到 USB 核心
// 简化的 probe 函数流程 static int dwc2_driver_probe(struct platform_device *pdev) { struct dwc2_hsotg *hsotg; /* 1. 分配和初始化硬件结构体 */ hsotg = devm_kzalloc(&pdev->dev, sizeof(*hsotg), GFP_KERNEL); /* 2. 获取资源(寄存器、中断等) */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); hsotg->regs = devm_ioremap_resource(&pdev->dev, res); /* 3. 初始化 Gadget 设备 */ hsotg->gadget.ops = &dwc2_hsotg_gadget_ops; hsotg->gadget.max_speed = USB_SPEED_HIGH; /* 4. 注册 UDC */ ret = usb_add_gadget_udc(&pdev->dev, &hsotg->gadget); return 0; }3. ConfigFS 动态配置实战
3.1 ConfigFS 基础配置
通过 ConfigFS 配置 USB Gadget 的典型流程:
# 挂载 configfs mount -t configfs none /sys/kernel/config # 创建 Gadget 实例 cd /sys/kernel/config/usb_gadget mkdir g1 cd g1 # 设置 VID/PID echo 0x1d6b > idVendor echo 0x0104 > idProduct # 配置字符串描述符 mkdir strings/0x409 echo "0123456789" > strings/0x409/serialnumber echo "My Company" > strings/0x409/manufacturer echo "Test Device" > strings/0x409/product3.2 ACM Function 配置示例
创建 ACM (串口) 功能并绑定到配置:
# 创建 ACM 功能 mkdir functions/acm.usb0 # 创建配置 mkdir configs/c.1 mkdir configs/c.1/strings/0x409 echo "CDC ACM Config" > configs/c.1/strings/0x409/configuration # 绑定功能到配置 ln -s functions/acm.usb0 configs/c.1 # 激活 UDC ls /sys/class/udc/ > UDC3.3 复合设备配置
创建包含多个功能的复合设备:
# 添加 Mass Storage 功能 mkdir functions/mass_storage.usb0 echo /dev/mmcblk0p1 > functions/mass_storage.usb0/lun.0/file # 添加 HID 功能 mkdir functions/hid.usb0 echo 1 > functions/hid.usb0/protocol echo 1 > functions/hid.usb0/subclass echo 8 > functions/hid.usb0/report_length echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.usb0/report_desc # 绑定所有功能 ln -s functions/mass_storage.usb0 configs/c.1 ln -s functions/hid.usb0 configs/c.14. 自定义 Function 驱动开发
4.1 Function 驱动框架
开发自定义 Function 驱动的基本结构:
#include <linux/usb/composite.h> struct myfunc_data { struct usb_function function; struct usb_ep *ep_in; struct usb_ep *ep_out; /* 私有数据 */ }; static struct usb_interface_descriptor myfunc_intf_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_VENDOR_SPEC, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, }; static struct usb_endpoint_descriptor myfunc_fs_ep_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = cpu_to_le16(64), }; /* 其他描述符... */ static int myfunc_bind(struct usb_configuration *c, struct usb_function *f) { struct myfunc_data *data = func_to_myfunc(f); struct usb_composite_dev *cdev = c->cdev; /* 分配端点 */ >static void myfunc_complete_in(struct usb_ep *ep, struct usb_request *req) { /* IN 端点传输完成处理 */ if (req->status != 0) printk(KERN_ERR "IN transfer error: %d\n", req->status); kfree(req->buf); usb_ep_free_request(ep, req); } static int myfunc_send_data(struct myfunc_data *data, void *buf, size_t len) { struct usb_request *req; int ret; req = usb_ep_alloc_request(data->ep_in, GFP_KERNEL); req->buf = kmemdup(buf, len, GFP_KERNEL); req->length = len; req->complete = myfunc_complete_in; ret = usb_ep_queue(data->ep_in, req, GFP_KERNEL); if (ret < 0) { kfree(req->buf); usb_ep_free_request(data->ep_in, req); return ret; } return 0; } static void myfunc_complete_out(struct usb_ep *ep, struct usb_request *req) { /* 处理接收到的数据 */ process_received_data(req->buf, req->actual); /* 重新提交请求以接收新数据 */ usb_ep_queue(ep, req, GFP_KERNEL); } static int myfunc_enable_out(struct myfunc_data *data) { struct usb_request *req; int i; /* 预分配多个 OUT 请求 */ for (i = 0; i < 5; i++) { req = usb_ep_alloc_request(data->ep_out, GFP_KERNEL); req->buf = kmalloc(512, GFP_KERNEL); req->length = 512; req->complete = myfunc_complete_out; if (usb_ep_queue(data->ep_out, req, GFP_KERNEL) < 0) { kfree(req->buf); usb_ep_free_request(data->ep_out, req); return -EIO; } } return 0; }5. 调试与性能优化
5.1 常用调试工具
USB Gadget 开发中的关键调试命令:
| 命令 | 用途 | 示例 |
|---|---|---|
lsusb -v | 查看 USB 设备详情 | lsusb -v -d 1d6b: |
dmesg | 查看内核日志 | dmesg | grep dwc2 |
cat /sys/kernel/debug/usb/devices | 查看 USB 设备树 | 需要 root 权限 |
usbmon | USB 流量监控 | cat /sys/kernel/debug/usb/usbmon/0u |
5.2 性能优化技巧
FIFO 大小优化:
- 根据实际数据传输需求调整 DTS 中的 FIFO 参数
- 大块数据传输使用更大的 FIFO
- 实时数据使用专用周期传输 FIFO
DMA 配置:
/* 在 probe 函数中启用 DMA */ hsotg->params.dma_enable = 1; hsotg->params.dma_desc_enable = 1;端点分配策略:
- 高带宽端点优先分配
- 实时端点使用专用 FIFO
- 批量传输端点共享 FIFO
电源管理:
/* 启用远程唤醒 */ gadget->remote_wakeup = 1;
5.3 常见问题排查
问题1:设备无法被主机识别
- 检查 DTS 配置是否正确
- 验证 UDC 驱动是否成功加载
- 使用示波器检查 USB 信号质量
问题2:数据传输不稳定
- 检查 FIFO 大小是否足够
- 验证 DMA 配置是否正确
- 调整端点最大包大小
问题3:高负载下丢包
- 增加预分配的 USB 请求数量
- 优化中断处理延迟
- 考虑使用 ISO 端点进行实时传输
在实际项目中,我们曾遇到一个案例:当同时启用 MSC 和 HID 功能时,HID 输入会出现延迟。通过分析发现是 MSC 的大块传输占用了过多带宽,解决方案是为 HID 端点分配专用 FIFO 并提高其优先级。