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)为例:

  1. 设置设备描述符:写入 VID/PID 和版本号。
  2. 配置字符串描述符:至少设置 serial、manufacturer、product。
  3. 创建配置:在 configs/ 下创建配置目录(如 c.1)。
  4. 创建功能实例:在 functions/ 下创建所需功能(如 acm.usb0、ecm.usb0)。
  5. 绑定功能到配置:创建符号链接将功能链接到配置目录。
  6. 绑定 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 时,如果 echo 操作返回 Invalid argument,通常是因为:
(1) UDC 名称错误(可通过 ls /sys/class/udc/ 查看正确名称);
(2) 某个功能模块未加载(如 modprobe usb_f_acm);
(3) 端点资源不足(如 ECM 需要 3 个端点,但 UDC 仅支持 2 个)。

4.3 ConfigFS 配置拓扑与数据流

下图展示了 ConfigFS 配置完成后,Gadget 内部各组件之间的层级关系与数据流向:

ConfigFS Gadget 配置拓扑与数据流 UDC 控制器 (ci_hdrc.0) Gadget 实例 (g1) 设备描述符 (VID/PID) 字符串描述符 (strings/0x409) 配置描述符 (configs/c.1) 功能实例 (acm.usb0) 功能实例 (ecm.usb0) 端点 (EP IN/OUT) 端点 (EP IN/OUT) 主机数据请求 → 说明: 数据流从主机发起请求,经 UDC 控制器 → 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 秒再执行后续清理操作。