之前在Android 平台上开发usb gadget功能时, Google提供了两种管理usb gadget 功能得方式。android/system/core/rootdir/init.usb.configfs.rc android 的usb device manager 直接通过binder 和usb gadget hal 通信进行管理。但是linux平台上,由于太开放了,大家都有一套自己的玩法,最简单的就是在系统系统后,运行个脚本,挂载configfs, 然后配置function ,连接功能等。这两天在rk3572 的linux平台上适配usb gadget 功能,发现rk有一套管理框架,于是就研究了下,在rk的框架上添加业务自定义的function就是在configfs/usb_gadget/rockchip/functions 下面的功能② 框架定义了一些hook, 可以在加载流程以及function连接前后定制操作| 文件 | 路径 | 说明 |
|---|
| 框架主脚本 | device/rockchip/common/overlays/rootfs/usb-gadget/usr/bin/usb-gadget | 1035 行,管理 configfs 全生命周期 |
| 安装脚本 | device/rockchip/common/overlays/rootfs/usb-gadget/install.sh | 编译时安装 overlay 到 rootfs |
| udev 规则 | device/rockchip/common/overlays/rootfs/usb-gadget/lib/udev/rules.d/61-usb-gadget.rules | USB 热插拔自动触发 |
| Kconfig | device/rockchip/common/configs/post/Config.in.usb-gadget | 编译期配置菜单 |
| 文件 | 路径 | 说明 |
|---|
| 环境变量 | /etc/profile.d/usb-gadget.sh | 编译时由 install.sh 生成,定义 USB_FUNCS 等 |
| Hook 脚本 | /etc/usb-gadget.d/*.sh | 用户自定义 hook,运行时被 source |
| 运行时状态 | /var/run/usb-gadget/funcs | 记录当前运行的 function 列表 |
| 日志 | /var/log/usb-gadget.log | 运行日志 |
┌─────────────────────────────────────────────────┐│ usb-gadget 命令 (用户执行) ││ usb-gadget start/stop/restart/update ││ │ ││ ▼ ││ echo $ACTION >> /var/run/usb-gadget/commands ││ │ ││ ▼ ││ ┌─────────────────────────────────────────┐ ││ │ usb-service 守护进程 (后台常驻) │ ││ │ tail -f commands | while read ACTION │ ││ │ │ │ ││ │ ▼ │ ││ │ usb_handle_request($ACTION) │ ││ │ │ │ ││ │ ▼ │ ││ │ usb_prepare() → usb_start() │ ││ └─────────────────────────────────────────┘ │└─────────────────────────────────────────────────┘
重要:usb-gadget restart 等命令不直接执行,而是通过命令文件发给后台守护进程。因此 export USB_FUNCS=xxx 后直接 usb-gadget restart 不会生效——守护进程用的是自己的环境变量(启动时从 /etc/profile 读取)。SUBSYSTEM=="android_usb", ACTION=="change", RUN+="/usr/bin/usb-gadget update"SUBSYSTEM=="udc", RUN+="/usr/bin/usb-gadget update"SUBSYSTEM=="extcon", ENV{NAME}=="extcon-usb-port*", RUN+="/usr/bin/usb-gadget update"
写入 UDC 文件、USB 状态变化、Type-C 插拔都会触发 usb-gadget update。usb_prepare() 阶段(每次 start/update/restart 都会执行):usb_prepare(){ # 1. source 所有 hook 脚本 if [ -d /etc/usb-gadget.d ]; then for hook in $(find /etc/usb-gadget.d/ -type f); do source "$hook" # ← hook 脚本中的变量和函数在这里加载 done fi USB_FUNCS=${USB_FUNCS:-adb} # ← 注意:如果 USB_FUNCS 为空会被覆盖为 adb # 2. 创建 gadget 目录(仅首次) if [ ! -d $USB_GADGET_DIR ]; then mkdir -p $USB_GADGET_DIR usb_run_stage usb init # ← usb_post_init_hook 在这里调用 fi # 3. 检查 function 是否变化 OLD_FUNCS=$(usb_get_started) if [ -n "$OLD_FUNCS" ] && [ "$OLD_FUNCS" != "$USB_FUNCS" ]; then usb_run_stage usb stop # ← 变化时先 stop fi}
2.2 Hook 调度函数
核心调度器 usb_run_stage()(第 291 行):usb_run_stage(){ for f in $1_pre_$2_hook $1_$2 $1_post_$2_hook; do type $f >/dev/null 2>/dev/null || continue # 函数不存在则跳过 usb_msg "Run stage: $f" eval $f || return 1 # 执行,失败则中断 done}
传入两个参数(function名 + 阶段名),自动构造 3 个函数名依次执行:| 调用示例 | 依次尝试的函数 |
|---|
usb_run_stage usb init | usb_pre_init_hook → usb_init → usb_post_init_hook |
usb_run_stage usb prepare | usb_pre_prepare_hook → usb_prepare → usb_post_prepare_hook |
usb_run_stage usb start | usb_pre_start_hook → usb_start → usb_post_start_hook |
usb_run_stage usb stop | usb_pre_stop_hook → usb_stop → usb_post_stop_hook |
usb_run_stage adb prepare | adb_pre_prepare_hook → adb_prepare → adb_post_prepare_hook |
usb_run_stage custom_audio prepare | custom_audio_pre_prepare_hook → custom_audio_prepare → custom_audio_post_prepare_hook |
2.3 完整生命周期
usb_handle_request("start") │ ├── usb_enable() # 创建 /var/run/usb-gadget/funcs │ ├── usb_prepare() │ ├── source /etc/usb-gadget.d/*.sh # 加载 hook 脚本 │ ├── usb_run_stage usb init # 仅首次(gadget 目录不存在时) │ │ ├── usb_pre_init_hook() # ★ 可定义:加载内核模块 │ │ ├── usb_init() # 框架:写 idVendor/strings/MaxPower │ │ └── usb_post_init_hook() # ★ 可定义 │ │ │ └── 检查 USB_FUNCS 是否变化 │ └── usb_start() │ ├── for func in $USB_FUNCS: # 遍历每个 function │ ├── mkdir functions/$instance # 框架自动创建 configfs 目录 │ ├── cd functions/$instance │ ├── usb_run_stage $func prepare # 调用 <func>_prepare() │ │ ├── <func>_pre_prepare_hook() # ★ 可定义 │ │ ├── <func>_prepare() # ★ 可定义:配置 configfs 属性 │ │ └── <func>_post_prepare_hook()# ★ 可定义 │ └── symlink → configs/b.1/f-$instance # 框架自动创建 │ ├── echo $UDC > UDC # 绑定 UDC,激活 gadget │ └── for func in $USB_FUNCS: └── usb_run_stage $func start # 调用 <func>_start() ├── <func>_pre_start_hook() # ★ 可定义 ├── <func>_start() # ★ 可定义 └── <func>_post_start_hook() # ★ 可定义
usb_handle_request("stop") ├── usb_run_stage usb stop │ ├── usb_pre_stop_hook() # ★ 可定义 │ ├── usb_stop() # 框架:解绑 UDC,删 symlink,删 function 目录 │ └── usb_post_stop_hook() # ★ 可定义 └── usb_disable() # 删除 /var/run/usb-gadget/funcs
2.4 Hook 调用时机注意事项
| Hook | 调用频率 | 适用场景 |
|---|
usb_pre_init_hook | 仅首次(gadget 目录不存在时) | 加载内核模块 |
usb_post_init_hook | 仅首次 | 不要在这里创建 function(restart 时不会重新执行) |
usb_pre_prepare_hook | 每次 start/update/restart | 动态调整变量 |
usb_post_prepare_hook | 每次 | 同上(但注意过度执行可能导致 USB 反复断开重连) |
<func>_prepare | 每次 start 时,针对每个 function | 推荐:配置 configfs 属性 |
<func>_pre_start_hook | 每次 start 时 | function 启动前的准备工作 |
usb_pre_stop_hook | 每次 stop 时 | 清理资源 |
三、自定义 Function 开发
3.1 推荐方案:让框架管理你的 Function
将自定义 function 注册到 USB_FUNCS,框架自动处理 mkdir、symlink、stop 清理。
步骤 1:创建 hook 脚本
文件位置:device/cvt/common/model/<机型>/customer-usb-gadget.sh
#!/bin/bash## Customer USB Gadget configfs hook 配置## 注意:USB_FUNCS 不在这里设置,由 /etc/profile.d/usb-gadget.sh 控制# 本脚本只负责:加载内核模块 + 定义各 function 的 _prepare() 函数## 覆盖框架变量USB_VENDOR_ID="<YOUR_VID>"USB_PRODUCT_ID="<YOUR_PID>"USB_MANUFACTURER="Customer"USB_PRODUCT="Customer Device"# ============================# 实例名映射 (框架的 usb_instances() 用)# ============================# usb_instances() 通过 <FUNC大写>_INSTANCES 查找实例名# 未定义时默认 <func>.0CUSTOMER_COMM_INSTANCES="customer_comm.0"CUSTOMER_HID_INSTANCES="customer_hid.1"HID_FEATURE_CTRL_INSTANCES="hid_feature_ctrl.2"CUSTOMER_AUDIO_INSTANCES="customer_audio.3"# ============================# 加载内核模块 (仅首次执行)# ============================usb_pre_init_hook(){ insmod /lib/modules/customer_mic_buffer.ko 2>/dev/null insmod /lib/modules/customer_aout_buffer.ko 2>/dev/null insmod /lib/modules/usb_f_customer_audio.ko 2>/dev/null insmod /lib/modules/usb_f_customer_hid.ko 2>/dev/null insmod /lib/modules/usb_f_customer_comm.ko 2>/dev/null insmod /lib/modules/usb_f_hid_feature_ctrl.ko 2>/dev/null}# ============================# 每个 function 的 prepare 函数# 框架自动: mkdir → cd 到 function 目录 → 调用 _prepare() → symlink# ============================customer_audio_prepare(){echo"Speaker" > str_spk_nameecho"Microphone" > str_mic_nameecho"Customer Audio" > str_card_name}customer_comm_prepare(){echo"Customer Communication" > str_intf_name}customer_hid_prepare(){ : # 无需额外配置}hid_feature_ctrl_prepare(){ : # 无需额外配置}步骤 2:在 defconfig 中注册 function
# device/cvt/customer/<客户>/rockchip_rk3572_customer_ab_defconfig# 自定义 function 列表(框架会遍历 USB_FUNCS 逐个创建)RK_USB_EXTRA="customer_comm customer_hid hid_feature_ctrl customer_audio"# 可选:关闭 ADB# RK_USB_ADBD is not set
install.sh 编译时会生成:
# /etc/profile.d/usb-gadget.sh (自动生成)exportUSB_FUNCS="adb customer_comm customer_hid hid_feature_ctrl customer_audio"# ^^^ 来自 RK_USB_ADBD=y ^^^^^^^^^^^^^^^^^^^^^^^^ 来自 RK_USB_EXTRAexportUSB_VENDOR_ID="0x2207"exportUSB_FW_VERSION="0x0310"exportUSB_MANUFACTURER="Rockchip"exportUSB_PRODUCT="rk3xxx"
步骤 3:安装 hook 脚本
在 defconfig 中配置 RK_USB_HOOKS:
RK_USB_HOOKS="customer-usb-gadget.sh"
install.sh 会从 RK_CHIP_DIR(device/rockchip/.chips/rk3572/)查找并安装到 rootfs/etc/usb-gadget.d/。
3.2 框架对每个 function 自动执行的操作
USB_FUNCS="customer_comm customer_hid hid_feature_ctrl customer_audio" │ ▼ usb_instances() 展开customer_comm → customer_comm.0 ← CUSTOMER_COMM_INSTANCEScustomer_hid → customer_hid.1 ← CUSTOMER_HID_INSTANCEShid_feature_ctrl → hid_feature_ctrl.2customer_audio → customer_audio.3 ← CUSTOMER_AUDIO_INSTANCES │ ▼ 遍历每个 instance (usb_start 第 906-924 行)mkdir -p functions/customer_comm.0 ← 框架自动cd functions/customer_comm.0customer_comm_prepare() ← 你的函数symlink → configs/b.1/f-customer_comm.0 ← 框架自动 │ ▼ 所有 function 处理完后echo 24000000.usb > UDC ← 框架自动绑定 │ ▼ Stop 时rm configs/b.1/f-* ← 框架自动删除 symlinkrmdir functions/customer_* ← 框架自动删除目录(保留 ffs.*)
3.3 框架内置 Function 的 prepare 实现参考
# adb (第 442-458 行)ADB_INSTANCES="ffs.adb"adb_prepare(){ mount -t devpts none /dev/pts # ADB 需要 PTS ifup --force lo # ADB 需要 loopback usb_mount ffs.adb /dev/usb-ffs/adb -t functionfs # 挂载 FunctionFS usb_start_daemon /usr/bin/adbd # 启动 adbd 守护进程 usb_wait_access -m /dev/usb-ffs/adb # 等待挂载完成}# uac1 (第 480-495 行)uac1_prepare(){echo3 > p_chmask # 播放声道数echo2 > p_ssize # 播放采样大小 (字节)echo8000,16000,44100,48000 > p_srate # 播放采样率echo3 > c_chmask # 录音声道数echo2 > c_ssize # 录音采样大小echo4 > req_number # 请求数echo8000,16000,44100,48000 > c_srate # 录音采样率}# ums (第 714-764 行)ums_prepare(){# 创建磁盘镜像文件(如果不存在)if [ ! -f"$UMS_FILE" ] && [ ! -b"$UMS_FILE" ]; then ums_formatfi}
四、运行时动态控制 Function
4.1 控制哪些 Function 生效
通过修改 /etc/profile.d/usb-gadget.sh 中的 USB_FUNCS 来控制:
# 全部启用 (ADB + 自定义)exportUSB_FUNCS="adb customer_comm customer_hid hid_feature_ctrl customer_audio"# 只开自定义 function,不要 ADBexportUSB_FUNCS="customer_comm customer_hid hid_feature_ctrl customer_audio"# 只开 ADBexportUSB_FUNCS="adb"# 只开 audioexportUSB_FUNCS="customer_audio"
4.2 正确重启 usb-gadget 的方法
因为 usb-gadget restart 发命令给守护进程,环境变量不会传递。必须改守护进程能读到的配置,然后重启守护进程:
# 1. 修改 profile.d(守护进程启动时读取)echo'export USB_FUNCS="customer_comm customer_hid hid_feature_ctrl customer_audio"' \ > /etc/profile.d/usb-gadget.sh# 2. 重启守护进程(不是 usb-gadget restart!)/etc/init.d/S50usb-gadget.sh stop/etc/init.d/S50usb-gadget.sh start# 或用 systemdsystemctl restart usb-gadget
4.3 临时禁用 usb-gadget 自动恢复
udev 规则会在 UDC 变化时自动触发 usb-gadget update。临时禁用:
# 方法1:让 update 跳过(推荐)rm /var/run/usb-gadget/funcs# usb_is_enabled 返回 false → update 直接 return# 方法2:删 udev 规则rm /lib/udev/rules.d/61-usb-gadget.rulesudevadm control --reload-rules# 方法3:停服务/etc/init.d/S50usb-gadget.sh stop
4.4 恢复
# 重新启用touch /var/run/usb-gadget/funcs/etc/init.d/S50usb-gadget.sh stop/etc/init.d/S50usb-gadget.sh start
五、常见问题与排查
5.1 usb-gadget restart 后环境变量不生效
原因:usb-gadget restart 通过命令文件发给后台守护进程,守护进程用的是自己启动时的环境变量。
解决: 修改 /etc/profile.d/usb-gadget.sh,然后重启整个服务(S50usb-gadget.sh stop/start)。
5.2 USB_FUNCS="" 设置后 ADB 还在
原因: 框架第 840 行有默认值保护:
USB_FUNCS=${USB_FUNCS:-adb}# 空值会被覆盖为 adb解决: 改为 USB_FUNCS=${USB_FUNCS:-}(去掉默认值)。
5.3 restart 后自定义 function 消失
原因:usb_post_init_hook() 只在 gadget 目录不存在时调用(首次启动)。restart 时 gadget 目录已存在,usb_init() 不执行,hook 不会被调用。
解决: 不要用 usb_post_init_hook() 创建 function。把 function 注册到 USB_FUNCS,用 _prepare() 函数配置,框架会在每次 start 时自动创建。
5.4 echo none > UDC 后自动恢复
原因: udev 规则 SUBSYSTEM=="udc" 触发 usb-gadget update 重新绑定。
解决: 先 rm /var/run/usb-gadget/funcs,再写 UDC。
5.5 删除 function symlink 后绑 UDC 报 Device or resource busy
原因: configfs 要求 configs/b.1/ 下至少有一个 function symlink。
解决: 先链接一个其他 function 进去,再删除目标 symlink。
5.6 USB 反复断开重连
原因:usb_post_prepare_hook 在每次 usb-gadget update(udev 触发)时都执行,可能导致反复操作。
解决: 不要用 usb_post_prepare_hook 创建/删除 function。用 USB_FUNCS + _prepare() 让框架管理生命周期。
5.7 日志查看
cat /var/log/usb-gadget.log | tail -30
关键日志:
Run stage: usb_pre_init_hook # 加载内核模块Run stage: usb_init # 框架初始化 gadgetPreparing instance: customer_audio.3 # 框架创建自定义 functionRun stage: customer_audio_prepare # 你的 prepare 函数执行Starting functions: adb customer_comm # UDC 绑定后的 function 列表
六、USB_FUNCS 变量传递链路
defconfig install.sh (编译时) 板子运行时───────── ──────────────── ──────────RK_USB_ADBD=y ──┐RK_USB_ACM=y ──┤RK_USB_UVC=y ──┤... ├──→ usb_funcs() ──→ /etc/profile.d/usb-gadget.shRK_USB_EXTRA="customer_ ──┘ export USB_FUNCS="adb customer_comm ..." comm customer_hid ..." │ ▼ source /etc/profile │ ▼ usb_prepare() 读取 USB_FUNCS │ ▼ usb_start() 遍历创建 function