首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

项目自定义 Function:f_bytrans 与 f_audio_raw

项目自定义 Function:f_bytrans 与 f_audio_raw

本树function/Makefile第 53-56 行无条件以obj-m编译两个 2024 年新增的私有 Function,上游内核不存在,是本项目(YPC Encode)的扩展。两者结构相似:ConfigFS function + 字符设备 + kfifo,用户态经/dev节点与 Host 交换数据。

1. f_bytrans(字节透传,/dev/msgtrans*

1.1 USB 接口

单接口 Vendor Specific(class 0xFF),3 个端点(f_bytrans.c:63):

端点 FS HS SS
Bulk IN 64B 512B 1024B + burst 15
Bulk OUT 64B 512B 1024B + burst 15
Interrupt IN 64B/interval16 512B/interval4 1024B/interval2

Interrupt IN 用于向 Host 上报RX_FIFO_FULL(0xA0)/TX_FIFO_FULL(0xA1)流控事件(宏定义 36-37 行)。

1.2 核心结构与资源

struct f_bytrans(213 行):

  • tx_fifo/rx_fifo:各 512KB kfifo(FIFO_SIZE=524288);
  • send_thread:内核线程bytrans_send,从 tx_fifo 取数据组 bulk IN 请求(847 行);
  • wq:workqueue 处理 bulk OUT completion 入 rx_fifo;
  • 包缓冲MAX_PACKET_SIZE=1024、每 URB MAX_PACKET_COUNT=32事务;
  • 字符设备:cdev_add于 901 行,device_create于 908 行,节点名msgtrans<minor>,class bytrans,minor 由 IDA 分配。

1.3 数据路径

1
2
3
4
5
6
Host -> bulk OUT -> bytrans_bulk_out_complete -> workqueue -> rx_fifo
-> 用户 read(/dev/msgtransN) (空时阻塞于 recv_wait)

用户 write(/dev/msgtransN) -> tx_fifo -> send_thread 唤醒
-> 组request queue到 bulk IN -> Host
FIFO 满 -> interrupt IN 通知 Host 暂停

1.4 生命周期与注册

  • bytrans_alloc_inst_fixed()(989 行)创建 instance 并初始化 configfs item(item_ops 966 行、attrs 977 行);
  • bytrans_alloc()(787 行)创建 function,function.name = "bytrans"(809 行);
  • DECLARE_USB_FUNCTION_INIT(bytrans, ...)(1011 行)注册,ConfigFS 用法:
1
2
mkdir functions/bytrans.0
ln -s functions/bytrans.0 configs/c.1/
  • 释放路径bytrans_free_inst()(778 行);unbind 时kthread_stop(937 行)。

1.5 审阅要点(潜在风险)

  • send_thread/workqueue 与set_alt/disable并发:断开时在途 request 与 kfifo 状态需要严格同步,注意online标志的内存序;
  • 代码含大量BYTRANS_DEBUG pr_info,量产应降级;
  • FIFO 满策略依赖 Host 配合中断通知,Host 端不理会时会丢数据或阻塞;
  • 字符设备无并发 open 保护说明,多进程同时读写语义未定义,应用侧应单实例使用。

2. f_audio_raw(原始音频,/dev/audio_raw.*

2.1 设计动机

绕开 UAC 标准协议栈,用 Vendor Specific 接口 + isochronous 端点直接传原始音频字节,Host 侧配套私有驱动/应用。相比 f_uac2 省去 ALSA 和 class request 复杂度,但失去标准 Host 兼容性。

2.2 USB 接口

单接口两个 alternate setting(116-137 行):

  • alt 0:无端点(空闲);
  • alt 1:isoc IN + isoc OUT 各一(FS 1023B/interval1,HS 1024B/interval4,SS 1024B/interval2 加 companion)。

audio_raw_set_alt()(687 行)在 alt 1 时使能端点、预队 OUT 请求并置online/rx_enabled/tx_enabled

2.3 数据面

struct f_audio_raw(48 行起):

  • fifo_rx:64KB kfifo(FIFO_SIZE37 行);
  • IN/OUT 各MAX_URBS=32个预分配 request;
  • 读写超时WRITE_TIMEOUT_MS/READ_TIMEOUT_MS = 5000
1
2
3
4
5
6
Host -> isoc OUT complete (274行) -> kfifo_in(fifo_rx) -> read_wait唤醒
用户 read (325行): 数据不足则等待(kfifo_len>=len 或 offline)
kfifo_to_user (360行)
用户 write (366行): 取空闲 in_req 填数据 queue 到 isoc IN
无空闲request时等待 write_wait
poll (453行): fifo_rx 有数据 -> EPOLLIN;有空闲URB -> EPOLLOUT

isoc 无重传:OUT 侧 fifo 满时新数据直接丢弃(281 行仅打 debug),IN 侧欠载表现为 Host 收流中断。实时性依赖应用及时消费/供给。

2.4 字符设备与实例

  • class audio_raw(862 行创建),chrdev region 256 个 minor(873 行);
  • 每实例节点名audio_raw.<instance_id>(964 行),function.name = "audio_raw"(968 行);
  • audio_raw_alloc_inst()(847 行)分配实例,DECLARE_USB_FUNCTION_INIT于 993 行;
  • ConfigFS:mkdir functions/audio_raw.0后同常规流程。

2.5 审阅要点

  • 文件为新写代码,含中文注释和 debug 日志,风格与上游不一致(缩进混用),合入主线前需清理;
  • offline 竞争:read/write在等待中检查!fa->online退出(352/398 行),但 completion 与 disable 的时序仍需重点测试拔线场景;
  • isoc 带宽在 HS interval4 下约 1024B/500µs≈2MB/s,设计音频格式时须核对是否够用;
  • OUT 丢弃策略意味着不能承载不容忍丢包的数据,只适合音频类容错流。

3. 与标准方案的选型对照

需求 建议
标准 Host 免驱音频 f_uac1/f_uac2
私有低延迟音频流、自控协议 f_audio_raw(本树方案)
可靠字节/消息通道 f_bytrans(bulk,有流控) 或 FunctionFS
用户态完整协议栈 FunctionFS

两个自定义 function 均为obj-m,部署需确认模块随镜像安装并在组装 gadget 前 modprobe。

RK3588 Gadget 部署、DWC3 集成与排障

RK3588 Gadget 部署、DWC3 集成与排障

1. 硬件路径

RK3588 的 Device 模式由 DWC3 控制器提供,驱动在drivers/usb/dwc3(详见linuxDoc/drivers/usb/04-DWC3双角色核心与RK3588集成.md),本目录只提供其上的 UDC 框架和 function。

SoC DTS(rk3588s.dtsi):

  • usbdrd3_0(3330 行):rockchip,rk3588-dwc3glue,子节点usb@fc000000(3340 行)snps,dwc3dr_mode = "otg",PHY 为u2phy0_otg+usbdp_phy0_u3(USB2+USB3/DP Combo);
  • 另一实例usb@fcd00000(3450 行)dr_mode = "host",不做 gadget。

因此 gadget 只能在 OTG 口(通常是 Type-C 调试/OTG 口)工作,UDC 名即fc000000.usb

2. 角色切换链

1
2
3
4
5
Type-C/extcon 或 role-switch 事件
-> dwc3 drd 切换到 device 模式
-> dwc3 gadget 注册 usb_add_gadget()
-> /sys/class/udc/fc000000.usb 出现
-> ConfigFS 写 UDC -> bind -> pullup

dr_mode="otg"时若无角色事件(缺 extcon/typec 配置或插的是 host 线),UDC 可能不进入 device 模式;板级 DTS 有时直接固定dr_mode="peripheral"规避。

3. 内核配置

三份 Rockchip defconfig 的 gadget 相关项:

1
2
3
4
5
6
7
8
9
10
CONFIG_USB_GADGET=y
CONFIG_USB_GADGET_DEBUG_FILES=y
CONFIG_USB_GADGET_VBUS_DRAW=500
CONFIG_USB_CONFIGFS=y
CONFIG_USB_CONFIGFS_UEVENT=y (Android扩展)
CONFIG_USB_CONFIGFS_ACM=y
CONFIG_USB_CONFIGFS_MASS_STORAGE=y
CONFIG_USB_CONFIGFS_F_FS=y
CONFIG_USB_CONFIGFS_F_UVC=y (仅 rockchip_linux)
CONFIG_USB_CONFIGFS_F_UAC1/UAC2/F_HID=y (仅 cpcem)

未启用任何 legacy gadget;网络 function(NCM/ECM/RNDIS)默认全关,需要时补配置。自定义usb_f_bytrans.ko/usb_f_audio_raw.ko为强制模块,确认打包进 rootfs。

4. 标准部署脚本模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
modprobe usb_f_bytrans 2>/dev/null   # 如需自定义function
mount -t configfs none /sys/kernel/config 2>/dev/null
cd /sys/kernel/config/usb_gadget
mkdir -p g1 && cd g1
echo 0x2207 > idVendor # Rockchip VID
echo 0x0011 > idProduct
mkdir -p strings/0x409
echo "0123456789ABCDEF" > strings/0x409/serialnumber
echo "Rockchip" > strings/0x409/manufacturer
echo "RK3588 Device" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "config1" > configs/c.1/strings/0x409/configuration
echo 500 > configs/c.1/MaxPower
mkdir functions/acm.usb0
ln -s functions/acm.usb0 configs/c.1/
ls /sys/class/udc/ # 确认 fc000000.usb
echo fc000000.usb > UDC

含 ffs 时先 mount functionfs、等 daemon 写完描述符再写 UDC。

5. 分层排障

1
2
3
4
5
6
7
8
9
10
A. /sys/class/udc 为空
-> DWC3 probe / PHY / 电源域 / dr_mode / 角色切换(extcon,typec)
B. 写 UDC 失败
-> 名字、UDC被占用、config缺function、ffs未就绪、端点不足
C. Host 不枚举
-> pullup是否执行(state属性)、VBUS、线缆、EP0日志(dmesg dwc3)
D. 枚举成功某function不工作
-> set_alt是否被调、端点enable、function自身日志
E. 传输错误/慢
-> 协商速度(current_speed)、描述符速度组、DMA/中断、Host侧驱动

常用命令:

1
2
3
4
cat /sys/class/udc/fc000000.usb/state
cat /sys/class/udc/fc000000.usb/current_speed
dmesg | grep -iE 'dwc3|gadget|configfs'
cat /proc/interrupts | grep dwc3

Host 侧配合lsusb -vusbmon抓包定位描述符与协议问题。

6. 速度与带宽核对

链路 bulk 理论 实际预期
HS (480Mbps) 512B×13×8/ms ≈ 53MB/s 35-42MB/s
SS (5Gbps) 1024B×burst 300-450MB/s

OTG0 走 USBDP Combo PHY 支持 USB3;若板级仅接 USB2 差分线,则永远协商 HS,isoc 类 function(UVC 高分辨率、audio_raw 多声道)要按 HS 带宽重新核算。

7. 电源与挂起

  • USB_GADGET_VBUS_DRAW=500:配置态最大取电 500mA,从 VBUS 取电的设计要处理 SUSPEND(composite_suspend 分发后应降至 2.5mA 规范值);
  • 自供电设备在描述符标 self-powered,并实现get_status正确应答;
  • 系统休眠时 DWC3 controller suspend 与 gadget 状态恢复由 dwc3 glue 处理,恢复后 Host 可能重新枚举,用户态服务要能重放 ConfigFS 状态。

8. 安全清单

  • ConfigFS 目录仅 root 写;
  • 量产固件固定 VID/PID 与 function 集合,不留任意组装接口;
  • ADB(ffs)默认关闭或强认证;
  • mass_storage 避免导出系统分区、优先只读;
  • 自定义 function(bytrans/audio_raw)的/dev节点设专用属组权限,Host 输入视为不可信。

u_audio 与 f_uac2 的匹配机制

u_audio 与 f_uac2 的匹配机制

源码:rk3588/kernel-6.1/drivers/usb/gadget/function/u_audio.cu_audio.hf_uac2.c)。

1. 两个文件的分工

u_audio.cf_uac2.c不是通过总线或 compatible 之类的动态“匹配”关联的,而是编译期就确定的分层调用关系,中间的桥梁是u_audio.h中定义的struct g_audio

1
2
3
f_uac2.c    UAC2 协议层:描述符、EP0 类请求、Alt Setting、Feature Unit
| 通过 struct g_audio + u_audio.h API 衔接
u_audio.c 通用音频引擎:虚拟 ALSA 声卡、ISO 请求队列、环形缓冲、反馈端点

u_audio.c同时服务于f_uac1.cf_uac2.c(以及本树的衍生 Function);它不理解 UAC1/UAC2 协议细节,只负责“把 ISO 端点数据和一块 ALSA 声卡对接起来”。

2. 结构上的嵌入关系

匹配的核心是两层container_of嵌套:

1
#include "u_audio.h"

f_uac2.c中的私有对象把g_audio作为第一个成员内嵌(64 行附近):

1
2
3
4
struct f_uac2 {
struct g_audio g_audio; /* 内嵌通用音频对象 */
...
};

g_audio内部又内嵌了 composite 框架的struct usb_function

1
2
struct usb_function func;
struct usb_gadget *gadget;

因此从任意一层都能找到另一层:

  • composite 回调收到usb_function *f后,func_to_g_audio(f)u_audio.h:121)用container_of得到g_audio
  • f_uac2.c再用func_to_uac2()(79 行)从同一个usb_function得到f_uac2
  • u_audio.c反向通过g_audio->uacuac->audio_dev在 ALSA 对象与g_audio之间互查。

这就是“匹配”的本质:同一块内存的三层视图(f_uac2 ⊃ g_audio ⊃ usb_function),用 container_of 互相转换

3. 绑定期的参数移交

afunc_bind()f_uac2.c:1036)完成协议层到引擎层的一次性移交:

1
2
3
4
5
6
7
afunc_bind()
-> usb_ep_autoconfig() 分配 out_ep / in_ep / in_ep_fback (1238~1264行)
-> 计算 in/out_ep_maxpsize(FS/HS/SS 取最大) (1266~1276行)
-> 把 configfs 参数 (f_uac2_opts) 拷入 agdev->params (1300~1325行)
p_chmask/p_srates/p_ssize、c_*、Feature Unit、req_number、fb_max
-> agdev->notify = afunc_notify(音量/静音变化回调 UAC2 中断端点)(1328行)
-> g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget") (1330行)

g_audio_setup()u_audio.c:1426)只依赖g_audio里填好的字段,不回头访问任何 UAC2 私有数据:

  1. 分配snd_uac_chip,双向指针挂接(g_audio->uacuac->audio_dev);
  2. c_chmask/p_chmask为捕获/播放各预分配req_numberusb_request指针数组和数据缓冲(大小max_psize来自 bind 阶段算好的out/in_ep_maxpsize);
  3. snd_card_new() + snd_pcm_new()创建虚拟 ALSA 声卡,PCM 名即传入的"UAC2 PCM"
  4. 按需创建反馈/pitch/音量/静音等 kcontrol。

所以主机侧看到的是 USB 描述符(f_uac2 生成),设备侧应用看到的是一块普通 ALSA 声卡(u_audio 生成),二者共享同一组端点和参数。

4. 运行期的控制反转

4.1 协议层驱动引擎层(下行)

Host 的 SetInterface/EP0 请求先到达 f_uac2,再转调 u_audio 导出函数:

f_uac2 事件 位置 调用的 u_audio API
SetAlt(OUT 流, alt=1/0) afunc_set_alt:1454~1456 u_audio_start_capture / u_audio_stop_capture
SetAlt(IN 流, alt=1/0) afunc_set_alt:1461~1463 u_audio_start_playback / u_audio_stop_playback
Disable afunc_disable:1499~1500 u_audio_stop_capture + u_audio_stop_playback
Suspend afunc_suspend:1510 u_audio_suspend
EP0 CUR 读采样率/音量/静音 afunc_setup路径 1527~1571 u_audio_get_*
EP0 CUR 写采样率 1706~1708 u_audio_set_playback/capture_srate
EP0 CUR 写音量/静音 out_rq_cur:1740~1748 u_audio_set_mute / u_audio_set_volume

u_audio_start_capture()u_audio.c:594)拿到的是g_audio里 bind 阶段存好的out_epconfig_ep_by_speed()usb_ep_enable()→把预分配的请求批量usb_ep_queue(),每个请求的 complete 都指向u_audio_iso_complete(647 行);若存在in_ep_fback还会建立异步反馈端点并按当前采样率初始化反馈值(696 行)。

4.2 引擎层通知协议层(上行)

反向只有一个钩子:g_audio->notify函数指针(u_audio.h:113)。ALSA 侧用户改变音量/静音时,u_audio 通过它回调afunc_notify()f_uac2.c:1359),由 f_uac2 在 UAC2 中断端点上发出 Interrupt Data Message 通知 Host。u_audio 不知道也不需要知道这个通知在 USB 上长什么样。

5. 数据面匹配

数据搬运完全在 u_audio 内完成,f_uac2 不参与:

1
2
3
4
5
Host ISO OUT ──> out_ep ──> u_audio_iso_complete (u_audio.c:153)
└─ memcpy 到 ALSA runtime->dma_area 环形缓冲
└─ snd_pcm_period_elapsed() 唤醒 arecord
aplay 写入 ALSA ──> playback 方向在 complete 里按采样率/残差算包长后
memcpy 到 req->buf ──> in_ep ──> Host ISO IN

请求完成后立即重新入队,形成常驻的 ISO 流水线;req_number(configfs 可调)决定流水线深度。

6. 解绑

afunc_unbind()f_uac2.c:2204)调用g_audio_cleanup()u_audio.c:1705)注销 ALSA 声卡并释放请求缓冲,然后 f_uac2 释放描述符。顺序上必须先停流(disable/set_alt 0 已调用 stop)再 cleanup。

7. 小结

问题 答案
怎么“匹配”的 编译期分层:f_uac2内嵌struct g_audio#include "u_audio.h"直接函数调用,无动态匹配
对象如何互转 func_to_g_audio()/func_to_uac2()两层container_of
参数何时传递 afunc_bind()把 configfs opts 拷入agdev->params后调用g_audio_setup()
控制流方向 f_uac2 → u_audio:start/stop/set/get 导出函数;u_audio → f_uac2:仅notify回调
数据流归属 完全在 u_audio(ISO 请求 ↔ ALSA 环形缓冲)
复用关系 同一 u_audio 引擎被 f_uac1/f_uac2 共用,协议差异全部隔离在 f_uac* 中

USB Gadget 关键调用链与函数索引

USB Gadget 关键调用链与函数索引

按当前 RK3588 Linux 6.1 源码整理,行号可能随厂商树变动,以函数名为稳定入口。RK3588 的硬件 UDC 是drivers/usb/dwc3/gadget.c(DWC3 gadget 模式);drivers/usb/gadget/udc/目录下除core.c外的 SoC UDC 驱动均与 RK3588 无关。

1. 分层与文件归属

1
2
3
4
5
6
用户态 ConfigFS (/sys/kernel/config/usb_gadget/)
-> configfs.c 组装 gadget、写 UDC 触发绑定
-> composite.c Chapter 9、config/function 生命周期
-> functions.c Function 注册表(usb_get_function_instance)
-> udc/core.c UDC 类、gadget bus、usb_ep_*/usb_gadget_* API
-> dwc3/gadget.c RK3588 真实 UDC(udc_start/pullup/queue)

2. UDC 上线(boot)

1
2
3
4
5
6
7
8
9
10
11
DTS usbdrd3_0 ("rockchip,rk3588-dwc3","rockchip,rk3399-dwc3")
-> dwc3-of-simple.c 匹配 rk3399-dwc3 fallback
-> of_platform_populate 子节点 "snps,dwc3"
-> dwc3_probe() dwc3/core.c:1976
-> dwc3_core_init_mode() dwc3/core.c:1504
dr_mode=peripheral -> dwc3_gadget_init()
dr_mode=otg -> dwc3_drd_init() 角色切到 DEVICE 后再 gadget_init
-> dwc3_gadget_init() dwc3/gadget.c:4697
-> usb_initialize_gadget() udc/core.c:1355
-> usb_add_gadget() udc/core.c:1378
-> /sys/class/udc/fc000000.usb(取 parent 设备名)

UDC 类初始化:usb_udc_init()udc/core.c,subsys_initcall)创建udc class 和gadget bus。

3. ConfigFS 组装与绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mkdir usb_gadget/g1
mkdir functions/uac2.usb0 -> function_make() configfs.c:623
-> usb_get_function_instance()
mkdir configs/c.1
ln -s functions/... configs/ -> config_usb_cfg_link() configfs.c:448
echo fc000000.usb > UDC -> gadget_dev_desc_UDC_store() configfs.c:293
-> usb_gadget_register_driver_owner() udc/core.c:1666
-> gadget bus match(按 udc_name)
-> gadget_bind_driver() udc/core.c:1578
-> configfs_composite_bind() configfs.c:1319
-> composite_dev_prepare()(分配 ep0 req)
-> 遍历 config 调 usb_add_function() composite.c:314
-> f->bind()(内部 usb_ep_autoconfig 认领端点)
-> usb_gadget_udc_start_locked() -> dwc3_gadget_start() dwc3/gadget.c:3008
-> pullup -> dwc3_gadget_pullup() dwc3/gadget.c:2786

注意:ConfigFS 的usb_composite_driver.bind是空操作,真正绑定逻辑在configfs_composite_bind(作为 gadget_driver.bind,见configfs.c:1718)。启用USB_CONFIGFS_UEVENT时 setup 入口是android_setup()configfs.c:1547),并通过android_usb class 发 CONNECTED/CONFIGURED uevent。

4. 枚举(EP0/Chapter 9)

1
2
3
4
5
6
7
8
Host SETUP -> DWC3 IRQ -> driver->setup
-> composite_setup() composite.c:1745
GET_DESCRIPTOR 填 cdev->req->buf
SET_CONFIGURATION -> set_config() composite.c:915
-> 每个 f->set_alt(f, intf, 0)
SET_INTERFACE -> f->set_alt()
类/厂商请求 -> f->setup()
-> composite_ep0_queue() -> usb_ep_queue(ep0)

状态机:bus reset 时usb_gadget_udc_reset()udc/core.c:1186)置DEFAULT;SET_ADDRESS/SET_CONFIG 在dwc3/ep0.c中推进到ADDRESS/CONFIGURED/sys/class/udc/<name>/state可读当前状态。

5. 数据面

1
2
3
4
5
6
7
8
9
10
11
12
Function set_alt:
config_ep_by_speed() composite.c:292 按速度选描述符
usb_ep_enable()
批量 usb_ep_queue() udc/core.c:288

完成(IRQ):
UDC -> usb_gadget_giveback_request() udc/core.c:989
-> req->complete()(原子上下文,通常立即重新入队)

取消/停流:
usb_ep_dequeue() udc/core.c:326 完成态 -ECONNRESET
usb_ep_disable() udc/core.c:156 未决请求 -ESHUTDOWN

请求分配:usb_ep_alloc_request()u_f.calloc_ep_req()。complete 回调不可睡眠,也不可递归造成入队死锁。

6. 断开与解绑

1
2
3
4
5
6
7
8
9
10
11
12
13
拔线/软断开:
usb_gadget_disconnect() udc/core.c
-> pullup(0)
-> composite_disconnect() composite.c:2204
-> reset_config() -> 每个 f->disable()

echo none > UDC:
unregister_gadget -> gadget_unbind_driver() udc/core.c:1633
-> disconnect -> driver->unbind
-> configfs_composite_unbind() configfs.c:1519
-> purge_configs_funcs()(f->unbind)
-> composite_dev_cleanup()
-> udc_stop

顺序不变量:先 pullup(0)/disable 停流,再 unbind 释放描述符与端点,最后 udc_stop。

7. 关键锁

位置 保护
udc_lock udc/core.c udc_list、driver 绑定
usb_udc.connect_lock udc/core.c started/connected/pullup
cdev->lock(spinlock) composite delayed_status、config 切换
gadget_info.lock/spinlock configfs.c 组装、setup 与 unbind 竞态
UDC 内部 spinlock dwc3 等 端点队列/硬件

8. RK3588 配置速查

三份 Rockchip defconfig 均启用USB_DWC3USB_GADGETUSB_CONFIGFS(+UEVENT)ACM/MASS_STORAGE/F_FS;差异:主配置多F_UVC,cpcem 多F_UAC1/F_UAC2/F_HIDudc/目录 SoC UDC 与USB_DUMMY_HCD均未启用。

DTS 角色:usbdrd_dwc3_0@fc000000默认dr_mode="otg"(gadget 口);usbhost_dwc3_0与 RK3588 完整版的 DRD1 默认 host。OTG 口需 Type-C role-switch 处于 device 角色,/sys/class/udc/下才有可绑定的 UDC。

运维三步:

1
2
3
ls /sys/class/udc/                       # 确认 UDC 名
echo fc000000.usb > $G/UDC # 绑定
cat /sys/class/udc/fc000000.usb/state # 观察枚举状态

Linux 6.1 USB Gadget 子系统文档索引

Linux 6.1 USB Gadget 子系统文档索引

源码:rk3588/kernel-6.1/drivers/usb/gadget/
平台:RK3588 / Rockchip Linux 6.1 厂商树(UDC 硬件为drivers/usb/dwc3

文档 内容
源码目录构建配置与模块索引.md 分层、Makefile/Kconfig、function 清单、defconfig 矩阵
01-UDC核心与Gadget设备模型.md usb_gadget/udc、端点API、usb_request、epautoconf
02-Composite框架EP0与描述符.md cdev/config/function、setup分发、SET_CONFIG、三速描述符
03-ConfigFS组装与Android-UEVENT扩展.md 目录模型、UDC绑定、os_desc、android_setup/uevent
04-FunctionFS与用户态Function.md ffs 描述符/事件/AIO、与ADB装配顺序
05-MassStorage存储Function.md BOT线程、LUN、数据一致性、f_tcm
06-串口与网络Function.md u_serial/ttyGS、u_ether/usb0、ACM/NCM/RNDIS选型
07-UAC音频与UVC视频Function.md u_audio/f_uac2、f_uvc/V4L2、isoc带宽
08-项目自定义Function-bytrans与audio_raw.md 本树私有 f_bytrans(/dev/msgtrans*)与 f_audio_raw
09-RK3588-DWC3部署与排障.md OTG口/UDC名、部署脚本、分层故障树、安全
10-u_audio与f_uac2匹配机制.md g_audio 嵌套/container_of、参数移交、控制与数据面边界
11-关键调用链与函数索引.md UDC上线/ConfigFS绑定/枚举/数据面/解绑全链路(带行号)、DWC3边界

相关文档:

  • linuxDoc/drivers/usb/04-DWC3双角色核心与RK3588集成.md(UDC 硬件层)
  • linuxDoc/drivers/usb/05-Gadget-UDC-Composite与ConfigFS.md(概览版,本目录为详析)
  • linuxDoc/drivers/usb/usbip/05-VUDC虚拟设备控制器与Gadget.md

推荐阅读:索引 → 01 → 02 → 03,然后按业务选 04~08,部署排障读 09。

1
2
3
ls /sys/class/udc/
cat /sys/kernel/config/usb_gadget/*/UDC
lsusb -v # Host 侧

drivers/usb/gadget 源码目录、构建配置与模块索引

drivers/usb/gadget 源码目录、构建配置与模块索引

1. 目录定位

drivers/usb/gadget实现 Linux 作为 USB 外设(Device/Peripheral) 时的软件栈。它不依赖 Host 侧CONFIG_USB,分为四层:

1
2
3
4
5
用户态 (ConfigFS / FunctionFS / /dev节点)
-> Function 驱动 (function/)
-> libcomposite (composite/configfs/epautoconf)
-> UDC 框架 (udc/core.c)
-> UDC 硬件驱动 (udc/*、或树外如 dwc3 gadget)

RK3588 的 UDC 硬件驱动不在本目录,而在drivers/usb/dwc3(DWC3 gadget 模式);本目录udc/下的 SoC 驱动均为其它平台。

2. 构建结构

顶层Makefile

1
2
3
CONFIG_USB_LIBCOMPOSITE -> libcomposite.o
= usbstring + config + epautoconf + composite + functions + configfs + u_f
CONFIG_USB_GADGET -> udc/ function/ legacy/

USB_LIBCOMPOSITEUSB_CONFIGFS或各 legacy gadget select,并强制CONFIGFS_FS

3. 顶层公共文件

文件 职责
composite.c Composite 框架:设备/配置/接口描述符、EP0 setup 分发、bind/unbind
configfs.c ConfigFS 组装 gadget、UDC 绑定、Android UEVENT 扩展
functions.c usb_function_driver注册表、按名称查找 function
epautoconf.c 端点自动分配(usb_ep_autoconfig*
config.c 描述符拷贝/组合辅助
usbstring.c UTF-8 到 UTF-16LE 字符串描述符
u_f.c alloc_ep_req()等 function 公共辅助

4. udc/ 目录

文件 职责
udc/core.c UDC 类核心:gadget 设备模型、/sys/class/udc、driver绑定、usb_ep_*/usb_gadget_* API
udc/dummy_hcd.c 软件模拟 HCD+UDC,本机回环测试
udc/trace.c tracepoint
其余 udc/*.c 各 SoC/PCI UDC(Renesas、Atmel、Aspeed vhub、BDC 等),RK3588 不使用

5. function/ 目录

分类 文件 说明
用户态桥 f_fs.c FunctionFS,ADB 等用户态协议
存储 f_mass_storage.c + storage_common.cf_tcm.c BOT 存储 / UAS target
串口类 u_serial.cf_acm.cf_serial.cf_obex.c /dev/ttyGS*
网络类 u_ether.cf_ecm.cf_ncm.cf_rndis.c+rndis.cf_eem.cf_subset.cf_phonet.c usb0网络接口
音频 u_audio.cf_uac1.cf_uac2.cf_uac1_legacy.c+u_uac1_legacy.c 虚拟 ALSA 声卡
视频 f_uvc.cuvc_v4l2.cuvc_video.cuvc_queue.cuvc_configfs.c V4L2 输出设备
其它 f_hid.cf_midi.cf_printer.cf_sourcesink.c/f_loopback.c HID/MIDI/打印/测试
本项目自定义 f_bytrans.c 字节透传,/dev/msgtrans*,无条件obj-m
本项目自定义 f_audio_raw.c 原始音频传输,kfifo+字符设备,无条件obj-m

f_bytrans.cf_audio_raw.c是本树 2024 年新增的项目私有 Function(function/Makefile第 53-56 行直接obj-m,不受 Kconfig 控制),上游内核不存在。

6. legacy/ 目录

预组装的一体化 gadget 模块(g_serialg_etherg_mass_storageg_ffsg_zerog_webcamraw_gadget、GadgetFS inode.c等)。现代系统一般用 ConfigFS 替代;raw_gadget用于 fuzzing/协议实验。

7. Rockchip defconfig 配置矩阵

配置 linux electric cpcem
USB_GADGET y y y
USB_GADGET_VBUS_DRAW 500 500 500
USB_CONFIGFS y y y
USB_CONFIGFS_UEVENT y y y
USB_CONFIGFS_ACM y y y
USB_CONFIGFS_MASS_STORAGE y y y
USB_CONFIGFS_F_FS y y y
USB_CONFIGFS_F_UVC y - -
USB_CONFIGFS_F_UAC1/UAC2/HID - - y
USB_DWC3 y y y

所有配置都未启用 legacy gadget,运行时通过 ConfigFS 组装。

8. 阅读顺序

建议:udc/core.c(设备模型)→ composite.c(EP0/描述符)→ configfs.c(用户组装)→ 目标 function → RK3588 DWC3 集成。只看 function 而不懂 composite 的 setup 分发,无法解释描述符和 SET_ALT 行为。

Linux 6.1 USB 子系统文档索引

Linux 6.1 USB 子系统文档索引

源码:rk3588/kernel-6.1/drivers/usb/

结构化专题

文档 主题
USB子系统架构与源码总览.md Host、Gadget、Type-C总体分层
源码目录与模块索引.md 839个文件的目录和构建边界
01-USB-Core设备模型枚举与匹配.md device/interface、Hub枚举和probe
02-URB端点DMA与传输生命周期.md URB submit/giveback、anchor和DMA
03-HCD-Root-Hub与xHCI-EHCI-OHCI.md Host controller架构
04-DWC3双角色核心与RK3588集成.md DWC3 Host/Gadget/DRD
05-Gadget-UDC-Composite与ConfigFS.md UDC、Function、复合设备
06-Type-C-PD-TCPM与Role-Switch.md Type-C、PD和角色切换
07-USB存储UAS串口与常用类驱动.md Storage、Serial、ACM等
08-电源管理Autosuspend与远程唤醒.md Runtime PM和系统休眠
09-RK3588-USB控制器PHY与端口拓扑.md DWC3、EHCI/OHCI、USB2/DP PHY
10-RK3588-DeviceTree与板级配置.md DTS节点、VBUS和板型差异
11-USBMon-DebugFS-Trace与故障排查.md 抓包、枚举和链路问题
12-USB安全授权USBIP与产品加固.md 授权、usbfs、USB/IP与安全
usbip/README.md USB/IP Stub、VHCI、VUDC、协议及安全详解
gadget/README.md Gadget UDC/Composite/ConfigFS/Function 及项目自定义 f_bytrans、f_audio_raw 详解

已有逐目录详解

RK3588关键路径是DWC3+xHCI、两组EHCI/OHCI、Rockchip USB2 PHY及USB/DP combo PHY;具体启用端口由最终板级DT决定。

源码目录、构建配置与对象模型

源码目录、构建配置与对象模型

1. 源文件

drivers/usb/usbip是扁平目录,共22个文件、约8772行。文件集合与标准Linux 6.1 USB/IP实现一致,未发现Rockchip专用源文件、版权标记或平台条件分支。

文件 职责
usbip_common.[ch] 协议结构、PDU/URB转换、字节序、socket收包、ISO辅助、调试
usbip_event.c 全局单线程event workqueue
stub_main.c stub模块入口、busid表、driver sysfs
stub_dev.c USB device driver、probe/disconnect、socket sysfs、EH回调
stub_rx.c 接收SUBMIT/UNLINK,构造并提交真实URB
stub_tx.c URB completion、返回PDU和unlink结果
stub.h stub_devicestub_privbus_id_priv
vhci_hcd.c 虚拟HCD、Root Hub、端口、enqueue/dequeue、平台设备
vhci_sysfs.c attach/detach/status/nports
vhci_tx.cvhci_rx.c VHCI请求发送与完成接收
vhci.h VHCI controller/device/request对象
vudc_main.c VUDC模块入口和虚拟controller实例
vudc_dev.c gadget/endpoint ops及UDC注册
vudc_sysfs.c socket、状态和设备描述符接口
vudc_rx.cvudc_tx.c Host PDU收发
vudc_transfer.c URB与gadget request撮合、定时调度
vudc.h VUDC、endpoint、request和传输对象

2. Makefile

1
2
3
4
5
usbip-core = usbip_common + usbip_event
usbip-host = stub_dev + stub_main + stub_rx + stub_tx
vhci-hcd = vhci_sysfs + vhci_tx + vhci_rx + vhci_hcd
usbip-vudc = vudc_dev + vudc_sysfs + vudc_tx + vudc_rx
+ vudc_transfer + vudc_main

CONFIG_USBIP_DEBUG为所有本目录源文件增加-DDEBUG

3. Kconfig

配置 类型/依赖 说明
USBIP_CORE tristate,依赖NET select USB_COMMONSGL_ALLOC
USBIP_HOST tristate,依赖core和USB 物理设备服务端
USBIP_VHCI_HCD tristate,依赖core和USB 客户端
USBIP_VHCI_HC_PORTS 1–15,默认8 每个USB2/USB3 RH的端口数
USBIP_VHCI_NR_HCS 1–128,默认1 虚拟controller数量
USBIP_VUDC tristate,依赖core和USB_GADGET 软件设备服务端
USBIP_DEBUG bool 编译调试消息

每个VHCI controller实际包含一对primary/shared HCD,即一个USB2 Root Hub和一个USB3 Root Hub,各有HC_PORTS个端口。

4. 公共对象

struct usbip_device嵌入stub、VHCI和VUDC实例,统一保存:

  • side和status;
  • status spinlock;
  • sysfs路径mutex;
  • userspace传入的sockfd及内核socket;
  • RX/TX kthread;
  • event位图和等待队列;
  • shutdown/reset/unusable回调;
  • 可选KCOV remote handle。

它不是Linux device model对象,而是三类前端共享的连接状态容器。

5. Stub对象

  • stub_device:真实usb_device、devid、请求链表和TX waitqueue;
  • stub_priv:一次远端SUBMIT,保存一个或多个本地URB、SG及完成计数;
  • stub_unlink:待发送的UNLINK回复;
  • bus_id_priv:最多16项的可导出busid表。

6. VHCI对象

  • vhci:一对USB2/USB3 HCD及全局锁;
  • vhci_hcd:每个HCD的port status、seqnum和vdev数组;
  • vhci_device:一个远端设备/Root Hub端口/连接;
  • vhci_priv:URB与USB/IP seqnum的映射;
  • vhci_unlink:取消请求及目标seqnum。

7. VUDC对象

  • vudcusb_gadget、gadget driver、endpoint集合和连接状态;
  • vep:虚拟usb_ep
  • vrequest:虚拟usb_request
  • urbp:远端Host发来的URB;
  • tx_item:SUBMIT或UNLINK完成队列项;
  • transfer_timer:模拟帧推进和传输撮合。

VUDC模块参数num默认1,用于创建多个usbip-vudc.N平台设备。

8. 本树构建状态

arch/arm64/configs中的Rockchip defconfig未发现CONFIG_USBIP_*。源码存在不代表镜像包含模块;需检查最终.configmodules.dep,产品配置还应显式评估安全影响。

USB/IP 协议、PDU 与公共核心

USB/IP 协议、PDU 与公共核心

1. 两层协议

用户态管理协议负责DEVLIST和IMPORT,定义在tools/usb/usbip/src/usbip_network.h。导入完成后,socket交给内核,使用usbip_common.h中的URB协议。

默认服务端端口为TCP 3240。用户态在连接后设置TCP_NODELAY,降低小控制传输的Nagle延迟。

2. 四类内核命令

命令 方向 语义
CMD_SUBMIT 1 VHCI→Stub/VUDC 提交URB
CMD_UNLINK 2 VHCI→Stub/VUDC 取消指定seqnum
RET_SUBMIT 3 Stub/VUDC→VHCI URB完成
RET_UNLINK 4 Stub/VUDC→VHCI 取消结果

数据面usbip_header固定48字节:20字节basic header加28字节最大union。基本头包含commandseqnumdeviddirectionepseqnum标识请求;Stub使用(busnum << 16) | devnum形成devid。

3. SUBMIT字段

usbip_header_cmd_submit序列化:

  • 可跨网络表达的URB transfer flags;
  • transfer buffer length;
  • start frame;
  • ISO packet数量;
  • interval;
  • 8字节control setup packet。

并非所有本地URB flag都可直接传输,tweak_transfer_flags()会过滤DMA映射等仅本机有效的标志。

RET_SUBMIT返回status、actual length、frame、ISO packet数和error count。

4. Payload顺序

1
2
3
4
5
CMD_SUBMIT OUT:
header -> OUT data -> ISO descriptors

RET_SUBMIT IN:
header -> IN data -> ISO descriptors

非ISO传输可使用线性buffer或scatter-gather。公共层的usbip_recv_xbuff()和发送侧kvec/iov_iter负责数据搬运。

ISO descriptor包含offset、期望length、actual length和status。Stub限制单URB最多1024个ISO packets,并检查负数、越界及长度;VUDC还按endpoint max packet验证packet数。

5. 字节序

usbip_header_correct_endian()对全部32-bit头字段执行CPU与big-endian转换,ISO descriptor同样按网络序处理。Setup packet本身是USB Chapter 9格式,不作为五个普通32-bit字段转换。

解包顺序必须是:

  1. 完整接收固定头;
  2. 转为CPU字节序;
  3. 验证command和长度;
  4. 分配URB/buffer;
  5. 接收payload和ISO描述符。

远端给出的有符号长度和packet数量不能在验证前转换为分配大小。

6. usbip_common.c

关键函数:

  • usbip_pack_pdu():URB和command-specific header互转;
  • usbip_header_correct_endian():网络字节序;
  • usbip_recv():循环接收指定长度,处理EOF和signal;
  • usbip_alloc_iso_desc_pdu():创建ISO descriptor PDU;
  • usbip_recv_iso():接收并验证ISO结果;
  • usbip_recv_xbuff():接收IN/OUT payload;
  • usbip_pad_iso():整理非连续ISO IN数据;
  • dump/debug函数:输出header和URB字段。

usbip_debug_flag是可写模块参数,同时通过各设备的usbip_debug sysfs暴露位掩码。

7. Event Handler

公共层创建名为usbip_event的单线程workqueue。每个usbip_device以位图记录:

  • SHUTDOWN:先关闭socket/线程;
  • RESET:恢复到可重连状态;
  • UNUSABLE:永久标记不可用;
  • BYE:对象即将移除。

usbip_event_add()使用全局spinlock把设备去重后加入event list。worker持有该设备sysfs_lock,严格按shutdown→reset→unusable顺序调用前端回调,并唤醒usbip_stop_eh()等待者。

这是全局串行恢复路径:一个设备的慢shutdown会延迟其它USB/IP连接的event处理。

8. 协议边界

内核URB协议没有独立session认证、MAC、加密、重放保护或每PDU长度字段。消息边界依赖command对应的固定头及其中长度。TCP只保证有序字节流,不保证对端可信。

Stub 物理设备导出与 URB 代理

Stub 物理设备导出与 URB 代理

1. 驱动注册

stub_main.c通过usb_register_device_driver()注册stub_driver。这是整设备usb_device_driver,不是普通usb_driver接口驱动,因此导出复合设备时会接管全部interface。

Hub被排除,不能把USB拓扑中的Hub作为普通设备导出。

2. busid允许表

match_busid是usbip-host driver属性,接受:

1
2
add 1-2.3
del 1-2.3

固定表最多16项,busid buffer为32字节。每项状态区分OTHER、REMOV、ADDED、ALLOC,并保存interface计数、设备引用和shutdown标志。

usbip bind --busid的本质是:

  1. 将busid加入match_busid
  2. unbind原interface drivers;
    3.触发设备重新匹配到usbip-host。

解除导出后rebind属性促使原class driver重新探测。

3. Probe

stub_probe()检查busid表,未列入允许表的设备返回不匹配。成功后:

  • 分配stub_device
  • 引用真实usb_device
  • 设置devid;
  • 初始化usbip_device状态、锁和EH;
  • 初始化priv_init/priv_tx/priv_free及unlink链表;
  • 创建设备属性usbip_statususbip_sockfdusbip_debug

此时设备只是“可导出”,尚未有数据连接。

4. Socket移交

usbipd完成IMPORT协商后,把已连接TCP socket fd写入设备的usbip_sockfd

1
2
3
4
5
6
userspace fd
-> sockfd_lookup()
-> kernel socket引用
-> create stub_rx/stub_tx
-> status = SDEV_ST_USED
-> wake_up_process()

写入-1请求断开。sysfs路径由sysfs_lock串行化,并检查设备当前状态,防止重复连接。

5. SUBMIT接收

stub_rx_loop()读取固定头并只接受CMD_SUBMIT/CMD_UNLINK。SUBMIT处理大致为:

1
2
3
4
5
6
7
8
recv header
-> validate endpoint/type/length/ISO count
-> alloc stub_priv + urb(s) + buffer/SG
-> unpack URB fields
-> receive OUT payload/ISO descriptors
-> tweak control requests
-> add priv_init
-> usb_submit_urb()

支持SG时,大buffer可能拆成多个本地URB;stub_priv记录num_urbscompleted_urbs,所有分片完成后才形成一个远端RET_SUBMIT。

6. 控制传输修正

远端Host看到的地址、configuration和interface状态不能机械复制。stub_rx.c识别并修正部分standard control request:

  • CLEAR_FEATURE(ENDPOINT_HALT)
  • SET_INTERFACE
  • SET_CONFIGURATION
  • device reset相关请求。

这些操作需要调用服务端USB Core API更新真实设备状态,而不仅是向endpoint 0提交原始URB。

7. 完成发送

真实URB completion stub_complete()在不可睡眠上下文中:

  • 聚合分片状态;
  • 将请求从priv_init移到priv_tx
  • 唤醒stub_tx_loop()

TX线程组装RET_SUBMIT:

  • header和status;
  • IN方向actual data;
  • ISO descriptor;
  • SG或线性buffer。

发送结束后对象进入priv_free并统一释放,避免completion直接执行socket I/O和复杂释放。

8. UNLINK竞态

CMD_UNLINK按目标seqnum搜索仍在priv_init的请求并调用usb_unlink_urb()。目标可能:

  • 仍在执行:异步取消,completion随后返回;
  • 已完成但尚未发送:需要避免重复giveback;
  • 已不在列表:返回对应错误状态。

unlink_txunlink_free保存RET_UNLINK,所有请求链表由priv_lock保护。

9. Disconnect

物理拔出、TCP错误或模块卸载触发EH:

  1. shutdown socket使RX/TX退出;
  2. stop并put线程;
  3. kill/cleanup未完成URB;
    4.释放socket引用;
  4. reset到AVAILABLE或标记ERROR;
  5. device remove时设置BYE并释放对象。

对客户端表现为远端USB设备断开,class driver必须能处理热拔插。