---
# System prepended metadata

title: 'Linux 核心設計: Audio Subsystem(1): DAPM'
tags: [Linux Kernel Internals, ' 作業系統', Audio]

---

# Linux 核心設計: Audio Subsystem(1): DAPM

## Overview

[Dynamic Audio Power Management (DAPM)](https://www.alsa-project.org/wiki/DAPM) 是在 Linux 作業系統中，以使音訊子系統中能達最低功耗為目的所設計的機制。其獨立並共存於其他核心電源管理框架，因為相關的管理必須深入到元件(component)級別，而非一般的裝置級別(後者是透過 [Runtime PM 框架](https://hackmd.io/@RinHizakura/SkziZRa9a)所管理)。

DAPM 基於兩個基本元素組成:
* Widget: 是指音訊硬體中可在被使用時被軟體啟用、並在不需要時被停用以節省功耗的各個部分
* Route: 是指 Widget 之間的連接，聲音可以藉此從一個 widget 流向另一個 widget

透過查詢音訊的路由圖(routing graph)，DAPM 能根據裝置內的任何音訊串流（capture/playback）活動，獨立地做出電源切換的決策，而無需 userspace 應用程式的干預。

以下圖為例，如果要從類比輸入擷取音頻，只需啟用 ADC（左圖）。如果想要從數位麥克風擷取語音，則需要啟用 DMIC（右圖）。其他與目標 stream 無關的元件，則可以被關閉進而達到省電。

![image](https://hackmd.io/_uploads/S1eWlWKyZe.png)


## DAPM Widgets

DAPM Widgets 能分為多種類型。Widgets 通常都具有個別的名稱、暫存器、shift 和 invert 這些特徵(參數)。部分 Widgets 還具有 stream 名稱和 kcontrols 等額外參數。

可以將 Widgets 大致劃分為三種類別。
* Endpoint widgets: 是 stream 的起始點(source)或終止點(sink)。
    * [類比數位轉換器 (ADC)](https://zh.wikipedia.org/zh-tw/%E9%A1%9E%E6%AF%94%E6%95%B8%E4%BD%8D%E8%BD%89%E6%8F%9B%E5%99%A8)、[數位轉類比轉換器 (DAC)](https://zh.wikipedia.org/zh-tw/%E6%95%B8%E4%BD%8D%E9%A1%9E%E6%AF%94%E8%BD%89%E6%8F%9B%E5%99%A8)
    * 揚聲器、麥克風、Line
* Pass-through widgets: 用於連接其他 widget
    * [PGA](https://en.wikipedia.org/wiki/Programmable-gain_amplifier)、Effect
    * Mixer, Mux, Demux, Switch
* Supply widgets: 為其他 widget 提供 Clock, current, 或者 voltage

## DAPM 的運作方式

### Phase1: Power State

DAPM 運作的第一階段是確認每個元件的電源狀態。

![image](https://hackmd.io/_uploads/B1VRKxKy-e.png)

首先，DAPM 會判斷 source widgets 是否處於 active 狀態(被某個 stream 所利用)且路由到 active sink widget 時，若是，則為其通電。Sink widgets 也類似，若其為 active 且路由到 active source widgets 時，為其通電。
![image](https://hackmd.io/_uploads/HyaA5et1Zl.png)

接著，在兩個 endpoint widgets 間路徑的 widgets 都需要被上電。

![image](https://hackmd.io/_uploads/Hy_NixtJZl.png)

最後，如果 supply widgets 通往已通電部件的路徑，也向其供電。

### Phase2: Power Sequence

第二階是根據新舊配置之間的差異，計算依照甚麼順序來:
* 為需被暫停的 widget 關電
* 採用新的 route 更改
* 供電給新的需啟用的 widget

可以達到最佳的功耗。


## 實作

### `struct snd_soc_dapm_widget` 

[`struct snd_soc_dapm_widget`](https://elixir.bootlin.com/linux/v6.17.7/source/include/sound/soc-dapm.h#L515) 是用來定義每個 widgets 的資料結構。包含了名稱、相關的暫存器、shift 和 invert 等等資訊。

一般而言，驅動的開發者不會直接定義 struct 裡的每個 fied，而是透過 [`SND_SOC_DAPM_*()`](https://elixir.bootlin.com/linux/v6.17.7/source/include/sound/soc-dapm.h#L45) 系列的 macro，舉例而言，以下的 [`adau1372_dapm_widgets`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/codecs/adau1372.c#L361) 動態的定義了許多 widgets

```cpp
static const struct snd_soc_dapm_widget adau1372_dapm_widgets[] = {
	SND_SOC_DAPM_INPUT("AIN0"),
	SND_SOC_DAPM_INPUT("AIN1"),
	SND_SOC_DAPM_INPUT("AIN2"),
	SND_SOC_DAPM_INPUT("AIN3"),
	SND_SOC_DAPM_INPUT("DMIC0_1"),
	SND_SOC_DAPM_INPUT("DMIC2_3"),

	SND_SOC_DAPM_SUPPLY("MICBIAS0", ADAU1372_REG_MICBIAS, 4, 0, NULL, 0),
	SND_SOC_DAPM_SUPPLY("MICBIAS1", ADAU1372_REG_MICBIAS, 5, 0, NULL, 0),
...
```


### `struct snd_soc_dapm_route` 

對於 route 的定義，則是使用 `struct snd_soc_dapm_route`。
```cpp
struct snd_soc_dapm_route {
	const char *sink;
	const char *control;
	const char *source;

	/* Note: currently only supported for links where source is a supply */
	int (*connected)(struct snd_soc_dapm_widget *source,
			 struct snd_soc_dapm_widget *sink);

	struct snd_soc_dobj dobj;
};
```

例如以下是以靜態定義的方式定義的 route。留意到由於 source 是第三個參數，所以方向是從右到左。

```cpp
static const struct snd_soc_dapm_route adau1372_dapm_routes[] = {
	{ "PGA0", NULL, "AIN0" },
	{ "PGA1", NULL, "AIN1" },
	{ "PGA2", NULL, "AIN2" },
	{ "PGA3", NULL, "AIN3" },
```

另外這裡可以發現 source 和 sink 是以字串方式定義而非 widgets 的某種資料結構指標，因此實際的配對是由子系統通過字串的比對完成，這也表示需要很小心輸入錯誤的可能性:laughing:。

### `snd_soc_component_driver`

當 widgets 和 routes 都被建立，最後就是將其註冊至驅動程式的關鍵結構 [`snd_soc_component_driver`](https://elixir.bootlin.com/linux/v6.17.7/source/include/sound/soc-component.h#L67) 之中。

例如以下的 [`adau1372_driver`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/codecs/adau1372.c#L855) 將前面定義的 widgets 和 routes 以靜態方式關聯在一起。


```cpp
static const struct snd_soc_component_driver adau1372_driver = {
	.set_bias_level = adau1372_set_bias_level,
	.controls = adau1372_controls,
	.num_controls = ARRAY_SIZE(adau1372_controls),
	.dapm_widgets = adau1372_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(adau1372_dapm_widgets),
	.dapm_routes = adau1372_dapm_routes,
	.num_dapm_routes = ARRAY_SIZE(adau1372_dapm_routes),
	.endianness = 1,
};
```

在某些情況下可能會需要動態註冊 widgets 或 routes，此時則可以通過 [`snd_soc_dapm_new_controls`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/soc-dapm.c#L3814)/[`snd_soc_dapm_add_routes`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/soc-dapm.c#L3159) 等 API。

## 除錯

DAPM 的發生由 kernel 自動管理，不需要 userspace 的介入。不過，如果開發者發現相關問題並想分析的情況下，能夠善用某些介面。

### debugfs

每個 widgets 會以 `/sys/kernel/debug/asoc/$CARD/$COMPONENT/dapm/$WIDGET` 或 `/sys/kernel/debug/asoc/$CARD/dapm/$WIDGET`(card level widget) 的形式呈現一些資訊到 userspace。

### damp-graph

透過 [dapm-graph](https://github.com/torvalds/linux/blob/master/tools/sound/dapm-graph) 這個 script，這個工具可以將 DAPM 圖轉換為 dot 檔案以提供每個 component 之間關係的視覺化，例如以下範例。

![image](https://hackmd.io/_uploads/rk6UnCNlbx.png)



## Reference

* [Introduction to DAPM, Linux Power Management for Embedded Audio Devices - Luca Ceresoli, Bootlin](https://www.youtube.com/watch?v=kdqvyY0JFWQ)
* [Introduction to DAPM](https://bootlin.com/pub/conferences/2024/eoss/ceresoli-dapm/ceresoli-dapm.pdf)
* [Advanced Linux Sound Architecture](https://en.wikipedia.org/wiki/Advanced_Linux_Sound_Architecture)
* [Dynamic Audio Power Management for Portable Devices](https://www.kernel.org/doc/html/latest/sound/soc/dapm.html)