Gadget驱动基本概念:UDC、功能驱动、复合设备

好,我们正式开始接触USB Gadget驱动的核心骨架了。这一章,咱们来聊聊几个最基础的概念——UDC、功能驱动,还有复合设备。

很多初学者一开始容易被这些名词搞晕。其实没那么复杂。

我个人习惯把Gadget驱动想象成一个“万能设备模拟器”。你的硬件板子(比如一个开发板)插到电脑上,电脑想识别成什么?是U盘?是串口?还是网卡?Gadget驱动来干这个事。

核心要点:

Gadget驱动体系由三部分组成:底层硬件抽象层(UDC)、中间功能驱动层、上层配置/布局层。这三层各司其职,组合起来就能让设备变身。

UDC层:底层管家

UDC,全称USB Device Controller。说白了,就是控制你板子上那个USB硬件IP的驱动层。

UDC负责最底层的工作:

  • 检测USB总线事件:比如插入、拔出、复位
  • 管理端点:分配端点、处理中断传输
  • 收发数据:通过DMA或PIO读写USB包
  • 控制链路状态:比如进入暂停模式

嗯,这里要注意。UDC是平台相关的。不同的SoC,有不同的UDC实现。我在项目中遇到过,某款芯片的UDC驱动只支持8个端点,但我们的复合设备需要12个。结果呢?只能砍掉一些功能,重新设计设备布局。

小提示:

Linux内核中,UDC驱动通常放在drivers/usb/gadget/udc/目录下。常见的实现有dwc2、dwc3、chipidea等。

Function驱动:上层逻辑

功能驱动(Function Driver),就是实际定义设备行为的模块。

比如你想让设备变成一个大容量存储设备,就加载g_mass_storage;想要一个串口,就用g_serial;想要一个网络适配器,就用g_ether。

每个Function驱动都实现了特定的USB类协议。它知道:

  • 怎么响应类请求
  • 端点怎么配置
  • 数据传输格式是什么

举个例子,g_mass_storage驱动内部实现了SCSI命令集。当主机发来一个READ(10)命令,驱动就能从后端存储设备读取数据,再通过USB发回去。

注意坑点:

Function驱动不直接操作硬件。它只和UDC层交互。我曾经以为能在Function驱动里直接访问DMA控制器,结果死机了两次才明白——所有硬件操作必须通过UDC提供的接口完成。

设备模型与复合设备

一个Gadget设备可以只包含一个function,比如最简单的“U盘模式”。但更多时候,我们需要同时模拟多种功能。这就是复合设备的用武之地。

复合设备,说白了就是在一个USB端口上,同时提供多个function。比如:

  • U盘 + 串口 + RNDIS网卡
  • 多功能打印机(扫描+打印+传真)
  • 开发板调试利器(ADB + Mass Storage + Serial)

怎么做到的?

内核有一个配置(Configuration)的概念。一个配置可以包含多个接口(Interface),每个接口对应一个function。主机通过选择不同的配置,决定启用哪些function。

组件 职责 例子
UDC驱动 硬件抽象层,处理底层USB协议 dwc3.ko, chipidea.ko
Function驱动 实现具体USB类功能 usb_f_mass_storage.ko, usb_f_serial.ko
配置文件 组合多个function,定义描述符 configfs 或 gadgetfs

我个人习惯用configfs来创建复合设备。它灵活,不需要重新编译内核。比如下面这个例子,创建一个“U盘+串口”的复合设备流程:

# 1. 创建Gadget实例
mkdir /sys/kernel/config/usb_gadget/g1
cd /sys/kernel/config/usb_gadget/g1

# 2. 设置VID/PID
echo 0x1d6b > idVendor
echo 0x0104 > idProduct

# 3. 创建字符串描述符
mkdir strings/0x409
echo "MyCompany" > strings/0x409/manufacturer
echo "MyGadget" > strings/0x409/product

# 4. 创建配置
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "Config 1: MS+Serial" > configs/c.1/strings/0x409/configuration

# 5. 添加mass storage function
mkdir functions/mass_storage.usb0
echo /dev/mmcblk0p1 > functions/mass_storage.usb0/lun.0/file

# 6. 添加serial function
mkdir functions/serial.usb0

# 7. 关联function到配置
ln -s functions/mass_storage.usb0 configs/c.1
ln -s functions/serial.usb0 configs/c.1

# 8. 绑定到UDC
echo musb-hdrc.0 > UDC

看到没?每一步都很清晰。创建配置、添加function、链接绑定。这就是复合设备的核心逻辑。

经验之谈:

实际项目中,复合设备的坑常常出在描述符冲突和端点分配上。你想想看,每个function都要占用一定数量的端点,如果UDC硬件支持的端点总数不够,那就得排队——要么减少function,要么优化端点使用。

我曾经做过一个项目,需要同时模拟RNDIS、ACM和MTP三种设备。结果RNDIS需要3个端点,ACM需要2个,MTP需要3个。总共8个端点,但UDC只有7个可用。最后不得不把RNDIS的协议从Ethernet Control Model换成Communication Device Class的Ethernet Emulation Model,省了一个端点才搞定。

USB Gadget驱动核心架构图 USB Gadget驱动核心架构 用户空间工具 / 应用程序 gadgetfs, libusb, 自定义配置工具 Gadget配置 / 描述符布局 configfs配置、字符串描述符、端点映射 Function驱动层 Mass Storage Serial (ACM) RNDIS / ECM UDC核心层 (Gadget API) 端点管理、DMA传输、请求队列、回调 USB设备控制器硬件 (DWC3 / Chipidea / ...) 向上:提供USB功能 向下:对硬件抽象

这张图很直观地展示了Gadget驱动的分层结构。从上到下,每一层都有清晰的职责边界。

说白了,整个系统的设计哲学就是分层和抽象。硬件变化,只需要换UDC驱动;要新增功能,只需写一个新的Function驱动;要组合设备,只需在配置层做好编排。