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* 中

文章互动

阅读 --

留言

0 条留言

正在加载留言…