route 蓝图 与 dai_link 蓝图

route 蓝图 与 dai_link 蓝图 routes 毛细血管分布式蓝图 - 实例化为以 widgetpathkcontrol 为单元的动态 有向无环图 DAG 的顶点和边的分布式 DAPM 电力管理系统其中这些 kcontrols 正是 ALSA-CORE 嵌入到 DAPM 黑箱系统里的一把出厂配置的钥匙。而这把被泛化的 kcontrol 钥匙被 ALSA-CORE 封装在 CONTROL 设备暴露给用户空间。这些驱动是扁平的电力控制驱动且在 ALSA-CORE 抽象逻辑很少并以 widget 电力控制粒度经 CONTROL 设备暴露到用户空间。dai_links 主动脉布局蓝图静态一个动态多个被实例化为 rtds 抽象出“搬运音频流过程中的完整流控驱动链路”继而被嵌入到 ALSA-CORE::PCM 设备的内被高度绑定到 ALSA-CORE 的 PCM 抽象流控策略逻辑之内而后以 CORE PCM 设备接口暴露到用户空间。routes 对应电力系统蓝图dai_link对应音频流搬运链路蓝图。route 负责电力控制dai_link负责音频流搬运控制。route具有强大稳定的供电逻辑且在 ASoC 内部自洽对 ALSA-CORE 就已经完全透明。那么route的 DPAM 系统与 PCM 设备的 rtd 和 CONTROL 设备的 widget_kcontrol 在动态运行过程中都有强烈的关系这个关系就是 DAPM 的图重算的5大触发源。DAPM 是被动服务的由 PCM 设备和 CONTROL 设备发出的触发源驱动 DAPM 工作当然还有注册绑定到 snd_soc_card 初期的首次触发。route 生发出 DAPM 系统和 widget_kcontrol 类型的 CONTROL 设备dai_link 生发出 PCM 设备route 蓝图绘出的 DAPM 系统为 ALSA 音频系统提供了坚实稳定逻辑的电力供应这种约束性来自 route 蓝图本身的硬件相关的供电逻辑强大的逻辑稳定性因此在 ALSA 代码层面并不存在也不需要显式描述 DAPM 与 PCM 设备 CONTROL 设备等等的约束。DAPM 系统感知 widget 这个插入在整个 ALSA 音频系统内各个角落的电力传感器被动地迎合整个 ALSA 系统业务变化而自洽地适配相应的电力供应策略是一股坚实稳定省心的电力供应系统。常规 widget 泛化 DAI widget 与 virtual widget最初widget 的起点确实非常纯粹就是为了帮 DAPM 标记“哪里有电闸”这就是“常规 widget”。但发展到今天“DAI Widget”的引入让它成功脱离了低级的物理寄存器束缚升华为整套 ALSA 系统中横跨“软件音频流PCM”与“硬件电路Control”的通用拓扑单元。现在的 widget 已经不再仅仅属于 DAPM 的私有财产而是成为了整个 ASoC 架构的核心通用抽象。也引入了virtual widget概念机制。所以widget 已经不再仅仅属于 DAPM 的独有资产了。DAPM widget 现在只是 widget 的一重身份而已。如果把早期的 ASoC 比作一个公司DAPM 只是“后勤电力部”而 Widget 只是电力部登记的一张“电闸清单”。但今天这个后勤部门通过 Widget 渗透到了公司业务的每一个角落流控、拓扑配置、多芯片协同、IPC 通信变成了整个公司运转的核心调度中心。DAI Widget包括dai_in、dai_out确实不是常规的 widget。它是 ASoC 架构为了解决复杂音频拓扑如多媒体多路并发、DPCM、Codec2Codec对早期 DAPM 进行的一次重大的高维泛化和架构重构。DAI widget 作为“物理端点Endpoints”是专门用来做 FE、BE 动态链接的。在官方的 DPCM 设计白皮书中 FE 和 BE 的动态链接是这样被官方阐述的FE前端是一个流的源头包含一个dai_out作为流出口。BE后端是一个流的终点包含一个dai_in作为流入口。正因为它们代表的是实打实的硬件接口流通道DAPM 在动态链接时才能根据它们的状态精准地控制具体的物理 I2S/TDM 接口应该在什么时候开启时钟、什么时候关闭电源。️‍ daiwidget在snd_soc_bind_card()内初始化阶段第一层缝合component 内部 DAI widget ↔ 内部普通 widget。没有这层缝合DAC/ADC 等内部 widget 和 DAI 接口 widget 之间就没有 path 边图遍历无法到达 DAC/ADC。第二层缝合CPU DAI widget ↔ Codec DAI widget跨 component。这些正是打通整张声卡 DAPM 图的必不可少的一环。️‍ daiwidget在 DPCM 的 FE/BE 动态绑定、PCM 与 DAPM 的互动关联过程中都起到核心缝合的作用配合 dai_link、route 实现了全局大一统的音频“路由表”同时“路由规则”也是分布式的隐式的存在于动态运行过程中的细节里面。FE/BE 动态链接的幕后过程即FE/BE 动态链接的本质DAPM 图的拓扑widget 节点 path 边在声卡注册时构建声卡生命周期内静态存在不动态创建或销毁。变化的是path-connect标志位由 kcontrol 回调通过soc_dapm_connect_path()修改以及 widget 的电源状态由dapm_power_widgets()计算。当path-connect改变后DAPM 自动通过dapm_power_widgets()重算全图 widget 电源状态随后调用snd_soc_dpcm_runtime_update()进入 DPCM 层。DPCM 动态路由的核心是从 FE CPU DAI widget 沿 DAPM 图向下游遍历收集可达的 BE DAI widgetdpcm_path_get再通过dpcm_get_be()将 widget 映射为 BE rtd最后dpcm_be_connect()分配struct snd_soc_dpcm建立 FE↔BE 双向绑定。绑定建立后由dpcm_run_update_startup()/dpcm_run_update_shutdown()驱动 BE rtd 链路硬件操作和对应的 DAPM stream event。整个寻路和动态绑定配对的过程本质就是寻找数据流向匹配且物理路径连通的dai_inwidget和dai_outwidget即 DAI widget。mapleay 2026年5月24日15:36:04Widget 驱动开发要关心的字段 与 route path 的定义和联动从dapm_power_widgets()的执行路径反推一个 widget 结构体里参与电源计算的关键字段分四组图遍历用sources / sinks ← 输入/输出 path 链表 endpoints[SINK/SOURCE] ← 连通的端点计数缓存-1失效需重算电源状态用power → new_power ← 当前态 → 目标态 connected ← pin/widget 是否被连接业务上的启用 active ← 是否有活跃的 stream is_ep ← 是否是遍历锚点SOURCE/SINK/0 force ← 强制上电ignore_suspend 用寄存器控制用reg, shift, mask, on_val/off_val ← 写什么寄存器、哪个位、什么值表示 on event, event_flags ← 上电前/下电后回调真实的硬件控制调度排序用id ← widget 类型 → 决定上电/下电顺序的类别supply pga mixer ... subseq ← 同类 widget 内的次序号写驱动时你最需要关心的就是reg/shift/mask/on_val和event/event_flags——前者是静态寄存器控制后者是动态时序控制。其余字段 DAPM 引擎自己维护驱动只需正确设好初始值。DAPM ↔ DPCM 联动这个联动链是运行时最核心的数据流值得单独研究。一句话概括三者的关系ALSA PCM Core → 驱动状态机OPEN→PREPARE→TRIGGER→STOP→CLOSE ↓ ↓ DPCM → 将 PCM 操作翻译成对哪些 DAI 做什么 ↓ ↓ DAPM → 对涉及到的 DAI widget 标脏 → 图遍历 → 重算电源PCM 层的价值理解rtd-ops是怎么被赋值的——dynamic1走dpcm_fe_dai_*否则走soc_pcm_*。这是唯一的调度分叉点。DPCM 层的价值理解 FE ↔ BE 的绑定和解除——dpcm_path_get()从 FE 的 DAI widget 出发在图里找 BE、dpcm_be_dai_trigger()逐个 BE 驱动。这是 DPCM 的核心调度逻辑。DAPM 层的价值你已经有了——soc_dapm_stream_event()把 PCM 的 START/STOP 翻译成 DAI widget 的is_ep、connect、active然后dapm_power_widgets()自动推导全图电源。三个层次分别解决何时做 → 对谁做 → 怎么做的问题。把这一条链路串起来ASoC 框架的运行时行为就基本通了。kcontrol put 回调 └─ soc_dapm_mixer_update_power() / soc_dapm_mux_update_power() │ ├─ soc_dapm_connect_path() ← ★ 改 path-connect │ └─ dapm_power_widgets(NOP) ← 第一轮 DAPM 算电 │ └─ (返回 ret 0) └─ snd_soc_dpcm_runtime_update(card) ├─ soc_dpcm_fe_runtime_update(new0) ← 查需要关的 BE │ ├─ dpcm_path_get() ← ★ 走 DAPM 图查当前可达 DAI │ └─ dpcm_prune_paths() ← 跟现有绑定对比标 prune │ └─ dpcm_run_update_shutdown() │ └─ dpcm_dapm_stream_event(SND_SOC_DAPM_STREAM_STOP) │ └─ dapm_power_widgets(SND_SOC_DAPM_STREAM_STOP) ← 第二轮 DAPM 算电 │ └─ soc_dpcm_fe_runtime_update(new1) ← 查需要开的 BE ├─ dpcm_path_get() ← ★ 走 DAPM 图查当前可达 DAI └─ dpcm_add_paths() └─ dpcm_run_update_startup() └─ dpcm_dapm_stream_event(SND_SOC_DAPM_STREAM_START) └─ dapm_power_widgets(SND_SOC_DAPM_STREAM_START) ← 第三轮 DAPM 算电kcontrol 回调 → 发现path的connect变化 → DAPM 算电源拓扑 → 发现DAI链路变了 → 调 DPCM 重算 FE/BE 绑定 → 执行2次 DAPM 算电拓扑— 这就是两层协作的代码闭环。kcontrol DAPM 刷新与 DPCM DAPM 刷新执行了两个大阶段的电力刷新这里讨论是否存在电力刷新冗余答案不冗余必须都执行电力刷新原因本质就是 kcontrol 的触发的那个 path-connect 只是边通断的触发类型无法覆盖 BEs DAI widget-is_ep 这种 snd_soc_dapm_stream_event(...SND_SOC_DAPM_STREAM_START/STOP)级别的增量 BE 链路电力更新需求