4. USB Gadget驱动开发实战:配置UVC、UAC、RNDIS、Mass Storage功能,实现复合设备

好,咱们进入第四章。这一章我打算聊点实在的——怎么把RK芯片变成一个“多功能USB小钢炮”。说白了,就是让设备插上电脑后,既能当摄像头(UVC),又能当麦克风(UAC),还能当网卡(RNDIS)和U盘(Mass Storage)。这种复合设备在视频会议、工业采集、车载系统里非常常见。

我个人习惯把Gadget驱动比作“乐高积木”。每个功能是一块积木,你要做的就是选好积木,然后拼在一起。但拼的时候要注意——有些积木会打架,比如带宽分配和端点冲突。嗯,咱们一步步来。

4.1 复合设备的设计思路

先看整体架构。RK平台的USB Gadget基于ConfigFS和FunctionFS。用户空间通过写配置文件来“组装”设备,内核负责底层传输。

核心原则:每个功能(UVC/UAC/RNDIS/Mass Storage)都是一个独立的Function,通过ConfigFS挂载到同一个配置描述符下。USB主机枚举时,会看到这个配置下包含多个接口(Interface),每个接口对应一个功能。

下面这张图展示了复合设备的逻辑结构,我建议你先看明白再动手。

RK USB Gadget 复合设备结构图 USB Device 配置描述符 (Configuration 1) UVC 视频类 (摄像头) UAC 音频类 (麦克风/扬声器) RNDIS 以太网 (远程NDIS) Mass Storage 大容量存储 (U盘) UDC (USB Device Controller) RK 内部 USB 2.0/3.0 OTG 控制器 说明: 每个Function独立占用一组接口(Interface)和端点(Endpoint)。 复合设备的总端点数量不能超过UDC硬件限制(RK3399最多32个端点)。

经验之谈:我在RK3399项目上做过一个四合一复合设备。刚开始没注意端点分配,结果UVC和UAC抢同一个Bulk端点,导致视频流卡顿。后来我强制给每个功能分配独立的端点区间,问题就解决了。你设计时最好先画一张端点分配表。

4.2 各功能模块的配置要点

下面逐个拆解。每个功能我都给出关键配置步骤和避坑点。

4.2.1 UVC(USB Video Class)

UVC让设备模拟摄像头。RK平台通常用g_webcam或uvc-gadget驱动。配置时要注意分辨率和帧率匹配。

关键配置步骤:

  1. 加载内核模块:modprobe uvc-gadget
  2. 通过ConfigFS创建UVC功能:mkdir /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0
  3. 设置格式描述符(支持的分辨率列表)
  4. 启动用户空间流服务:uvc-gadget -u /dev/video0 -r 1920x1080

注意:UVC的实时性要求高。我曾经在RK3288上遇到视频帧率不稳的问题,后来发现是DMA缓冲区太小。建议把vb2-videobuf2-dma-contig的缓冲区数量调到4以上。

4.2.2 UAC(USB Audio Class)

UAC让设备变成USB声卡。RK平台支持UAC1(全速)和UAC2(高速)。我建议用UAC2,延迟更低。

配置示例:

# 创建UAC功能
mkdir /sys/kernel/config/usb_gadget/g1/functions/uac2.usb0

# 设置音频参数(采样率、通道数)
echo 48000 > /sys/kernel/config/usb_gadget/g1/functions/uac2.usb0/p_srate
echo 2 > /sys/kernel/config/usb_gadget/g1/functions/uac2.usb0/p_chmask

# 绑定到配置
ln -s /sys/kernel/config/usb_gadget/g1/functions/uac2.usb0 /sys/kernel/config/usb_gadget/g1/configs/c.1/

避坑指南:UAC和UVC同时工作时,音频流可能被视频流挤占带宽。我曾经在RK3568上遇到音频断断续续的问题,解决办法是把UAC的端点优先级设高(通过修改u_audio.c中的端点描述符)。

4.2.3 RNDIS(Remote NDIS)

RNDIS让设备模拟USB网卡。Windows主机免驱,Linux/macOS需要额外驱动。配置相对简单。

配置步骤:

  1. 加载模块:modprobe g_ether 或通过ConfigFS创建rndis.usb0
  2. 设置MAC地址(避免与主机冲突)
  3. 配置IP地址:ifconfig usb0 192.168.2.1

小技巧:RNDIS的吞吐量受端点大小影响。我习惯把Bulk端点设为512字节(高速)或1024字节(超高速)。在RK3588上实测,1024字节端点能跑到280Mbps。

4.2.4 Mass Storage

Mass Storage让设备变成U盘。RK平台支持g_mass_storage或ConfigFS方式。我推荐用ConfigFS,更灵活。

配置示例:

# 创建Mass Storage功能
mkdir /sys/kernel/config/usb_gadget/g1/functions/mass_storage.usb0

# 设置存储文件(可以是文件镜像或块设备)
echo /data/usb_storage.img > /sys/kernel/config/usb_gadget/g1/functions/mass_storage.usb0/lun.0/file

# 设置只读属性(可选)
echo 1 > /sys/kernel/config/usb_gadget/g1/functions/mass_storage.usb0/lun.0/ro

注意:存储文件必须是FAT32或ext4格式。我遇到过用NTFS格式导致Windows无法识别的问题。另外,文件大小建议不超过4GB(FAT32限制)。

4.3 复合设备的完整配置流程

好,现在把四个功能拼在一起。下面是一个完整的脚本示例,我在RK3568上验证过。

#!/bin/bash
# 复合设备配置脚本 (UVC + UAC + RNDIS + Mass Storage)

GADGET_DIR=/sys/kernel/config/usb_gadget/g1

# 1. 创建Gadget
mkdir $GADGET_DIR
echo 0x2207 > $GADGET_DIR/idVendor   # RK VID
echo 0x0123 > $GADGET_DIR/idProduct  # 自定义PID

# 2. 创建配置
mkdir $GADGET_DIR/configs/c.1
echo "Composite Device" > $GADGET_DIR/configs/c.1/configuration

# 3. 创建各功能
# UVC
mkdir $GADGET_DIR/functions/uvc.usb0
echo 1920x1080 > $GADGET_DIR/functions/uvc.usb0/streaming/uncompressed/u/1920x1080/frame.dwFrameInterval

# UAC
mkdir $GADGET_DIR/functions/uac2.usb0
echo 48000 > $GADGET_DIR/functions/uac2.usb0/p_srate

# RNDIS
mkdir $GADGET_DIR/functions/rndis.usb0
echo "00:22:33:44:55:66" > $GADGET_DIR/functions/rndis.usb0/dev_addr

# Mass Storage
mkdir $GADGET_DIR/functions/mass_storage.usb0
echo /data/usb_storage.img > $GADGET_DIR/functions/mass_storage.usb0/lun.0/file

# 4. 绑定功能到配置
ln -s $GADGET_DIR/functions/uvc.usb0 $GADGET_DIR/configs/c.1/
ln -s $GADGET_DIR/functions/uac2.usb0 $GADGET_DIR/configs/c.1/
ln -s $GADGET_DIR/functions/rndis.usb0 $GADGET_DIR/configs/c.1/
ln -s $GADGET_DIR/functions/mass_storage.usb0 $GADGET_DIR/configs/c.1/

# 5. 绑定UDC
echo "fe800000.usb" > $GADGET_DIR/UDC  # RK3568 OTG控制器地址

4.4 常见问题与调试方法

配置过程中,你可能会遇到下面这些问题。我把它们整理成表格,方便你对照排查。

问题现象 可能原因 解决方案
主机枚举失败,设备无响应 UDC未正确绑定或驱动未加载 检查/sys/class/udc/是否有设备节点;dmesg | grep udc查看错误
UVC视频流卡顿 端点带宽不足或缓冲区太小 增大vb2缓冲区数量;检查USB速度是否为高速/超高速
UAC音频有杂音 采样率不匹配或时钟抖动 固定采样率为48kHz;使用外部晶振提供时钟
RNDIS无法获取IP MAC地址冲突或DHCP服务未启动 设置唯一MAC地址;主机端手动配置静态IP
Mass Storage无法挂载 文件系统格式不支持或镜像损坏 使用mkfs.fat重新格式化;检查lun.0/file路径是否正确

调试利器:我调试复合设备时,最常用的命令是lsusb -v(查看描述符)和usbmon(抓USB包)。有一次RNDIS死活不通,用usbmon抓包发现控制传输返回了STALL,原来是端点描述符长度写错了。

4.5 性能调优建议

复合设备同时跑四个功能,带宽和CPU资源是瓶颈。下面是我总结的几条调优经验。

  • 端点分配:给实时性要求高的功能(UVC、UAC)分配独立的Bulk/Isochronous端点。避免共享端点导致争抢。
  • 缓冲区大小:UVC的缓冲区建议设为4~8个帧缓冲(每个1920x1080帧约3MB)。UAC的缓冲区可以小一些,256KB足够。
  • 中断频率:RNDIS和Mass Storage对中断不敏感,可以降低中断频率来减少CPU开销。
  • DMA一致性:确保所有功能使用DMA一致性内存(dma_alloc_coherent),避免cache一致性问题。

我的习惯:在RK3588上做复合设备时,我会先单独测试每个功能的吞吐量,确保达标后再组合。比如UVC先测30fps是否稳定,UAC测48kHz无爆音,RNDIS测iperf吞吐量。每个功能都过了,再合起来测。这样定位问题快很多。

嗯,这一章的内容就到这里。复合设备的配置其实不难,难的是把四个功能协调好。你按照上面的步骤走,遇到问题对照表格排查,应该能顺利搞定。记住,调试时多用dmesg和lsusb,它们是你最好的朋友。