RK平台UAC1+CDC ACM复合设备驱动:legacy gadget实现开机即用

发布时间:2026/10/1 3:36:06
RK平台UAC1+CDC ACM复合设备驱动:legacy gadget实现开机即用 简介这份资源面向 Linux 与 Android 底层驱动开发者尤其是需要在 RK 平台上实现 USB 复合设备功能的工程师。它提供了一套基于 legacy 方式的 UAC1 与 CDC 串口复合设备驱动方案开机即可直接枚举为 USB Audio 与 UART无需额外配置脚本若改用脚本方式只需不编译 legacy 驱动并在 USB 脚本中配置 acm、uac1 功能即可灵活性较高。压缩包共 12 个文件约 41KB包含 4 个 Kconfig、4 个 Makefile、2 个 C 源码文件以及 2 个 rk3308_linux_defconfig 配置文件覆盖驱动编译配置、内核选项与平台适配入口便于快速移植到其他平台。目前已有 1010 人学习下载适合希望理解 USB Gadget 复合设备枚举流程、掌握 UAC 与 CDC 组合驱动实现思路的开发者参考也可作为 RK 平台音频与串口一体化方案的实践起点。1. 一个 .rar 里塞着 UAC1 与 CDC 的复合设备驱动到底解决了什么做过 RK 平台 USB 外设的兄弟大概率遇到过这种需求一块板子既要当 USB 声卡用又要同时暴露一个串口给上位机通信。单独配 UAC 或者单独配 CDC-ACM 都不难难的是让它们在同一个 USB 配置里共存还要在 Linux 和 Android 上都能开机直接枚举不依赖任何 init.rc 或 gadget 脚本。这个usb_audiocdc复合设备驱动.rar就是干这件事的——它基于 legacy gadget 框架把 UAC1 音频和 CDC ACM 串口打包成一个复合设备插上 USB 线就能同时看到声卡和 tty 设备节点不需要额外跑配置脚本。适合正在做 RK 平台 USB 音频串口二合一产品的嵌入式工程师也适合想搞清楚 legacy gadget 与 configfs 两种复合设备实现差异的人。关键词就三个uac1cdc复合设备、usbaudio复合设备、uacacm下面拆开讲。2. legacy gadget 与 configfs 两条路为什么这份驱动选了前者2.1 legacy 与 configfs 的本质区别Linux USB gadget 框架发展到现在实际上存在两套并行的实现方式。一套是早期的 legacy 方式驱动代码里直接定义struct usb_composite_driver把功能模块function和配置描述符全部硬编码在 C 文件里模块加载即完成枚举。另一套是 configfs 方式内核只提供骨架用户空间通过写/sys/kernel/config/usb_gadget/下的文件来动态组合功能Android 上通常由 init.rc 触发脚本完成配置。legacy 的优势在于确定性。代码编译进去开机自动加载枚举流程完全可预期不会因为脚本执行顺序、文件系统挂载时机、SELinux 策略等问题导致 gadget 没起来。劣势是灵活性差想换一个 PID/VID 或者调整功能组合得改代码重新编译。configfs 反过来灵活但依赖用户空间配合Android 上尤其容易因为 init 阶段时序问题翻车。这份驱动选 legacy核心原因就一个开机即用零配置。对于量产设备来说少一个环节就少一类现场故障。2.2 复合设备描述符的组织方式UAC1 CDC ACM 复合设备的关键在于描述符的正确组织。USB 复合设备需要一套完整的描述符树设备描述符、配置描述符、接口关联描述符IAD、以及各功能自己的接口描述符和端点描述符。UAC1 音频功能通常占用两个接口一个控制接口用于音量、静音等控制和一个流接口用于音频数据传输流接口需要一个等时端点。CDC ACM 则占用两个接口一个通信类接口含中断端点用于通知和一个数据类接口含批量输入输出端点。两者加起来一个配置里至少有四个接口、三个端点一个等时、一个中断、两个批量。描述符里最容易出错的是 IAD 的使用。当复合设备中某个功能占用多个接口时必须用 IAD 把它们关联起来否则主机端可能只识别到部分接口。Windows 对 IAD 的解析尤其严格Linux 相对宽松但为了兼容性该加的 IAD 一个都不能少。2.3 代码结构与关键文件解压后的源码目录通常包含以下几个核心文件文件作用f_uac1.cUAC1 音频功能实现含音频控制与流接口逻辑f_acm.cCDC ACM 串口功能实现含 tty 注册与数据收发f_audio_cdc.c复合设备主驱动组织描述符、注册 composite driverMakefile/Kconfig编译配置决定是否编入 legacy 路径audio_cdc_defs.h描述符宏定义与端点地址分配主驱动里会定义一个usb_composite_driver结构体.name字段通常为audio_cdc.dev指向设备描述符.strings指向字符串描述符。在audio_cdc_bind()回调里依次调用acm_bind_config()和uac1_bind_config()把两个功能挂到同一个配置上。static struct usb_composite_driver audio_cdc_driver { .name audio_cdc, .dev audio_cdc_device_desc, .strings audio_cdc_strings, .max_speed USB_SPEED_HIGH, .bind audio_cdc_bind, .unbind audio_cdc_unbind, }; static int __init audio_cdc_init(void) { return usb_composite_probe(audio_cdc_driver); } module_init(audio_cdc_init);这段代码的逻辑很直白模块加载时调用usb_composite_probe()注册复合驱动内核 gadget 层随后调用.bind回调完成功能绑定。max_speed设为高速如果板子只支持全速改成USB_SPEED_FULL即可。注意module_init的加载时机如果驱动编成模块需要确保在 USB 控制器驱动之后加载。2.4 端点分配与带宽考量高速模式下UAC1 音频流端点通常用等时传输每个微帧传 192 字节48kHz、16bit、双声道。CDC ACM 的中断端点一般 16 字节批量端点 512 字节。端点地址不能冲突UAC1 的等时端点常用0x01OUT和0x82INCDC 的中断端点用0x83批量端点用0x04OUT和0x85IN。这些地址在audio_cdc_defs.h里以宏定义形式给出改的时候要同步改描述符和功能代码里的端点引用漏改一处就枚举失败。3. 从编译到枚举RK 平台上的完整落地步骤3.1 内核配置与编译拿到源码后第一步是确认内核版本和 gadget 框架的配置。RK 平台常见的 4.19 和 5.10 内核都支持 legacy gadget但配置项名称略有差异。在arch/arm64/configs/下找到板级 defconfig确保以下配置打开CONFIG_USB_GADGETy CONFIG_USB_CONFIGFSn CONFIG_USB_LEGACYy CONFIG_USB_AUDIOy CONFIG_USB_CDC_COMPOSITEy CONFIG_SND_USB_AUDIOy如果内核里没有CONFIG_USB_LEGACY这个选项说明 gadget 框架已经全面转向 configfslegacy 路径可能被裁剪了。这种情况下需要把源码里的 legacy 驱动文件放到drivers/usb/gadget/legacy/下并在该目录的 Makefile 和 Kconfig 里添加对应条目。obj-$(CONFIG_USB_AUDIO_CDC) audio_cdc.o audio_cdc-y : f_audio_cdc.o f_uac1.o f_acm.o这段 Makefile 把三个源文件编成一个模块audio_cdc.ko。f_uac1.o和f_acm.o如果内核里已有同名文件需要重命名避免冲突比如改成f_uac1_cdc.o和f_acm_cdc.o同时改掉源码里的函数名和导出符号。3.2 设备树与 UDC 确认RK 平台的 USB 控制器设备树节点通常叫usbdrd3_0或usbfcc00000里面要确保dr_mode是otg或peripheralstatus是okay。如果dr_mode设成hostgadget 侧根本不会注册 UDC驱动加载了也枚举不了。usbdrd3_0 { status okay; dr_mode otg; };确认 UDC 是否就绪可以在板子起来后执行ls /sys/class/udc/有输出比如fcc00000.dwc3说明 UDC 注册成功。如果目录为空先查 USB 控制器驱动有没有加载、设备树状态对不对、供电是否正常。3.3 加载驱动与验证枚举驱动编译成模块后推送到板子加载insmod audio_cdc.ko dmesg | tail -30dmesg 里应该能看到audio_cdc相关的 probe 日志以及gadget: audio_cdc ready之类的提示。然后检查功能节点ls /dev/ttyGS* ls /dev/snd//dev/ttyGS0是 CDC ACM 的串口节点/dev/snd/下应该出现一个 USB 音频设备。如果 ttyGS 没出来查f_acm.c里gs_alloc_req和gs_bind的返回值如果声卡没出来查 UAC1 的snd_card注册流程。主机端PC 或另一块板子插上 USB 线后lsusb应该能看到一个设备包含 Audio 和 CDC 两个功能。Linux 主机上dmesg会打印usb 1-1: new high-speed USB device以及cdc_acm和snd-usb-audio的绑定信息。Windows 上可能需要装 CDC ACM 驱动才能识别串口音频部分通常免驱。3.4 音频通路与串口回环测试音频测试用arecord和aplayarecord -D hw:1,0 -f S16_LE -r 48000 -c 2 -d 5 test.wav aplay -D hw:1,0 test.wavhw:1,0里的1是声卡编号具体值用aplay -l查。如果录音全是静音检查 UAC1 的流接口端点是否使能、时钟源配置是否正确。CDC 串口回环测试stty -F /dev/ttyGS0 115200 raw -echo cat /dev/ttyGS0 echo hello /dev/ttyGS0主机端用串口工具打开对应的 COM 口或/dev/ttyACM0发数据应该能在板子端cat出来。如果数据丢包查批量端点的req-length和actual是否匹配以及f_acm.c里acm_cdc_notify的中断端点有没有正常上报。4. 避坑与排查枚举失败、音频无声、串口丢数据的血泪经验4.1 枚举失败lsusb 看不到设备现象驱动加载了dmesg 有 probe 日志但主机端lsusb完全看不到设备或者只看到一个未知设备。原因最常见的是描述符总长度计算错误。复合设备的配置描述符wTotalLength必须等于所有接口、端点、类特定描述符的字节数之和。少算一个 IAD 或者端点描述符主机就会在解析配置时失败。另一个原因是端点地址冲突两个功能用了同一个端点号。解决用usbmon抓包看主机端到底收到了什么。wTotalLength的值和实际字节数对不上时主机通常会直接复位设备。逐个核对audio_cdc_defs.h里的描述符宏确保每个USB_DT_*的bLength和实际定义一致。端点地址用grep -n 0x8 f_*.c全局搜一遍确认没有重复。4.2 声卡识别了但录音/播放无声现象/dev/snd/下有设备节点aplay -l也能看到但播放没声音录音全是零。原因UAC1 的流接口端点没有正确使能或者音频时钟源配置不对。UAC1 规范里流接口的bAlternateSetting为 0 时是零带宽为 1 时才真正传输数据。主机端snd-usb-audio驱动会先选 alt 0开始播放时切到 alt 1。如果驱动里 alt 1 的端点描述符有问题切换就会失败。解决检查f_uac1.c里uac1_bind_config()中流接口的bAlternateSetting和端点描述符。确保 alt 1 的bNumEndpoints为 1端点属性是USB_ENDPOINT_XFER_ISOCwMaxPacketSize和采样率匹配。另外确认snd_usb_audio模块在主机端已加载dmesg里搜cannot set freq或cannot submit urb之类的错误。4.3 CDC 串口节点出不来现象/dev/ttyGS0不存在或者存在但打不开。原因f_acm.c里gs_bind()返回失败通常是gs_alloc_req()分配端点请求失败或者tty_register_driver没执行。RK 平台上如果CONFIG_USB_GADGET的 UDC 没就绪usb_composite_probe会延迟功能绑定也不会执行。解决先确认/sys/class/udc/有设备。没有的话查 USB 控制器驱动和设备树。有 UDC 但 ttyGS 没出来在f_acm.c的gs_bind里加pr_info打印每一步返回值定位是gs_alloc_req失败还是tty_register_driver失败。gs_alloc_req失败通常是端点被占用或 UDC 不支持所需传输类型。4.4 串口大数据量传输丢包现象小数据量收发正常一跑高速率比如 1Mbps 以上就丢数据。原因批量端点的请求队列深度不够或者f_acm.c里acm_read_complete回调没有及时重新提交请求。CDC ACM 的批量传输依赖 URB 链如果只提交一个请求处理完再提交下一个中间的空窗期就会丢数据。解决在gs_bind里多分配几个gs_alloc_req比如 4 到 8 个每个请求的 buffer 大小设为端点最大包长的整数倍。在完成回调里立即重新提交请求保持队列始终有数据。另外检查f_acm.c里的GS_LOG或类似调试开关打开后能看到请求提交和完成的时序。4.5 Android 上 SELinux 拦截导致节点不可用现象Linux 上一切正常Android 上/dev/ttyGS0存在但应用打不开或者声卡节点权限不对。原因Android 的 SELinux 策略默认不允许普通应用访问ttyGS设备节点ueventd.rc里也没有对应的权限规则。解决在ueventd.rc里添加/dev/ttyGS0 0660 system system并在 SELinux 策略里给对应 domain 添加rw_file_perms。音频节点同理需要确认audio_device的 label 和权限。如果不想改策略把应用放到system分区或者用su调试但量产固件必须走正规策略配置。5. 进阶玩法从 legacy 切到 configfs 脚本方式以及一个验证习惯legacy 驱动虽然省心但产品迭代时难免要调 PID/VID、换功能组合、或者在同一固件里支持多种配置。这时候把功能代码保留、只切到 configfs 脚本方式是个平滑过渡方案。具体做法是不编译f_audio_cdc.c这个 legacy 主驱动只保留f_uac1.c和f_acm.c作为功能模块然后在用户空间用脚本组合。#!/bin/sh GADGET/sys/kernel/config/usb_gadget/audio_cdc mkdir -p $GADGET echo 0x2207 $GADGET/idVendor echo 0x0010 $GADGET/idProduct mkdir -p $GADGET/strings/0x409 echo RK Audio CDC $GADGET/strings/0x409/product mkdir -p $GADGET/functions/acm.usb0 mkdir -p $GADGET/functions/uac1.usb0 mkdir -p $GADGET/configs/c.1 ln -s $GADGET/functions/acm.usb0 $GADGET/configs/c.1/ ln -s $GADGET/functions/uac1.usb0 $GADGET/configs/c.1/ ls /sys/class/udc/ | head -1 $GADGET/UDC这段脚本的逻辑是创建 gadget 目录设置 VID/PID 和字符串实例化 acm 和 uac1 两个功能挂到配置 c.1 下最后把 UDC 写进去触发枚举。idVendor和idProduct按实际项目改uac1.usb0的音频参数采样率、通道数可以在功能目录下的p_srate、c_srate、p_chmask等文件里调。脚本方式的好处是改配置不用重编内核坏处是 Android 上要处理 init 时序和 SELinux量产时得把脚本固化到init.rc或vendor分区。我自己的习惯是每次改完描述符或者端点分配先在 Linux 主机上用usbmon抓一次完整枚举流程确认wTotalLength、接口数、端点地址全部正确再推到板子上跑。这个习惯帮我省了至少三次“板子端一切正常但主机不认”的排查时间。另外legacy 和 configfs 不要同时开内核里两个路径都编进去时加载顺序不同可能导致 UDC 被先注册的那个占用后加载的报-EBUSY。从那以后我每次切方案都先把旧路径的CONFIG_关干净再编新路径。希望帮到你。本文还有配套的精品资源点击获取