# 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)