ALSA 术语的重要性
ALSA 代码里的专业术语做的很差,需要搞懂专业术语,尤其是引入了那次底层重构:从 platform/cpu/codec 代码框架到 基于 一切皆 component 的重构。这个重构是代码上的重构,而 platform/cpu/codec 甚至 SoC (比如 SoC DAI)也被创造出来用于描述这个系统里的驱动切面,这个精髓没有丢,代码内部也有点影子。所以,component 的引入不等于彻底抛弃了 platform/cpu/codec 这样的天然物理映射的驱动切面!这样就导致,很多时候,描述 ALSA 时,这些词汇很容易让人迷糊感觉很玄乎。。其实,Linux ALSA 官方文档里就对这些词汇有特定上下文的描述和显式的定义。当我们研究到一定程度了,一定要回来搞定这些词汇的含义,不然不利于后续理清思路。
ALSA SoC Layer Overview¶
The overall project goal of the ALSA System on Chip (ASoC) layer is to provide better ALSA support for embedded system-on-chip processors (e.g. pxa2xx, au1x00, iMX, etc) and portable audio codecs. Prior to the ASoC subsystem there was some support in the kernel for SoC audio, however it had some limitations:-
Codec drivers were often tightly coupled to the underlying SoC CPU. This is not ideal and leads to code duplication - for example, Linux had different wm8731 drivers for 4 different SoC platforms.
There was no standard method to signal user initiated audio events (e.g. Headphone/Mic insertion, Headphone/Mic detection after an insertion event). These are quite common events on portable devices and often require machine specific code to re-route audio, enable amps, etc., after such an event.
Drivers tended to power up the entire codec when playing (or recording) audio. This is fine for a PC, but tends to waste a lot of power on portable devices. There was also no support for saving power via changing codec oversampling rates, bias currents, etc.
ASoC Design
The ASoC layer is designed to address these issues and provide the following features :-
Codec independence. Allows reuse of codec drivers on other platforms and machines.
Easy I2S/PCM audio interface setup between codec and SoC. Each SoC interface and codec registers its audio interface capabilities with the core and are subsequently matched and configured when the application hardware parameters are known.
Dynamic Audio Power Management (DAPM). DAPM automatically sets the codec to its minimum power state at all times. This includes powering up/down internal power blocks depending on the internal codec audio routing and any active streams.
Pop and click reduction. Pops and clicks can be reduced by powering the codec up/down in the correct sequence (including using digital mute). ASoC signals the codec when to change power states.
Machine specific controls: Allow machines to add controls to the sound card (e.g. volume control for speaker amplifier).
To achieve all this, ASoC basically splits an embedded audio system into multiple re-usable component drivers :-
Codec class drivers: The codec class driver is platform independent and contains audio controls, audio interface capabilities, codec DAPM definition and codec IO functions. This class extends to BT, FM and MODEM ICs if required. Codec class drivers should be generic code that can run on any architecture and machine.
Platform class drivers: The platform class driver includes the audio DMA engine driver, digital audio interface (DAI) drivers (e.g. I2S, AC97, PCM) and any audio DSP drivers for that platform.
Machine class driver: The machine driver class acts as the glue that describes and binds the other component drivers together to form an ALSA "sound card device". It handles any machine specific controls and machine level audio events (e.g. turning on an amp at start of playback).
ASoC Codec Class Driver¶
The codec class driver is generic and hardware independent code that configures the codec, FM, MODEM, BT or external DSP to provide audio capture and playback. It should contain no code that is specific to the target platform or machine. All platform and machine specific code should be added to the platform and machine drivers respectively.
Each codec class driver must provide the following features:-
-
Codec DAI and PCM configuration
-
Codec control IO - using RegMap API
-
Mixers and audio controls
-
Codec audio operations
-
DAPM description.
-
DAPM event handler.
Optionally, codec drivers can also provide:-
- DAC Digital mute control.
Its probably best to use this guide in conjunction with the existing codec driver code in sound/soc/codecs/
ASoC Platform Driver
An ASoC platform driver class can be divided into audio DMA drivers, SoC DAI drivers and DSP drivers. The platform drivers only target the SoC CPU and must have no board specific code.
Audio DMA
The platform DMA driver optionally supports the following ALSA operations:-
/* SoC audio ops */
struct snd_soc_ops {
int (*startup)(struct snd_pcm_substream *);
void (*shutdown)(struct snd_pcm_substream *);
int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
int (*hw_free)(struct snd_pcm_substream *);
int (*prepare)(struct snd_pcm_substream *);
int (*trigger)(struct snd_pcm_substream *, int);
};
The platform driver exports its DMA functionality via struct snd_soc_component_driver:-
struct snd_soc_component_driver {
const char *name;
...
int (*probe)(struct snd_soc_component *);
void (*remove)(struct snd_soc_component *);
int (*suspend)(struct snd_soc_component *);
int (*resume)(struct snd_soc_component *);
/* pcm creation and destruction */
int (*pcm_new)(struct snd_soc_pcm_runtime *);
void (*pcm_free)(struct snd_pcm *);
...
const struct snd_pcm_ops *ops;
const struct snd_compr_ops *compr_ops;
...
};
Please refer to the ALSA driver documentation for details of audio DMA.
An example DMA driver is soc/pxa/pxa2xx-pcm.c
SoC DAI Drivers
Each SoC DAI driver must provide the following features:-
-
Digital audio interface (DAI) description
-
Digital audio interface configuration
-
PCM's description
-
SYSCLK configuration
-
Suspend and resume (optional)
Please see ASoC Codec Class Driver for a description of items 1 - 4.
SoC DSP Drivers
Each SoC DSP driver usually supplies the following features :-
-
DAPM graph
-
Mixer controls
-
DMA IO to/from DSP buffers (if applicable)
-
Definition of DSP front end (FE) PCM devices.
Please see DPCM.txt for a description of item 4.
ASoC Machine Driver
The ASoC machine (or board) driver is the code that glues together all the component drivers (e.g. codecs, platforms and DAIs). It also describes the relationships between each component which include audio paths, GPIOs, interrupts, clocking, jacks and voltage regulators.
The machine driver can contain codec and platform specific code. It registers the audio subsystem with the kernel as a platform device and is represented by the following struct:-
/* SoC machine */
struct snd_soc_card {
char *name;
...
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
/* the pre and post PM functions are used to do any PM work before and
* after the codec and DAIs do any PM work. */
int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);
int (*suspend_post)(struct platform_device *pdev, pm_message_t state);
int (*resume_pre)(struct platform_device *pdev);
int (*resume_post)(struct platform_device *pdev);
...
/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link;
int num_links;
...
};
Dynamic PCM
DPCM machine driver
The DPCM enabled ASoC machine driver is similar to normal machine drivers except that we also have to :-
-
Define the FE and BE DAI links.
-
Define any FE/BE PCM operations.
-
Define widget graph connections.
FE and BE DAI links
| Front End PCMs | SoC DSP | Back End DAIs | Audio devices |
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers
* DSP *
PCM2 <------------> * * <----DAI2-----> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
For the example above we have to define 4 FE DAI links and 6 BE DAI links. The FE DAI links are defined as follows :-
SND_SOC_DAILINK_DEFS(pcm0,
DAILINK_COMP_ARRAY(COMP_CPU("System Pin")),
DAILINK_COMP_ARRAY(COMP_DUMMY()),
DAILINK_COMP_ARRAY(COMP_PLATFORM("dsp-audio")));
static struct snd_soc_dai_link machine_dais[] = {
{
.name = "PCM0 System",
.stream_name = "System Playback",
SND_SOC_DAILINK_REG(pcm0),
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
},
.....< other FE and BE DAI links here >
};
This FE DAI link is pretty similar to a regular DAI link except that we also set the DAI link to a DPCM FE with the dynamic = 1. There is also an option to specify the ordering of the trigger call for each FE. This allows the ASoC core to trigger the DSP before or after the other components (as some DSPs have strong requirements for the ordering DAI/DSP start and stop sequences).
The FE DAI above sets the codec and code DAIs to dummy devices since the BE is dynamic and will change depending on runtime config.
The BE DAIs are configured as follows :-
SND_SOC_DAILINK_DEFS(headset,
DAILINK_COMP_ARRAY(COMP_CPU("ssp-dai.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("rt5640.0-001c", "rt5640-aif1")));
static struct snd_soc_dai_link machine_dais[] = {
.....< FE DAI links here >
{
.name = "Codec Headset",
SND_SOC_DAILINK_REG(headset),
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = hswult_ssp0_fixup,
.ops = &haswell_ops,
},
.....< other BE DAI links here >
};
This BE DAI link connects DAI0 to the codec (in this case RT5460 AIF1). It sets the no_pcm flag to mark it has a BE.
The BE has also flags set for ignoring suspend and PM down time. This allows the BE to work in a hostless mode where the host CPU is not transferring data like a BT phone call :-
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers
* DSP *
PCM2 <------------> * * <====DAI2=====> MODEM
* *
PCM3 <------------> * * <====DAI3=====> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
This allows the host CPU to sleep while the DSP, MODEM DAI and the BT DAI are still in operation.
A BE DAI link can also set the codec to a dummy device if the codec is a device that is managed externally.
Likewise a BE DAI can also set a dummy cpu DAI if the CPU DAI is managed by the DSP firmware.
FE/BE PCM operations
The BE above also exports some PCM operations and a fixup callback. The fixup callback is used by the machine driver to (re)configure the DAI based upon the FE hw params. i.e. the DSP may perform SRC or ASRC from the FE to BE.
e.g. DSP converts all FE hw params to run at fixed rate of 48k, 16bit, stereo for DAI0. This means all FE hw_params have to be fixed in the machine driver for DAI0 so that the DAI is running at desired configuration regardless of the FE configuration.
static int dai0_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
/* The DSP will convert the FE rate to 48k, stereo */
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
/* set DAI0 to 16 bit */
params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
return 0;
}
The other PCM operation are the same as for regular DAI links. Use as necessary.
Widget graph connections
The BE DAI links will normally be connected to the graph at initialisation time by the ASoC DAPM core. However, if the BE codec or BE DAI is a dummy then this has to be set explicitly in the driver :-
/* BE for codec Headset - DAI0 is dummy and managed by DSP FW */
{"DAI0 CODEC IN", NULL, "AIF1 Capture"},
{"AIF1 Playback", NULL, "DAI0 CODEC OUT"},
Writing a DPCM DSP driver
The DPCM DSP driver looks much like a standard platform class ASoC driver combined with elements from a codec class driver. A DSP platform driver must implement :-
-
Front End PCM DAIs - i.e.
struct snd_soc_dai_driver. -
DAPM graph showing DSP audio routing from FE DAIs to BEs.
-
DAPM widgets from DSP graph.
-
Mixers for gains, routing, etc.
-
DMA configuration.
-
BE AIF widgets.
Items 6 is important for routing the audio outside of the DSP. AIF need to be defined for each BE and each stream direction. e.g for BE DAI0 above we would have :-
SND_SOC_DAPM_AIF_IN("DAI0 RX", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("DAI0 TX", NULL, 0, SND_SOC_NOPM, 0, 0),
The BE AIF are used to connect the DSP graph to the graphs for the other component drivers (e.g. codec graph).
Hostless PCM streams
A hostless PCM stream is a stream that is not routed through the host CPU. An example of this would be a phone call from handset to modem.
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <====DAI1=====> Codec Speakers/Mic
* DSP *
PCM2 <------------> * * <====DAI2=====> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
In this case the PCM data is routed via the DSP. The host CPU in this use case is only used for control and can sleep during the runtime of the stream.
The host can control the hostless link either by :-
Configuring the link as a CODEC <-> CODEC style link. In this case the link is enabled or disabled by the state of the DAPM graph. This usually means there is a mixer control that can be used to connect or disconnect the path between both DAIs.
Hostless FE. This FE has a virtual connection to the BE DAI links on the DAPM graph. Control is then carried out by the FE as regular PCM operations. This method gives more control over the DAI links, but requires much more userspace code to control the link. Its recommended to use CODEC<->CODEC unless your HW needs more fine grained sequencing of the PCM ops.
CODEC <-> CODEC link
This DAI link is enabled when DAPM detects a valid path within the DAPM graph. The machine driver sets some additional parameters to the DAI link i.e.
static const struct snd_soc_pcm_stream dai_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link dais[] = {
< ... more DAI links above ... >
{
.name = "MODEM",
.stream_name = "MODEM",
.cpu_dai_name = "dai2",
.codec_dai_name = "modem-aif1",
.codec_name = "modem",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.c2c_params = &dai_params,
.num_c2c_params = 1,
}
< ... more DAI links here ... >
These parameters are used to configure the DAI hw_params() when DAPM detects a valid path and then calls the PCM operations to start the link. DAPM will also call the appropriate PCM operations to disable the DAI when the path is no longer valid.
Hostless FE¶
The DAI link(s) are enabled by a FE that does not read or write any PCM data. This means creating a new FE that is connected with a virtual path to both DAI links. The DAI links will be started when the FE PCM is started and stopped when the FE PCM is stopped. Note that the FE PCM cannot read or write data in this configuration.
dai_link 的概念深入分析
🏳️🌈 dai_link 只属于 Machine 驱动,且在 Machine 驱动中定义;永远不在 Codec 或 Platform 驱动里定义。
原因:dai_link 回答的问题是"谁和谁怎么连 "------这是板级拓扑信息,只有 machine driver 知道:
-
这块板子上 I2S0 连的是 ES8323 还是 WM8960?
-
有没有 ASRC(异步采样率转换器)?要不要 DPCM?
-
MCLK 倍频系数是多少?
-
耳机/扬声器 GPIO 怎么接?
这些每块板子都不同。同一个 ES8323 codec 驱动,在板 A 上可能连 I2S0,在板 B 上可能连 I2S1------codec 驱动不该也不可能知道。
而 platform driver 和 codec driver 各自只负责:
| 驱动类型 | 注册什么 | 不管什么 |
|---|---|---|
| codec (如 es8323.c) | component + DAI("ES8323 HiFi")+ DAPM widgets/routes | 谁连我、怎么连 |
| platform/CPU (如 rockchip_i2s.c) | component + DAI(I2S 控制器)+ dmaengine PCM | 连哪个 codec |
| machine (如 rockchip_multicodecs.c) | dai_link + snd_soc_card | --- |
三者关系:
machine driver (板级拓扑)
├── dai_link[0]: cpu = I2S0, codec = ES8323, platform = I2S0
├── dai_link[1]: ...
└── snd_soc_card
platform driver (I2S 硬件) codec driver (ES8323 硬件)
└── component + DAI └── component + DAI
三者在 devm_snd_soc_register_card() 时由 ASoC core 统一 bind
machine driver 是胶水,dai_link 是胶水的配方。
🏳️🌈 snd_soc_dai_link->snd_soc_dai_link_component
struct snd_soc_dai_link_component {
// name 和 of_node 用于匹配 component !!!
const char *name;
struct device_node *of_node;
// dai_name 和 dai_args 用于匹配 component 内部的 dais !!!
const char *dai_name;
const struct of_phandle_args *dai_args;
unsigned int ext_fmt;
};
struct snd_soc_dai_link {
//name:dai_link 的标识名,用于 debugfs、sysfs、日志显示。不参与任何匹配逻辑。(正确注释)
const char *name;
//stream_name:DAPM stream 匹配键。DAPM 用它来关联 stream domain widget(DAC/ADC/AIF)与这条 dai_link。(正确注释)
const char *stream_name;
struct snd_soc_dai_link_component *cpus;
unsigned int num_cpus;
struct snd_soc_dai_link_component *codecs;
unsigned int num_codecs;
struct snd_soc_dai_link_component *platforms;
unsigned int num_platforms;
...
}
- 内核注释未即使更新,可能会误人子弟,需要说明:
//截止 Linux-7.01 版本的内核注释确实写着:
struct snd_soc_dai_link {
const char *name; /* Codec name */ ← 内核原始注释(错误注注释)
const char *stream_name; /* Stream name */ ← 内核原始注释(错误注注释)
};
这个注释极其过时 。它存在于代码中是因为历史原因------最早 ASoC dai_link 只有一个 codec,name 字段曾经用来存 codec 名称。但现在的实际用法完全不是这样了。看所有现役 Machine 驱动的用法全是声卡链路逻辑名,不是 codec 芯片名。真正的 codec 名称存在 codecs[i].dai_name("es8388-aif1")或 codecs[i].of_node(指向 codec 的 DT 节点)。
注释是早期代码的化石残留,但不能按注释字面理解 。name 现在是 debugfs/sysfs 下 rtd 的显示名,stream_name 是 DAPM stream event 的匹配键。建议你按实际代码逻辑理解,别管那个注释。
- platforms/cpus/codecs dlc 的语义理解
| 字段 | 你说的 | 实际 | 代码证据 |
|---|---|---|---|
codecs |
codec_itself + codec_dai | 对 | dlc 的 name/of_node 定位 component,dai_name 定位 DAI |
cpus |
cpu_dai only? | 对,就是 cpu_dai | L735 注释你自己写了:"CPU 字眼就是指 CPU_DAI"。内核注释 L734 也说"matched using .cpu_name only"------匹配的是 DAI |
platforms |
搬运引擎 DSP/DMA? | 对 | L756 注释你自己写了:"指的是 PCM 音频流的搬运引擎,比如 DSP 或 DMA"。内核原始注释 L753 也说"the link's platform/PCM/DMA driver" |
三者的角色分工
cpus[] → 谁(哪个 DAI)负责从 CPU 侧把音频流送进/接出这条链路
codecs[] → 谁(哪个 codec+DAI)在链路的另一端接收/发送音频流
platforms[] → 谁来搬运数据(DMA buffer 分配、pointer、trigger DMA 通道)
🏳️🌈 搬运引擎自身会不会泛化 DAI?答案:DMA 不会泛化DAI ,但是 DSP 会。
DMA --- 不泛化 DAI
DMA 引擎在 ASoC 中注册为 Platform component,注册时:
// soc-generic-dmaengine-pcm.c
ret = snd_soc_add_component(&pcm->component, NULL, 0);
↑ ↑
无 DAI 0 个
它是纯粹的搬运者。它的数据结构里没有 snd_soc_dai,没有 snd_soc_dai_driver,没有 widget,没有 route。它只提供 copy/mmap/pointer/ack 这些搬运回调,通过 rtd->ops 的 component 补充步骤注入。DMA 不参与 rtd 的 dais[],不参与 for_each_rtd_dais 遍历,不参与 DAPM 图。它的唯一任务是:substream buffer ↔ DAI FIFO。
DSP --- 泛化 DAI
DSP 注册为带 DAI 的 component:
// MT8183 AFE
devm_snd_soc_register_component(dev,
&mt8183_afe_pcm_dai_component, // component_driver
afe->dai_drivers, // ← 有 DAI!十几条!
afe->num_dai_drivers);
// 每条 DAI 对应一个 DSP 内部端口:"DL1" → playback memif、"UL1" → capture memif、"I2S0" → 物理 I2S 接口映射 ... 全部是 DAI。。哈哈哈哈。。
DSP 的每一条 DAI 都可以有自己的 widget(通常 SND_SOC_DAPM_AIF_IN/OUT),可以参与 DAPM 图连接。DSP 的每个 memif 通道都注册为 DAI,因为每个 memif 都是一个独立的音频数据端口------有独立的启动/停止/参数配置生命周期。这就是 DSP 能同时承载 FE 和 BE 两端的原因------它的 DAI 列表里既有面向内存的虚拟端点(FE memif),也有面向物理总线接口的映射(BE I2S)。
DMA 没有 DAI 的 component VS. DSP 带有泛化 DAI 的 component 为什么不同?
| DMA 引擎 | DSP memif | |
|---|---|---|
| 本质 | 搬运通道:CPU memory ↔ 设备 FIFO | 数据端口:音频流进出 DSP 的入口/出口 |
| 生命周期 | 随 PCM substream 走,不需要独立 open/close | 有独立的 startup/shutdown/hw_params/trigger |
| 多实例? | 一个 DMA 引擎服务整张声卡的所有 PCM | 多个 memif 各自独立,DL1 和 DL2 互不干扰 |
| 需要被 dai_link 引用? | 不需要,通过 platform 字段找 | 需要 ,FE dai_link 的 cpus[0] 要引用 "DL1" |
核心判断标准 :如果这个硬件资源有独立的流级生命周期 (能单独 open/close/hw_params/trigger),它就需要被建模为 DAI;如果只是被动服务于已有流(流已经 open 了,它只是搬数据),就不需要。DMA 引擎是后者------流由 PCM substream 管理,DMA 只是附属的搬运能力。DSP memif 是前者------每个 memif 就是一个流入口,必须独立管理。
🏳️🌈 DPCM 下 dai_link 的数量和 rtd 的组织方式------谁决定、怎么决定、形成几条完整链路。
🔆 Static PCM --- 1 条 dai_link
1 dai_link → 1 rtd → 1 snd_pcm → 1 /dev/snd/pcmCxDx
CPU(真实) + Codec(真实) + Platform(真实)
一条链路包含一切,用户 open 直接走到 DAI
🔆 DPCM --- 最少 2 条 dai_link
1 FE dai_link + 1 BE dai_link = 最少 2 条 dai_link
├─ FE rtd → 完整 snd_pcm → /dev/snd/pcmCxDx (用户 open)
└─ BE rtd → internal snd_pcm (内核内部)
≠ 2 个 /dev 设备,= 1 个用户设备 + 1 个内部端点
实际通常 > 2:
1 FE + N BE (耳机/扬声器/BT/Modem...)
或 N FE + M BE (PCM0~PCM3 分别路由到不同 BE 组合)
最简:1 FE + 1 BE = 2 dai_link → 2 rtd
典型:1 FE + N BE = N+1 dai_link → N+1 rtd(如手机:1 FE + 4~6 BE)
多FE:M FE + N BE = M+N dai_link → M+N rtd
🔆 DPCM 下谁决定组织多少个 rtd 形成完整的音频流链路?
分两层:静态声明 + 动态绑定
层1:machine driver 静态声明 dai_link 数量
machine driver 写死有多少条 FE dai_link 和 BE dai_link:
// rockchip_multicodecs.c:1 FE + 1 BE + 1 直连 = 3 dai_link
// MT8183:4 FE + 6 BE = 10 dai_link
这些在 devm_snd_soc_register_card() 时全部实例化为 rtd,加入 card->rtd_list。此时的 rtd 都是孤立的------FE rtd 的 be_clients 为空,BE rtd 的 fe_clients 也为空。
层2:运行时 DAPM 决定 FE↔BE 连接拓扑
DPCM 框架不知道也不硬编码"FE 应该连哪些 BE"------这完全由 DAPM 图在运行时动态决定。
FE rtd 孤立
│
▼ dpcm_path_get() 遍历 DAPM 图
│
├── 当前 DAPM 连通性 → FE 连到 BE1 + BE3
├── mixer 切换后 → FE 连到 BE2 + BE3
└── 又一次切换 → FE 连到 BE1
同一个 FE 在不同时刻可以连不同数量的 BE,连接组合完全取决于 DAPM mixer/mux 的当前状态。
Dynamic-PCM模式下,一条完整音频搬运链路的形成过程:
答案版本1 (Deepseek-4.0Pro) OK的
阶段一:dai_link → rtd(静态构造)
Machine 驱动:
[0] FE "Media" (dynamic=1, cpu=DL1, codec=dummy)
[1] BE "Speaker" (no_pcm=1, cpu=I2S0, codec=ES8388)
[2] BE "Headphone" (no_pcm=1, cpu=I2S1, codec=ES8388)
结果:card->rtd_list = [rtd_FE, rtd_BE0, rtd_BE1]
三个孤立的 rtd,互不相知
阶段二:DAPM 图 + DPCM walk → rtd 之间的 snd_soc_dpcm 连接(运行时)
用户 open /dev/snd/pcmC0D0p
→ dpcm_path_get(fe, stream, &list)
DAPM walk:DL1 widget → Mixer → I2S0 widget (BE0)
DL1 widget → Mixer → I2S1 widget (BE1)
→ dpcm_add_paths()
→ dpcm_be_connect(fe, rtd_BE0, stream)
snd_soc_dpcm{ fe=rtd_FE, be=rtd_BE0 }
list_add → fe->dpcm[].be_clients
list_add → be->dpcm[].fe_clients
→ dpcm_be_connect(fe, rtd_BE1, stream)
snd_soc_dpcm{ fe=rtd_FE, be=rtd_BE1 }
list_add → fe->dpcm[].be_clients
list_add → be->dpcm[].fe_clients
结果:fe->dpcm[stream].be_clients = [dpcm_to_BE0, dpcm_to_BE1]
be0->dpcm[stream].fe_clients = [dpcm_from_FE]
be1->dpcm[stream].fe_clients = [dpcm_from_FE]
答案版本2 (GLM-5.1) OK的
静态声明(machine driver)
│
├── dai_link[0]: FE (dynamic=1) ──实例化──→ FE rtd (孤立)
├── dai_link[1]: BE0 (no_pcm=1) ──实例化──→ BE0 rtd (孤立)
├── dai_link[2]: BE1 (no_pcm=1) ──实例化──→ BE1 rtd (孤立)
└── dai_link[3]: BE2 (no_pcm=1) ──实例化──→ BE2 rtd (孤立)
用户 open("/dev/snd/pcmC0D0p")
│
▼ dpcm_fe_dai_open()
│
├── dpcm_path_get(): 从 FE cpu_dai widget 出发遍历 DAPM 图
│ 当前 DAPM 连通性 → 发现 BE0, BE1 可达
│
├── dpcm_add_paths(): 建立连接
│ dpcm_be_connect(FE, BE0) → FE.be_clients ← dpcm → BE0.fe_clients
│ dpcm_be_connect(FE, BE1) → FE.be_clients ← dpcm → BE1.fe_clients
│
└── dpcm_fe_dai_startup(): 启动整条链路
BE0: open → hw_params → ... ← 由 FE 触发
BE1: open → hw_params → ... ← 由 FE 触发
FE: open → hw_params → ... ← 自身
此刻形成完整链路:
用户空间 → FE(搬运引擎+memif) ─┬→ BE0(I2S+CodecA) → 喇叭
└→ BE1(I2S+CodecB) → 耳机
实例化(构造 rtd)完全是通用逻辑 ------逐条遍历 dai_link,用固定的匹配算法找 component/DAI,构造 rtd,挂入 card->rtd_list。DPCM 在此阶段没有任何特殊计算 。DPCM 框架本身不知道也不决定组织多少个 rtd 形成完整链路。
devm_snd_soc_register_card(card)
│
▼ snd_soc_bind_card()
│
├── for each dai_link in card->dai_link[]:
│ │
│ ▼ snd_soc_add_pcm_runtime(card, dai_link)
│ │
│ ├── 遍历 dlc (cpus/codecs/platforms),调用 snd_soc_find_dai() 匹配
│ ├── 构造 rtd,填入 dais[]、components[]
│ ├── rtd->dai_link = dai_link
│ └── list_add(&rtd->list, &card->rtd_list)
│
├── for each rtd in card->rtd_list:
│ ▼ soc_new_pcm(rtd) ← 这里才分叉!
│ │
│ ├── dai_link->no_pcm? → snd_pcm_new_internal() (BE)
│ ├── else → snd_pcm_new() (FE/static)
│ │
│ ├── dai_link->dynamic? → rtd->ops = dpcm_fe_xxx (FE)
│ └── else → rtd->ops = soc_pcm_xxx (static)
machine driver 声明了"候选池"(哪些 FE/BE 存在),DAPM 决定了"当前连线"(哪些 FE↔BE 活跃)。
实例化(构造 rtd)是 ASoC core 的通用能力,不区分 static/DPCM。DPCM 只在实例化之后的"配置"阶段和"运行时"阶段才有自己的逻辑。
运行时由 DAPM 图的连通性决定哪些 FE↔BE 互相绑定。
DPCM 框架只是执行者------把 DAPM 的连通性判断翻译为 rtd 间的 snd_soc_dpcm 连接和流控操作。
🏳️🌈 DPCM 下的 dai_link 升维
Dynamic-PCM 的 dai_link 就不止一个,这个音频流搬运链路(引擎+通路)的含义已经提升了,不再是 Static-PCM 的 一条 dai_link 实例化 一条 rtd 关联一个 PCM 设备运行服务直到生命周期结束。
static-PCM:一条 dai_link = 一条完整链路 = 一个闭环服务
1 dai_link → 1 rtd → 1 PCM 设备 → 用户空间可用的完整音频服务
│ │
└── 自给自足:搬运引擎+DAI+codec 全在这一个 rtd 里 ──┘
dai_link 就是链路本身,1:1:1 贯穿始终。
DPCM:多条 dai_link = 多个碎片 → 运行时拼合 = 一个完整服务
FE dai_link → FE rtd → 完全体 PCM 设备 (用户空间入口)
BE dai_link → BE rtd → internal PCM (不可见)
│
▼ 运行时 dpcm_be_connect()
│
一条完整音频服务 = FE rtd + BE rtd(s),snd_soc_dpcm 是焊缝
dai_link 不再是链路本身,而是链路的一个碎片。 链路的完整含义从"一个 dai_link"提升为"一组 dai_link 的运行时组合"。
这就是 DPCM 对 static-PCM 的语义升维:
| static-PCM | DPCM | |
|---|---|---|
| 链路单元 | 一个 dai_link | 一组 dai_link |
| 链路含义 | 编译时确定,静态闭合 | 运行时拼合,动态闭合 |
| PCM 服务归属 | 1 rtd 自身 | FE rtd 服务代理和搬运引擎 + BE rtd(s) 搬运物流服务 |
| 变化能力 | 不变 | DAPM 变化 → 重新拼合 |
"音频流搬运链路"这个概念,从 static-PCM 下的名词 (一个固定对象),变成了 DPCM 下的动词(一个动态组装过程)。
重点:之所以多个 dai_link 实例化为多个 rtd 动态拼接组合为一条完整的音频流链路,是因为ALSA ASoC 架构能够支持这种 static-PCM 的单个 dai_link/rtd 泛化为 Dynamic-PCM 的多 dai_link/rtd 形成一条完整的音频链路的能力。好像没说。。。哈哈哈。。。。可能更加关键的是 ASoC 的泛化能力支持的,他们高内聚低耦合(注册、DAPM、DPCM、注入ALSA-CORE)。
多条 dai_link/rtd 之所以能动态拼合为一条完整音频链路,根因不是 DPCM 本身,而是 ASoC 架构的四个解耦支柱使得这种拼合成为可能:
-
注册解耦:component/DAI 独立注册、延迟匹配(-EPROBE_DEFER),dai_link 只声明"要什么"不绑死"在哪"------使得 FE 和 BE 可以各自独立实例化,不需要对方先就绪
-
DAPM 解耦:DAPM 只管 widget 连通性和电源,不知道 FE/BE 的存在------DPCM 通过 dpcm_path_get() 只读消费 DAPM 图,使得路由拓扑可以任意设计而框架无需修改
-
DPCM 解耦:snd_soc_dpcm 双向链表 + 引用计数使得 FE↔BE 连接关系在运行时任意增减,且多对多共享原生支持------使得拼合方式不受框架约束
-
注入 ALSA-CORE 解耦:FE 的完全体 PCM 和 BE 的 internal PCM 都通过 snd_device 统一编排,用户空间只看到 FE 的字符设备,BE 的存在对上层透明------使得拼合结果对外呈现为"一个完整的 PCM 服务"
四个解耦支柱各自高内聚、互相低耦合,DPCM 只是利用了这种架构能力来实现动态拼合------而不是 DPCM 自己创造了这种能力。 这就是泛化的底气:框架先具备了解耦能力,DPCM 才能在其上编织动态路由。