USB Gadget 让具有 USB 设备控制器的 Linux 开发板作为 USB 外设。配置网络功能后,主机与开发板各获得一个网络接口,再通过常规 IP 网络通信。它不会自动提供 DHCP、路由、互联网共享或应用服务。
本文以 Linux 上游 ConfigFS 接口为依据。具体内核版本、UDC 驱动、设备树和主机系统必须按开发板核对;文中脚本经过 Bash 语法检查,没有在目标开发板创建 Gadget 或验证枚举。
1. 从硬件到 IP 网络的关系
跳转到“1. 从硬件到 IP 网络的关系”支持 peripheral/OTG 角色的 USB 控制器及连接口 ↓UDC 驱动与 Gadget 框架 ↓ECM、NCM、RNDIS 或 EEM 功能 ↓开发板网络接口 ← USB 链路 → 主机网络接口 ↓IP 地址、路由、防火墙和应用协议只有 USB 主机控制器、只接出 host 角色的端口、充电专用线缆或不正确的设备树角色都可能阻止此方案。先检查 /sys/class/udc/ 是否有目标控制器,再决定上层功能配置。参见 Linux Gadget ConfigFS 文档。
2. 两条配置路线不要混在一起
跳转到“2. 两条配置路线不要混在一起”固定 Gadget 驱动:g_ether
跳转到“固定 Gadget 驱动:g_ether”g_ether 是模块名;上游内核生成它的配置符号是 CONFIG_USB_ETH。原笔记中的 CONFIG_USB_G_ETHER 不是所核对上游内核的配置项。CONFIG_USB_ETH_RNDIS、CONFIG_USB_ETH_EEM 是此固定驱动的可选功能,不能直接当作 ConfigFS 网络功能的开启方法。
在板级驱动、角色和模块已准备好的条件下,可以加载 g_ether,再为实际生成的接口设置地址。这一路线适合固定功能测试,不要求同时创建一个 ConfigFS Gadget。配置依据见 Linux v6.6 legacy Kconfig。
动态组合:ConfigFS
跳转到“动态组合:ConfigFS”ConfigFS 路线使用 USB_CONFIGFS 及需要的功能选择;它会选择相应的 USB_LIBCOMPOSITE、USB_F_* 等依赖。
| 配置 | 含义 |
|---|---|
CONFIG_USB_GADGET | Gadget 框架 |
| 板级 UDC 对应配置 | 具体 USB 设备控制器驱动 |
CONFIG_USB_CONFIGFS | 通过 ConfigFS 组合功能 |
CONFIG_USB_CONFIGFS_ECM | ECM 网络功能 |
CONFIG_USB_CONFIGFS_NCM | NCM 网络功能 |
CONFIG_USB_CONFIGFS_RNDIS | RNDIS 功能 |
CONFIG_USB_CONFIGFS_EEM | EEM 功能 |
是否设为 y 或 m 取决于依赖和启动方式;不要把内部依赖符号当作菜单中的独立开关。见 上游 Gadget Kconfig。同一 UDC 通常不能同时被 g_ether 和另一个 ConfigFS Gadget 绑定;切换路线前先识别并停止已有配置。
3. 选择主机能够使用的网络功能
跳转到“3. 选择主机能够使用的网络功能”| 功能 | 选择时需要考虑 |
|---|---|
| ECM | 常见 Linux/macOS 主机支持路径;仍需核对目标系统驱动与枚举 |
| NCM | 可聚合多个以太网帧;Windows 11、Windows Server 2022 提供 UsbNcm 驱动 |
| RNDIS | 可用于已有 Windows 兼容需求;描述符和驱动匹配仍需配置 |
| EEM | 另一种以太网封装方式;不能仅凭名字推断主机支持或最高效率 |
原文把 RNDIS 写成所有 Windows 的必需选项、保证自动无驱动识别,并把 EEM 称为“最新且效率最高”,都缺少条件。微软现在建议硬件供应商采用 NCM;具体 Windows 支持以内置 USB 类驱动列表为准。RNDIS 是否自动匹配,还涉及接口描述符、INF 或相应 OS 描述符,不能只创建一个目录就保证。
下面只配置一个 ECM 功能,便于先在支持 ECM 的主机上验证。增加多个功能或多配置,需要单独设计描述符与主机选择行为,不能假定主机会自动挑选所有兼容功能。
4. ConfigFS 最小 ECM 示例
跳转到“4. ConfigFS 最小 ECM 示例”执行前需要确定:自己有权使用的 VID/PID、UDC 名称、稳定序列号、真实电流需求以及不冲突的地址。原示例的 0x1d6b/0x0104 是其他设备身份,不能作为任意产品都可使用的标识。bcdUSB=0x0200 也不会把硬件端口自动升级为高速。
下面以开发板 192.168.7.2/24、主机 192.168.7.1/24 为实验地址。若与现有网络冲突,两端应一起换网段。MaxPower 在此 ConfigFS 接口中以 mA 配置,不应混淆为原始描述符中的编码单位。
保存为 setup-ecm.sh,在开发板上根据实际参数设置 USB_VID、USB_PID、USB_UDC、USB_SERIAL、USB_MAX_POWER_MA 后运行。脚本拒绝覆盖已存在的同名 Gadget;失败会解绑并保留目录,便于检查和明确清理。
#!/usr/bin/env bashset -euo pipefail: "${USB_VID:?Set your authorized vendor ID, such as 0xNNNN}": "${USB_PID:?Set the product ID allocated for this device}": "${USB_UDC:?Choose one name from /sys/class/udc}": "${USB_SERIAL:?Set a stable device serial number}": "${USB_MAX_POWER_MA:?Set the actual USB 2 power requirement in mA}"[[ $EUID == 0 ]] || { echo 'Run on the target board as root.' >&2; exit 1; }[[ $USB_VID =~ ^0x[0-9A-Fa-f]{4}$ && $USB_PID =~ ^0x[0-9A-Fa-f]{4}$ ]][[ $USB_UDC != */* && -e /sys/class/udc/"$USB_UDC" ]][[ $USB_MAX_POWER_MA =~ ^[0-9]+$ && ${#USB_MAX_POWER_MA} -le 3 ]](( 10#$USB_MAX_POWER_MA <= 500 ))
modprobe libcompositemountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/configgadget=/sys/kernel/config/usb_gadget/vitalogos_ecm[[ ! -e $gadget ]] || { echo 'Gadget already exists; inspect or stop it first.' >&2; exit 1; }mkdir "$gadget"cd "$gadget"trap 'printf "\n" > UDC 2>/dev/null || true; echo "Setup failed; inspect the retained gadget directory." >&2' ERRprintf '%s\n' "$USB_VID" > idVendorprintf '%s\n' "$USB_PID" > idProductprintf '0x0100\n' > bcdDeviceprintf '0x0200\n' > bcdUSBmkdir strings/0x409printf '%s\n' "$USB_SERIAL" > strings/0x409/serialnumberprintf 'Example Device\n' > strings/0x409/manufacturerprintf 'USB ECM network\n' > strings/0x409/productmkdir configs/c.1mkdir configs/c.1/strings/0x409printf 'ECM\n' > configs/c.1/strings/0x409/configurationprintf '%s\n' "$USB_MAX_POWER_MA" > configs/c.1/MaxPowermkdir functions/ecm.usb0# 以下地址只用于单对设备的实验;多台设备必须分配不同的本地单播 MAC。printf '%s\n' "${USB_DEV_MAC:-02:00:00:00:00:01}" > functions/ecm.usb0/dev_addrprintf '%s\n' "${USB_HOST_MAC:-02:00:00:00:00:02}" > functions/ecm.usb0/host_addrln -s functions/ecm.usb0 configs/c.1/ecm.usb0printf '%s\n' "$USB_UDC" > UDCinterface=$(cat functions/ecm.usb0/ifname)ip link set dev "$interface" upip address replace 192.168.7.2/24 dev "$interface"trap - ERRprintf 'Device interface: %s; address: 192.168.7.2/24\n' "$interface"functions/ecm.usb0/ifname 反映功能关联的网口名;不要因为实例名含 usb0 就假定最终接口一定叫 usb0。功能的接口属性和验证流程见 Linux Gadget Testing:ECM。
原文使用 ls /sys/class/udc > UDC,在零个或多个 UDC 时都可能错误;必须选择一个确切名称。固定 sleep 2 也不能证明枚举成功,应该分别检查绑定、主机枚举和 IP 连通性。
5. 主机端地址与双向验证
跳转到“5. 主机端地址与双向验证”在 Linux 主机上先识别新增接口,随后以它的实际名称配置:
ip -brief link# 把 usb_host_if 替换为实际接口名。ip address replace 192.168.7.1/24 dev usb_host_ifip link set dev usb_host_if upping -c 3 192.168.7.2开发板再测试 ping -c 3 192.168.7.1。主机侧也可以通过已有网络管理工具配置,避免多个管理器争夺同一接口。
原命令 ifconfig usb0 ... 可表达静态地址意图,但系统未必安装 net-tools;这里使用 ip。ip_forward=1 只开启转发,不会自动创建 NAT、默认路由或 DNS。仅需主机与板子通信时,不必打开转发;确需共享上网时再单独设计转发与防火墙规则。
6. 按层排查
跳转到“6. 按层排查”| 现象 | 优先核对 |
|---|---|
/sys/class/udc 为空 | USB 口角色、设备树、PHY、电源、板级驱动 |
| 功能目录创建失败 | 对应 USB_CONFIGFS_* 配置、模块和内核日志 |
| 写 UDC 失败 | 名称是否正确、是否被另一个 Gadget 占用、功能是否完整 |
| 主机看不到 USB 设备 | 线缆、端口、角色、描述符与主机枚举日志 |
| 有 USB 设备但无网卡 | 主机类驱动匹配及所选 ECM/NCM/RNDIS/EEM 功能 |
| 有网卡但 ping 不通 | 两端地址和掩码、接口状态、地址冲突、防火墙 |
| ping 通但应用不通 | 服务监听地址、端口、应用协议与访问策略 |
开发板可以结合 dmesg、ip -brief address 和 Gadget 的 UDC 属性查看状态。lsmod 只能显示模块,不能据此判断编入内核的驱动不存在。
7. 停止配置与开机启动
跳转到“7. 停止配置与开机启动”清理这个示例时,先确认正在操作的正是该 Gadget,再按创建的反序移除:
cd /sys/kernel/config/usb_gadget/vitalogos_ecmprintf '\n' > UDCrm configs/c.1/ecm.usb0rmdir configs/c.1/strings/0x409rmdir configs/c.1rmdir functions/ecm.usb0rmdir strings/0x409cd ..rmdir vitalogos_ecmConfigFS 属性由内核管理,不应把这里当作普通文件树递归删除。若前一次创建只完成了一部分,应先查看实际存在的条目,再清理对应目录。
稳定运行后可把启动和停止过程封装成 systemd 服务,设置与实际 UDC、ConfigFS 和网络管理方式相符的依赖,并记录失败日志。原文的 rc.local 方案需要系统确实启用兼容服务;不能把“将几条命令贴进去”当作所有发行版均可用的启动方式。加入自动启动前,应完成重复启停、拔插和主机重新连接测试。