4. 模块编译与加载

在嵌入式 Linux 开发中,将 ECM Gadget 驱动编译为可动态加载的内核模块(.ko),而非直接编译进内核镜像,是调试与部署阶段的最佳实践。这种方式允许开发者在不重启系统的情况下快速迭代驱动代码、调整运行时参数,并灵活控制 USB Gadget 功能的启用与关闭。本章将详细讲解 ECM Gadget 模块的编译配置、动态加载/卸载的标准流程,以及通过 modprobe 和 insmod 传递参数的核心技巧。

4.1 编译为内核模块的配置方法

将 ECM Gadget 驱动编译为模块,需要在 Linux 内核源码树中正确配置 Kconfig 和 Makefile。对于使用标准内核源码的工程,通常通过 menuconfig 或 defconfig 进行配置。

4.1.1 Kconfig 与 Makefile 配置要点

ECM Gadget 驱动位于内核源码的 drivers/usb/gadget/function/ 目录下。其模块名称为 u_ecm.ko(核心功能模块)和 g_ecm.ko(复合设备模块)。

配置项 Kconfig 宏 说明
USB Gadget 支持 CONFIG_USB_GADGET 必须开启,作为 Gadget 框架基础
ECM 功能模块 CONFIG_USB_GADGET_U_ECM 编译为 u_ecm.ko,提供 ECM 协议实现
ECM 复合设备 CONFIG_USB_ETH 编译为 g_ecm.ko,封装为网络设备接口
模块版本支持 CONFIG_MODVERSIONS 建议开启,避免模块与内核版本不匹配

典型配置命令:

# 进入内核源码根目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 在 menuconfig 界面中依次选择:
# Device Drivers  --->
#   [*] USB support  --->
#     <M> USB Gadget Support  --->
#       USB Gadget Drivers  --->
#         <M> Ethernet Gadget (ECM subset)
#         <M> USB Ethernet Gadget (ECM + RNDIS + ...)

配置完成后,执行 make modules 即可在 drivers/usb/gadget/function/ 目录下生成 u_ecm.ko 和 g_ecm.ko。

工程提示: 在嵌入式 BSP 开发中,建议将 CONFIG_USB_GADGET_U_ECM 和 CONFIG_USB_ETH 设置为 m(模块),而非 y(内置)。这样可以在系统启动后按需加载,避免因驱动初始化失败导致整个 USB 子系统挂死。

4.1.2 模块依赖关系

ECM Gadget 模块存在明确的依赖链,加载时必须按顺序进行。下图展示了模块间的依赖关系:

ECM Gadget 模块依赖关系图 ECM Gadget 模块依赖链 usb_gadget.ko udc-core.ko u_ecm.ko g_ecm.ko 加载顺序:usb_gadget.ko → udc-core.ko → u_ecm.ko → g_ecm.ko

4.2 动态加载与卸载流程

在目标板上进行 ECM Gadget 模块的动态加载与卸载,需要遵循严格的顺序,以避免资源冲突或内核崩溃。

4.2.1 标准加载流程

  1. 确认 USB Gadget 框架已加载: 检查 /sys/kernel/config/usb_gadget/ 目录是否存在,若不存在则需先加载 libcomposite.ko。
  2. 加载 UDC 驱动: 根据硬件平台加载对应的 UDC 控制器驱动,例如 dwc2.ko 或 dwc3.ko。
  3. 加载 ECM 功能模块: 使用 modprobe 或 insmod 加载 u_ecm.ko。
  4. 加载复合设备模块: 加载 g_ecm.ko,该模块会自动创建网络接口(如 usb0)。
  5. 配置 USB 设备属性: 通过 ConfigFS 设置 VID、PID、序列号等。
  6. 绑定 UDC: 将 Gadget 实例绑定到具体的 UDC 控制器。

完整加载脚本示例:

#!/bin/sh
# ECM Gadget 动态加载脚本

# 1. 加载 USB Gadget 核心框架
modprobe libcomposite

# 2. 加载 UDC 驱动(以 DWC2 为例)
modprobe dwc2

# 3. 加载 ECM 功能模块
modprobe u_ecm

# 4. 加载 ECM 复合设备模块
modprobe g_ecm

# 5. 通过 ConfigFS 配置设备属性
cd /sys/kernel/config/usb_gadget/
mkdir g1 && cd g1
echo 0x1d6b > idVendor   # Linux Foundation
echo 0x0104 > idProduct  # ECM Gadget
echo 0x0100 > bcdDevice
echo 0x0200 > bcdUSB

# 6. 创建 ECM 功能实例
mkdir functions/ecm.usb0

# 7. 绑定 UDC
echo "dwc2.0" > UDC

echo "ECM Gadget 加载完成,网络接口 usb0 已创建"

4.2.2 标准卸载流程

卸载顺序与加载顺序相反,必须从最上层的复合设备开始,逐级释放资源。

  1. 解绑 UDC: 向 UDC 文件写入空字符串。
  2. 删除 Gadget 目录: 移除 ConfigFS 中创建的目录结构。
  3. 卸载复合设备模块: rmmod g_ecm。
  4. 卸载功能模块: rmmod u_ecm。
  5. 卸载 UDC 驱动: rmmod dwc2。
  6. 卸载核心框架: rmmod libcomposite。
警告: 在卸载模块前,务必确保没有用户态进程正在使用网络接口(如 usb0)。可以使用 ip link set usb0 down 先关闭接口,否则卸载模块可能导致内核崩溃或内存泄漏。

4.3 modprobe 与 insmod 参数传递

在 ECM Gadget 驱动中,许多关键参数(如 MAC 地址、缓冲区大小、中断间隔)可以在模块加载时通过参数传递进行定制。正确使用参数传递机制,是性能调优的第一步。

4.3.1 参数传递方式对比

工具 参数格式 自动处理依赖 适用场景
insmod insmod module.ko param=value 否 调试阶段,手动控制加载顺序
modprobe modprobe module param=value 是 生产环境,自动解决依赖

4.3.2 ECM Gadget 关键参数详解

g_ecm.ko 模块支持以下可调参数,这些参数在驱动初始化阶段通过 module_param() 宏注册:

参数名 类型 默认值 说明
dev_addr charp "00:11:22:33:44:55" 设备端 MAC 地址
host_addr charp "00:22:44:66:88:aa" 主机端 MAC 地址
qmult int 5 队列乘数,影响吞吐量
is_zlp_ok bool 1 是否允许零长度包

使用 insmod 传递参数示例:

# 加载 u_ecm 模块(无参数)
insmod /lib/modules/5.10.0/kernel/drivers/usb/gadget/function/u_ecm.ko

# 加载 g_ecm 模块并传递 MAC 地址和队列参数
insmod /lib/modules/5.10.0/kernel/drivers/usb/gadget/function/g_ecm.ko \
  dev_addr="00:11:22:33:44:66" \
  host_addr="00:22:44:66:88:bb" \
  qmult=10

使用 modprobe 传递参数示例:

# modprobe 会自动查找模块并解决依赖
modprobe g_ecm \
  dev_addr="00:11:22:33:44:66" \
  host_addr="00:22:44:66:88:bb" \
  qmult=10
性能调优提示: qmult 参数控制每个 USB 请求的 URBs 数量。在高速 USB 2.0 或 USB 3.0 环境下,将 qmult 从默认的 5 提升到 15-20,可以显著提高批量传输的吞吐量。但过高的值会消耗更多内存,需根据系统可用内存进行权衡。

4.3.3 参数持久化配置

在嵌入式产品中,通常需要将参数固化到配置文件中,避免每次手动输入。可以通过 /etc/modprobe.d/ 目录下的配置文件实现:

# 创建 /etc/modprobe.d/ecm-gadget.conf
echo "options g_ecm dev_addr=00:11:22:33:44:66 host_addr=00:22:44:66:88:bb qmult=15" \
  > /etc/modprobe.d/ecm-gadget.conf

# 之后只需执行 modprobe g_ecm,参数会自动加载
modprobe g_ecm

4.4 加载状态验证与调试

模块加载完成后,需要通过系统工具验证驱动是否正常工作。

4.4.1 验证步骤

  1. 检查模块列表: lsmod | grep -E "g_ecm|u_ecm|libcomposite"
  2. 检查网络接口: ip link show usb0
  3. 检查 USB 设备状态: cat /sys/kernel/debug/usb/gadget/status
  4. 检查内核日志: dmesg | tail -20

正常加载后的 dmesg 输出示例:

[ 1234.567890] g_ecm: Ethernet Gadget, version: 1.0
[ 1234.567895] g_ecm: using random self ethernet address
[ 1234.567900] g_ecm: using random host ethernet address
[ 1234.567905] usb0: HOST MAC 00:22:44:66:88:bb
[ 1234.567910] usb0: MAC 00:11:22:33:44:66
[ 1234.567915] g_ecm: qmult set to 15

4.4.2 常见加载失败原因

错误现象 可能原因 解决方案
modprobe: FATAL: Module not found 模块未安装到 /lib/modules/ 执行 make modules_install
Unknown symbol 依赖模块未加载或版本不匹配 检查 modprobe --show-depends
device or resource busy UDC 已被其他 Gadget 占用 卸载冲突模块或重启 USB 子系统
usb0: Network is down 未绑定 UDC 或绑定失败 检查 /sys/kernel/config/usb_gadget/ 配置

4.5 本章小结

将 ECM Gadget 编译为内核模块并动态加载,是嵌入式 USB 开发中的核心技能。开发者需要掌握:

  • 通过 Kconfig 将 u_ecm 和 g_ecm 配置为模块(m)
  • 严格遵循 libcomposite → UDC → u_ecm → g_ecm 的加载顺序
  • 利用 modprobe 的自动依赖解析和参数传递能力简化部署
  • 通过 dev_addr、host_addr、qmult 等参数进行运行时调优
  • 使用 lsmod、dmesg、ip link 验证加载状态