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,省了一个端点才搞定。
这张图很直观地展示了Gadget驱动的分层结构。从上到下,每一层都有清晰的职责边界。
说白了,整个系统的设计哲学就是分层和抽象。硬件变化,只需要换UDC驱动;要新增功能,只需写一个新的Function驱动;要组合设备,只需在配置层做好编排。