u_audio 与 f_uac2 的匹配机制
源码:rk3588/kernel-6.1/drivers/usb/gadget/function/(u_audio.c、u_audio.h、f_uac2.c)。
1. 两个文件的分工
u_audio.c与f_uac2.c不是通过总线或 compatible 之类的动态“匹配”关联的,而是编译期就确定的分层调用关系,中间的桥梁是u_audio.h中定义的struct g_audio:
1 | f_uac2.c UAC2 协议层:描述符、EP0 类请求、Alt Setting、Feature Unit |
u_audio.c同时服务于f_uac1.c、f_uac2.c(以及本树的衍生 Function);它不理解 UAC1/UAC2 协议细节,只负责“把 ISO 端点数据和一块 ALSA 声卡对接起来”。
2. 结构上的嵌入关系
匹配的核心是两层container_of嵌套:
1 | #include "u_audio.h" |
f_uac2.c中的私有对象把g_audio作为第一个成员内嵌(64 行附近):
1 | struct f_uac2 { |
而g_audio内部又内嵌了 composite 框架的struct usb_function:
1 | struct usb_function func; |
因此从任意一层都能找到另一层:
- 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->uac和uac->audio_dev在 ALSA 对象与g_audio之间互查。
这就是“匹配”的本质:同一块内存的三层视图(f_uac2 ⊃ g_audio ⊃ usb_function),用 container_of 互相转换。
3. 绑定期的参数移交
afunc_bind()(f_uac2.c:1036)完成协议层到引擎层的一次性移交:
1 | afunc_bind() |
g_audio_setup()(u_audio.c:1426)只依赖g_audio里填好的字段,不回头访问任何 UAC2 私有数据:
- 分配
snd_uac_chip,双向指针挂接(g_audio->uac、uac->audio_dev); - 按
c_chmask/p_chmask为捕获/播放各预分配req_number个usb_request指针数组和数据缓冲(大小max_psize来自 bind 阶段算好的out/in_ep_maxpsize); snd_card_new()+snd_pcm_new()创建虚拟 ALSA 声卡,PCM 名即传入的"UAC2 PCM";- 按需创建反馈/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_ep:config_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 | Host ISO OUT ──> out_ep ──> u_audio_iso_complete (u_audio.c:153) |
请求完成后立即重新入队,形成常驻的 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* 中 |
正在加载留言…