4. ConfigFS 基础:挂载 ConfigFS、创建 Gadget 目录、配置 UDC、绑定 Gadget 的流程
ConfigFS(Configuration File System)是 Linux 内核提供的一种基于内存的虚拟文件系统,用于在用户空间动态配置 USB Gadget 的功能和拓扑。与传统的 Gadget 驱动静态编译不同,ConfigFS 允许开发者通过简单的文件操作(mkdir、echo、ln)来构建任意组合的复合 USB 设备,是当前主流嵌入式平台(如 Raspberry Pi、i.MX、STM32MP)调试 USB Gadget 枚举问题的核心工具链。
调试要点: 枚举失败时,90% 的根因都可以在 ConfigFS 的配置流程中找到线索——UDC 是否可用、字符串描述符是否缺失、端点数量是否超限。掌握 ConfigFS 的每一步操作,就等于掌握了 USB 枚举的“手术刀”。
4.1 ConfigFS 挂载与 Gadget 目录创建
ConfigFS 默认挂载在 /sys/kernel/config/usb_gadget 路径下。如果该目录不存在,需要手动挂载:
# 挂载 ConfigFS
sudo mount -t configfs none /sys/kernel/config
# 确认挂载成功
ls /sys/kernel/config/usb_gadget/
挂载成功后,即可创建 Gadget 实例。每个 Gadget 实例对应一个 USB 设备,其目录名称即为 Gadget 的名称(建议使用有意义的标识,如 g1、acm_ecm):
cd /sys/kernel/config/usb_gadget/
mkdir g1
cd g1
创建完成后,g1 目录下会自动生成以下核心子目录和文件:
| 路径/文件 | 作用 | 调试关注点 |
|---|---|---|
UDC |
绑定目标 UDC 控制器(如 ci_hdrc.0) |
写入前必须确认 UDC 名称正确且状态为 free |
idVendor / idProduct |
USB 厂商 ID 和产品 ID | 必须为 4 位十六进制数,如 0x1d6b |
bcdDevice / bcdUSB |
设备版本号 / USB 协议版本 | bcdUSB 常用 0x0200(USB 2.0) |
strings/ |
存放语言 ID 和字符串描述符 | 缺少字符串描述符会导致 Windows 主机枚举失败 |
configs/ |
存放配置描述符(可包含多个配置) | 每个配置下可绑定多个功能(Function) |
functions/ |
存放功能实例(如 acm.usb0、ecm.usb0) |
功能名称必须与内核模块匹配 |
常见错误: 如果
mkdir g1 失败并返回 Operation not supported,说明内核未编译 CONFIG_USB_GADGET 或 CONFIGFS_FS。请检查内核配置并重新编译。
4.2 配置 UDC 与绑定 Gadget 的完整流程
以下是一个标准的 ConfigFS 配置流程,以创建一个“串口 + 网络”复合设备(ACM + ECM)为例:
- 设置设备描述符:写入 VID/PID 和版本号。
- 配置字符串描述符:至少设置
serial、manufacturer、product。 - 创建配置:在
configs/下创建配置目录(如c.1)。 - 创建功能实例:在
functions/下创建所需功能(如acm.usb0、ecm.usb0)。 - 绑定功能到配置:创建符号链接将功能链接到配置目录。
- 绑定 UDC:将 UDC 名称写入
UDC文件,激活 Gadget。
# 1. 设置 VID/PID(以 Linux Foundation 为例)
echo "0x1d6b" > idVendor
echo "0x0104" > idProduct
echo "0x0200" > bcdUSB
echo "0x0100" > bcdDevice
# 2. 配置字符串描述符(英文)
mkdir strings/0x409
echo "0123456789" > strings/0x409/serialnumber
echo "MyCompany" > strings/0x409/manufacturer
echo "Composite ACM+ECM" > strings/0x409/product
# 3. 创建配置 c.1
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "Config 1: ACM+ECM" > configs/c.1/strings/0x409/configuration
# 4. 创建功能实例
mkdir functions/acm.usb0
mkdir functions/ecm.usb0
# 5. 绑定功能到配置
ln -s functions/acm.usb0 configs/c.1/
ln -s functions/ecm.usb0 configs/c.1/
# 6. 查询可用 UDC 并绑定
cat /sys/class/udc/UDC/state # 确认状态为 "free"
echo "ci_hdrc.0" > UDC # 绑定 UDC,激活 Gadget
枚举失败高发点: 绑定 UDC 时,如果
(1) UDC 名称错误(可通过
(2) 某个功能模块未加载(如
(3) 端点资源不足(如 ECM 需要 3 个端点,但 UDC 仅支持 2 个)。
echo 操作返回 Invalid argument,通常是因为:(1) UDC 名称错误(可通过
ls /sys/class/udc/ 查看正确名称);(2) 某个功能模块未加载(如
modprobe usb_f_acm);(3) 端点资源不足(如 ECM 需要 3 个端点,但 UDC 仅支持 2 个)。
4.3 ConfigFS 配置拓扑与数据流
下图展示了 ConfigFS 配置完成后,Gadget 内部各组件之间的层级关系与数据流向:
4.4 调试技巧:验证绑定状态与常见问题定位
绑定 UDC 后,可以通过以下命令验证 Gadget 是否成功激活:
# 查看 UDC 状态
cat /sys/class/udc/ci_hdrc.0/state
# 期望输出:configured
# 查看 Gadget 是否已绑定
ls -l /sys/kernel/config/usb_gadget/g1/UDC
# 期望输出:指向 UDC 名称
# 查看主机是否识别(dmesg)
dmesg | tail -20
# 期望看到:New USB device found, idVendor=1d6b, idProduct=0104
如果状态不是 configured,请对照下表排查:
| UDC 状态 | 含义 | 常见原因 | 解决措施 |
|---|---|---|---|
free |
UDC 空闲,未绑定任何 Gadget | 未写入 UDC 文件,或写入后立即被解绑 | 重新执行 echo "udc_name" > UDC |
configured |
Gadget 已成功激活,等待主机枚举 | 正常状态 | 无需操作 |
suspended |
USB 总线挂起 | 主机进入休眠或线缆断开 | 重新插拔 USB 线缆 |
error |
绑定失败 | 端点不足、功能模块未加载、描述符错误 | 检查 dmesg 错误日志,逐一排查 |
实战案例: 某项目在 i.MX8M 平台上绑定 ECM 功能时,UDC 状态始终为
free。经排查,发现 UDC 名称写成了 ci_hdrc.0,但实际控制器名称为 dwc3.0。修正后 Gadget 正常枚举。建议始终通过 ls /sys/class/udc/ 获取准确的 UDC 名称。
4.5 解绑与清理 Gadget
调试完成后,或需要重新配置时,按以下顺序解绑并清理:
# 1. 解绑 UDC(写入空字符串)
echo "" > UDC
# 2. 移除功能链接
rm configs/c.1/acm.usb0
rm configs/c.1/ecm.usb0
# 3. 删除功能实例
rmdir functions/acm.usb0
rmdir functions/ecm.usb0
# 4. 删除配置和字符串
rmdir configs/c.1/strings/0x409
rmdir configs/c.1
rmdir strings/0x409
# 5. 删除 Gadget 实例
cd ..
rmdir g1
注意: 解绑 UDC 前,必须确保主机端已断开设备连接(或设备未被占用)。如果直接删除 Gadget 目录而未先解绑,可能导致内核 panic 或 UDC 控制器死锁。建议在解绑后等待 1-2 秒再执行后续清理操作。