Linux Sound 子系统深度分析 R2
概述
本文档深入分析 Linux Kernel Sound 子系统中的 ASoC(ALSA System on Chip) 核心框架,重点关注 Dynamic Audio Power Management (DAPM)、DAI Link 匹配机制以及 PCM hw_params 流程。源码基于 sound/soc/soc-dapm.c、sound/soc/soc-core.c 和 sound/soc/soc-pcm.c。
1. ASoC DAPM (Dynamic Audio Power Management)
1.1 DAPM 上下文结构体
struct snd_soc_dapm_context (sound/soc/soc-dapm.c:44-62)
struct snd_soc_dapm_context {
enum snd_soc_bias_level bias_level; // 当前偏置电平
bool idle_bias; // 空闲时使用BIAS_OFF而非STANDBY
struct snd_soc_component *component; // 父组件
struct snd_soc_card *card; // 父卡片
enum snd_soc_bias_level target_bias_level; // 目标偏置电平
struct list_head list; // DAPM更新列表
struct snd_soc_dapm_widget *wcache_sink; // widget缓存
struct snd_soc_dapm_widget *wcache_source;
};偏置电平枚举 (include/sound/soc-dapm.h:415-420):
enum snd_soc_bias_level {
SND_SOC_BIAS_OFF = 0, // 电源关闭
SND_SOC_BIAS_STANDBY = 1, // 低功耗待机
SND_SOC_BIAS_PREPARE = 2, // 准备音频操作
SND_SOC_BIAS_ON = 3, // 完全开启
};1.2 Widget 类型枚举
enum snd_soc_dapm_type (include/sound/soc-dapm.h:422-465):
| 类型 | 值 | 描述 |
|---|---|---|
snd_soc_dapm_input | 0 | 输入引脚 |
snd_soc_dapm_output | 1 | 输出引脚 |
snd_soc_dapm_mux | 2 | 从多个输入选择1个模拟信号 |
snd_soc_dapm_demux | 3 | 将输入连接到多个输出之一 |
snd_soc_dapm_mixer | 4 | 混合多个模拟信号 |
snd_soc_dapm_pga | 5 | 可编程增益/衰减 |
snd_soc_dapm_adc | 6 | 模数转换器 |
snd_soc_dapm_dac | 7 | 数模转换器 |
snd_soc_dapm_hp | 8 | 耳机 |
snd_soc_dapm_spk | 9 | 扬声器 |
snd_soc_dapm_mic | 10 | 麦克风 |
snd_soc_dapm_supply | 14 | 电源/时钟供应 |
snd_soc_dapm_aif_in | 18 | 音频接口输入 |
snd_soc_dapm_aif_out | 19 | 音频接口输出 |
snd_soc_dapm_dai_in | 22 | DAI结构链接输入 |
snd_soc_dapm_dai_out | 23 | DAI结构链接输出 |
snd_soc_dapm_dai_link | 24 | 两个DAI结构之间的链接 |
1.3 Widget 电源序列
上电序列 dapm_up_seq[] (sound/soc/soc-dapm.c:74-113):
static int dapm_up_seq[] = {
[snd_soc_dapm_pre] = 1,
[snd_soc_dapm_regulator_supply] = 2,
[snd_soc_dapm_pinctrl] = 2,
[snd_soc_dapm_clock_supply] = 2,
[snd_soc_dapm_supply] = 3,
[snd_soc_dapm_dai_link] = 3,
[snd_soc_dapm_micbias] = 4,
[snd_soc_dapm_vmid] = 4,
[snd_soc_dapm_dai_in] = 5,
[snd_soc_dapm_dai_out] = 5,
[snd_soc_dapm_aif_in] = 5,
[snd_soc_dapm_aif_out] = 5,
[snd_soc_dapm_mic] = 6,
[snd_soc_dapm_input] = 6,
[snd_soc_dapm_output] = 6,
[snd_soc_dapm_mux] = 7,
[snd_soc_dapm_demux] = 7,
[snd_soc_dapm_dac] = 8,
[snd_soc_dapm_switch] = 9,
[snd_soc_dapm_mixer] = 9,
[snd_soc_dapm_pga] = 10,
[snd_soc_dapm_adc] = 11,
[snd_soc_dapm_out_drv] = 12,
[snd_soc_dapm_hp] = 12,
[snd_soc_dapm_line] = 12,
[snd_soc_dapm_spk] = 13,
[snd_soc_dapm_kcontrol] = 14,
[snd_soc_dapm_post] = 15,
};下电序列 dapm_down_seq[] (sound/soc/soc-dapm.c:115-154) 按相反顺序执行。
2. snd_soc_dapm_path 结构详解
2.1 数据结构
struct snd_soc_dapm_path (include/sound/soc-dapm.h:486-513):
struct snd_soc_dapm_path {
const char *name; // 路径名称
// source(输入)和sink(输出)widget的联合体
union {
struct {
struct snd_soc_dapm_widget *source; // 源widget
struct snd_soc_dapm_widget *sink; // 目标widget
};
struct snd_soc_dapm_widget *node[2]; // 索引: 0=IN, 1=OUT
};
// 状态标志
u32 connect:1; // source和sink widget已连接
u32 walking:1; // 路径正在被遍历
u32 is_supply:1; // 至少一个连接的widget是supply类型
// 条件路径的回调函数
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink);
struct list_head list_node[2]; // 双向链表节点
struct list_head list_kcontrol; // kcontrol关联列表
struct list_head list; // 全局路径列表
};2.2 路径添加流程
dapm_add_path() 函数 (sound/soc/soc-dapm.c:604-704):
dapm_add_path(dapm, wsource, wsink, control, connected)
|
+---> 验证supply widget约束
|
+---> 检查动态路径有效性
|
+---> kzalloc_obj(struct snd_soc_dapm_path) 分配路径
|
+---> 初始化 path->node[SND_SOC_DAPM_DIR_IN] = wsource
| path->node[SND_SOC_DAPM_DIR_OUT] = wsink
|
+---> 根据control参数设置连接状态
| - control == NULL: path->connect = 1 (静态路径)
| - source是demux: dapm_connect_mux()
| - sink是mux: dapm_connect_mux()
| - sink是mixer/switch: dapm_connect_mixer()
|
+---> list_add(&path->list, &dapm->card->paths)
|
+---> list_add(&path->list_node[dir], &path->node[dir]->edges[dir])
| (将路径添加到source和sink的边列表)
|
+---> dapm_path_invalidate(path) 刷新路径缓存2.3 路径连接/断开机制
dapm_connect_path() 函数 (sound/soc/soc-dapm.c:2621-2631):
static void dapm_connect_path(struct snd_soc_dapm_path *path,
bool connect, const char *reason)
{
if (path->connect == connect)
return;
path->connect = connect; // 更新连接状态
dapm_mark_dirty(path->source, reason); // 标记source为dirty
dapm_mark_dirty(path->sink, reason); // 标记sink为dirty
dapm_path_invalidate(path); // 使端点缓存失效
}2.4 路径遍历宏
Widget路径遍历 (include/sound/soc-dapm.h:717-751):
// 遍历指定方向的所有路径
#define snd_soc_dapm_widget_for_each_path(w, dir, p) \
list_for_each_entry(p, &w->edges[dir], list_node[dir])
// 遍历所有sink路径(从widget流出)
#define snd_soc_dapm_widget_for_each_sink_path(w, p) \
snd_soc_dapm_widget_for_each_path(w, SND_SOC_DAPM_DIR_IN, p)
// 遍历所有source路径(流入widget)
#define snd_soc_dapm_widget_for_each_source_path(w, p) \
snd_soc_dapm_widget_for_each_path(w, SND_SOC_DAPM_DIR_OUT, p)3. snd_soc_widget 结构详解
3.1 Widget 数据结构
struct snd_soc_dapm_widget (include/sound/soc-dapm.h:516-570):
struct snd_soc_dapm_widget {
enum snd_soc_dapm_type id; // widget类型
const char *name; // widget名称
const char *sname; // stream名称
struct list_head list; // 卡片widget列表
struct snd_soc_dapm_context *dapm; // DAPM上下文
void *priv; // widget特定数据
struct regulator *regulator; // 关联的regulator
struct pinctrl *pinctrl; // 关联的pinctrl
// DAPM控制
int reg; // 寄存器号,负值表示无直接DAPM
unsigned char shift; // 位移
unsigned int mask; // 非移位掩码
unsigned int on_val; // 开启状态值
unsigned int off_val; // 关闭状态值
unsigned char power:1; // 当前电源状态
unsigned char active:1; // DAC/ADC上的活跃流
unsigned char connected:1; // 连接的codec引脚
unsigned char new:1; // 新完成
unsigned char force:1; // 强制状态
unsigned char ignore_suspend:1; // 挂起期间保持启用
unsigned char new_power:1; // 本次运行的新电源状态
unsigned char power_checked:1; // 本次运行已检查
unsigned char is_supply:1; // 是supply类型widget
unsigned char is_ep:2; // 是端点类型widget
int subseq; // widget类型内的排序
int (*power_check)(struct snd_soc_dapm_widget *w); // 电源检查回调
// 外部事件
unsigned short event_flags; // 事件类型标志
int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int); // 事件处理
// kcontrols
int num_kcontrols;
const struct snd_kcontrol_new *kcontrol_news;
struct snd_kcontrol **kcontrols;
struct snd_soc_dobj dobj;
// 输入/输出边
struct list_head edges[2]; // edges[0]=输入边, edges[1]=输出边
// DAPM更新期间使用
struct list_head work_list;
struct list_head power_list;
struct list_head dirty;
int endpoints[2]; // endpoints[0]=输入端点数, endpoints[1]=输出端点数
struct clk *clk;
int channel;
};3.2 Widget 事件类型
事件标志 (include/sound/soc-dapm.h:385-395):
#define SND_SOC_DAPM_PRE_PMU 0x1 // widget上电前
#define SND_SOC_DAPM_POST_PMU 0x2 // widget上电后
#define SND_SOC_DAPM_PRE_PMD 0x4 // widget下电前
#define SND_SOC_DAPM_POST_PMD 0x8 // widget下电后
#define SND_SOC_DAPM_PRE_REG 0x10 // 音频路径设置前
#define SND_SOC_DAPM_POST_REG 0x20 // 音频路径设置后
#define SND_SOC_DAPM_WILL_PMU 0x40 // 序列开始时调用
#define SND_SOC_DAPM_WILL_PMD 0x80 // 序列开始时调用
// 便捷检测宏
#define SND_SOC_DAPM_EVENT_ON(e) (e & (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU))
#define SND_SOC_DAPM_EVENT_OFF(e) (e & (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD))3.3 Widget 事件处理流程
dapm_seq_check_event() 函数 (sound/soc/soc-dapm.c:1824-1877):
static void dapm_seq_check_event(struct snd_soc_card *card,
struct snd_soc_dapm_widget *w, int event)
{
// 根据event类型确定事件名称和目标功率状态
switch (event) {
case SND_SOC_DAPM_PRE_PMU: ev_name = "PRE_PMU"; power = 1; break;
case SND_SOC_DAPM_POST_PMU: ev_name = "POST_PMU"; power = 1; break;
case SND_SOC_DAPM_PRE_PMD: ev_name = "PRE_PMD"; power = 0; break;
case SND_SOC_DAPM_POST_PMD: ev_name = "POST_PMD"; power = 0; break;
}
// 检查widget是否处于目标状态
if (w->new_power != power)
return;
// 如果widget有event回调且event_flags匹配
if (w->event && (w->event_flags & event)) {
ret = w->event(w, NULL, event); // 调用widget事件处理
if (ret < 0)
dev_err(dev, "ASoC: %s: %s event failed: %d\n",
ev_name, w->name, ret);
}
}3.4 dapm_kcontrol_data 结构
struct dapm_kcontrol_data (sound/soc/soc-dapm.c:383-388):
struct dapm_kcontrol_data {
unsigned int value; // kcontrol当前值
struct snd_soc_dapm_widget *widget; // 关联的widget
struct list_head paths; // 使用此kcontrol的路径列表
struct snd_soc_dapm_widget_list *wlist; // widget列表
};4. snd_soc_card 结构与组件绑定
4.1 Card 数据结构
struct snd_soc_card (include/sound/soc.h:972-1090):
struct snd_soc_card {
const char *name; // 卡片名称
const char *long_name; // 卡片全名
const char *driver_name; // 驱动名称
const char *components; // 组件字符串
struct device *dev; // 设备指针
struct snd_card *snd_card; // ALSA卡片
struct module *owner; // 模块所有者
struct mutex mutex; // 卡片互斥锁
struct mutex dapm_mutex; // DAPM互斥锁
struct mutex pcm_mutex; // PCM互斥锁
enum snd_soc_pcm_subclass pcm_subclass; // PCM子类
// 生命周期回调
int (*probe)(struct snd_soc_card *card);
int (*late_probe)(struct snd_soc_card *card);
void (*fixup_controls)(struct snd_soc_card *card);
int (*remove)(struct snd_soc_card *card);
// 电源管理回调
int (*suspend_pre)(struct snd_soc_card *card);
int (*suspend_post)(struct snd_soc_card *card);
int (*resume_pre)(struct snd_soc_card *card);
int (*resume_post)(struct snd_soc_card *card);
// 偏置电平回调
int (*set_bias_level)(struct snd_soc_card *,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level);
int (*set_bias_level_post)(struct snd_soc_card *,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level);
// DAI链接回调
int (*add_dai_link)(struct snd_soc_card *, struct snd_soc_dai_link *link);
void (*remove_dai_link)(struct snd_soc_card *, struct snd_soc_dai_link *link);
long pmdown_time; // 电源关闭延迟时间(ms)
// DAI链接
struct snd_soc_dai_link *dai_link; // 预定义的DAI链接
int num_links; // 预定义链接数量
struct list_head rtd_list; // runtime列表
int num_rtd; // runtime数量
// codec配置
struct snd_soc_codec_conf *codec_conf;
int num_configs;
// 辅助设备
struct snd_soc_aux_dev *aux_dev;
int num_aux_devs;
struct list_head aux_comp_list; // 辅助组件列表
struct list_head component_dev_list; // 组件设备列表
// 控件
const struct snd_kcontrol_new *controls;
int num_controls;
// DAPM控件和路由
const struct snd_soc_dapm_widget *dapm_widgets;
int num_dapm_widgets;
struct snd_soc_dapm_widget *of_dapm_widgets;
int num_of_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
int num_dapm_routes;
const struct snd_soc_dapm_route *of_dapm_routes;
int num_of_dapm_routes;
// DAPM统计
struct snd_soc_dapm_stats dapm_stats;
struct list_head dapm_dirty; // dirty widget列表
// 调试相关
struct dentry *debugfs_card_root;
bool fully_routed; // 是否完全路由
bool instantiated; // 是否已实例化
...
};4.2 snd_soc_bind_card() 组件绑定流程
snd_soc_bind_card() 函数 (sound/soc/soc-core.c:2163-2320):
static int snd_soc_bind_card(struct snd_soc_card *card)
{
// 1. 填充dummy DAI (如果CPU/Codec未指定)
snd_soc_fill_dummy_dai(card);
// 2. 初始化Card的DAPM上下文
snd_soc_dapm_init(dapm, card, NULL);
// 3. 检查拓扑FE
soc_check_tplg_fes(card);
// 4. 绑定辅助设备
ret = soc_bind_aux_dev(card);
if (ret < 0) goto probe_end;
// 5. 添加PCM运行时
card->num_rtd = 0;
ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links);
if (ret < 0) goto probe_end;
// 6. 创建ALSA Sound Card
ret = snd_card_new(card->dev, ...);
if (ret < 0) goto probe_end;
// 7. 初始化调试FS
soc_init_card_debugfs(card);
soc_resume_init(card);
// 8. 添加DAPM控件
ret = snd_soc_dapm_new_controls(dapm, card->dapm_widgets, ...);
ret = snd_soc_dapm_new_controls(dapm, card->of_dapm_widgets, ...);
// 9. 卡片探测
ret = snd_soc_card_probe(card);
// 10. 探测所有组件
ret = soc_probe_link_components(card);
// 11. 探测辅助设备
ret = soc_probe_aux_devices(card);
// 12. 探测所有DAI链接
ret = soc_probe_link_dais(card);
// 13. 初始化PCM运行时
for_each_card_rtds(card, rtd) {
ret = soc_init_pcm_runtime(card, rtd);
}
// 14. 链接DAI widgets
snd_soc_dapm_link_dai_widgets(card);
snd_soc_dapm_connect_dai_link_widgets(card);
// 15. 添加控件
ret = snd_soc_add_card_controls(card, card->controls, ...);
// 16. 添加DAPM路由
ret = snd_soc_dapm_add_routes(dapm, card->dapm_routes, ...);
ret = snd_soc_dapm_add_routes(dapm, card->of_dapm_routes, ...);
// 17. 设置DMI名称
snd_soc_set_dmi_name(card);
// 18. 注册Sound Card
ret = snd_card_register(card->snd_card);
// 19. 标记卡片为已实例化
card->instantiated = 1;
// 20. 同步DAPM
snd_soc_dapm_mark_endpoints_dirty(card);
snd_soc_dapm_sync(dapm);
probe_end:
if (ret < 0)
soc_cleanup_card_resources(card);
return ret;
}4.3 组件探查流程
soc_probe_component() 函数 (sound/soc/soc-core.c:1602-1692):
static int soc_probe_component(struct snd_soc_card *card,
struct snd_soc_component *component)
{
// 1. 检查dummy组件
if (snd_soc_component_is_dummy(component))
return 0;
// 2. 绑定到卡片
component->card = card;
// 3. 设置名称前缀
soc_set_name_prefix(card, component);
// 4. 初始化调试FS
soc_init_component_debugfs(component);
// 5. 初始化DAPM
snd_soc_dapm_init(dapm, card, component);
// 6. 创建DAPM控件
ret = snd_soc_dapm_new_controls(dapm,
component->driver->dapm_widgets,
component->driver->num_dapm_widgets);
// 7. 创建DAI widgets
for_each_component_dais(component, dai) {
ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
}
// 8. 组件探测
ret = snd_soc_component_probe(component);
// 9. 组件初始化
ret = snd_soc_component_init(component);
// 10. 添加组件控件
ret = snd_soc_add_component_controls(component,
component->driver->controls,
component->driver->num_controls);
// 11. 添加DAPM路由
ret = snd_soc_dapm_add_routes(dapm,
component->driver->dapm_routes,
component->driver->num_dapm_routes);
// 12. 添加到组件列表
list_add(&component->card_list, &card->component_dev_list);
}5. DAI Link 结构与匹配机制
5.1 DAI Link 数据结构
struct snd_soc_dai_link (include/sound/soc.h:702-799):
struct snd_soc_dai_link {
const char *name; // 链接名称
const char *stream_name; // 流名称
// CPU端配置
struct snd_soc_dai_link_component *cpus;
unsigned int num_cpus;
// Codec端配置
struct snd_soc_dai_link_component *codecs;
unsigned int num_codecs;
// 通道映射(支持N:M连接)
struct snd_soc_dai_link_ch_map *ch_maps;
// Platform端配置
struct snd_soc_dai_link_component *platforms;
unsigned int num_platforms;
int id; // 可选的机器驱动链接标识
// Codec2Codec参数
const struct snd_soc_pcm_stream *c2c_params;
unsigned int num_c2c_params;
unsigned int dai_fmt; // 初始化时设置的格式
// DPCM触发类型
enum snd_soc_dpcm_trigger trigger[2];
// 初始化/退出回调
int (*init)(struct snd_soc_pcm_runtime *rtd);
void (*exit)(struct snd_soc_pcm_runtime *rtd);
// BE参数修复回调
int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params);
// 机器流操作
const struct snd_soc_ops *ops;
const struct snd_soc_compr_ops *compr_ops;
// 触发顺序
enum snd_soc_trigger_order trigger_start;
enum snd_soc_trigger_order trigger_stop;
// 标志位
unsigned int nonatomic:1;
unsigned int playback_only:1;
unsigned int capture_only:1;
unsigned int ignore_suspend:1;
unsigned int symmetric_rate:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_sample_bits:1;
unsigned int no_pcm:1; // 不为此DAI链接创建PCM(BE)
unsigned int dynamic:1; // 运行时可路由的FE
...
};5.2 DAI Link Component 结构
struct snd_soc_dai_link_component (include/sound/soc.h:642-658):
struct snd_soc_dai_link_component {
const char *name; // 组件名称
struct device_node *of_node; // OF设备节点
const char *dai_name; // DAI名称
const struct of_phandle_args *dai_args; // DAI参数
// 扩展格式 = SND_SOC_DAIFMT_Bx_Fx
unsigned int ext_fmt;
};5.3 DAI 匹配函数
snd_soc_is_matching_dai() 函数 (sound/soc/soc-core.c:273-299):
static int snd_soc_is_matching_dai(const struct snd_soc_dai_link_component *dlc,
struct snd_soc_dai *dai)
{
if (!dlc)
return 0;
// 如果有dai_args,使用参数匹配
if (dlc->dai_args)
return snd_soc_is_match_dai_args(dai->driver->dai_args, dlc->dai_args);
// 如果没有dai_name,匹配所有
if (!dlc->dai_name)
return 1;
// 按优先级尝试匹配
// 1. dai->driver->name
if (dai->driver->name &&
strcmp(dlc->dai_name, dai->driver->name) == 0)
return 1;
// 2. dai->name
if (strcmp(dlc->dai_name, dai->name) == 0)
return 1;
// 3. dai->component->name
if (dai->component->name &&
strcmp(dlc->dai_name, dai->component->name) == 0)
return 1;
return 0;
}5.4 snd_soc_find_dai() 函数
snd_soc_find_dai() 函数 (sound/soc/soc-core.c:917-934):
struct snd_soc_dai *snd_soc_find_dai(const struct snd_soc_dai_link_component *dlc)
{
struct snd_soc_component *component;
struct snd_soc_dai *dai;
lockdep_assert_held(&client_mutex);
// 遍历所有注册的组件
for_each_component(component)
if (snd_soc_is_matching_component(dlc, component))
// 遍历组件的所有DAI
for_each_component_dais(component, dai)
if (snd_soc_is_matching_dai(dlc, dai))
return dai;
return NULL;
}5.5 PCM Runtime 添加流程
snd_soc_add_pcm_runtime() 函数 (sound/soc/soc-core.c:1175-1267):
static int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
struct snd_soc_dai_link *dai_link)
{
// 1. 添加DAI链接到卡片
ret = snd_soc_card_add_dai_link(card, dai_link);
// 2. DAI链接健全性检查
ret = soc_dai_link_sanity_check(card, dai_link);
// 3. 创建新的PCM运行时
rtd = soc_new_pcm_runtime(card, dai_link);
// 4. 查找并添加CPU DAI
for_each_link_cpus(dai_link, i, cpu) {
snd_soc_rtd_to_cpu(rtd, i) = snd_soc_find_dai(cpu);
snd_soc_rtd_add_component(rtd, snd_soc_rtd_to_cpu(rtd, i)->component);
}
// 5. 查找并添加Codec DAI
for_each_link_codecs(dai_link, i, codec) {
snd_soc_rtd_to_codec(rtd, i) = snd_soc_find_dai(codec);
snd_soc_rtd_add_component(rtd, snd_soc_rtd_to_codec(rtd, i)->component);
}
// 6. 查找并添加Platform
for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
if (snd_soc_is_matching_component(platform, component))
snd_soc_rtd_add_component(rtd, component);
}
}
}6. PCM hw_params 阶段分析
6.1 hw_params 入口函数
soc_pcm_hw_params() 函数 (sound/soc/soc-pcm.c:1172-1182):
static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_hw_params(substream, params);
snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}6.2 核心 hw_params 函数
__soc_pcm_hw_params() 函数 (sound/soc/soc-pcm.c:1070-1169):
static int __soc_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai, *codec_dai;
struct snd_pcm_hw_params tmp_params;
int i, ret = 0;
snd_soc_dpcm_mutex_assert_held(rtd);
// 1. 参数对称性检查
ret = soc_pcm_params_symmetry(substream, params);
if (ret) goto out;
// 2. 调用machine driver的hw_params
ret = snd_soc_link_hw_params(substream, params);
if (ret < 0) goto out;
// 3. 配置每个Codec DAI
for_each_rtd_codec_dais(rtd, i, codec_dai) {
// 获取TDM掩码
unsigned int tdm_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream);
// 跳过不支持当前流类型的Codec
if (!snd_soc_dai_stream_valid(codec_dai, substream->stream))
continue;
// 复制参数
tmp_params = *params;
// 根据TDM掩码修复参数
if (tdm_mask)
soc_pcm_codec_params_fixup(&tmp_params, tdm_mask);
// 调用Codec DAI的hw_params
ret = snd_soc_dai_hw_params(codec_dai, substream, &tmp_params);
if (ret < 0) goto out;
// 保存DAI参数
soc_pcm_set_dai_params(codec_dai, &tmp_params);
// 更新DAPM DAI
snd_soc_dapm_update_dai(substream, &tmp_params, codec_dai);
}
// 4. 配置每个CPU DAI
for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
struct snd_soc_dai_link_ch_map *ch_maps;
unsigned int ch_mask = 0;
// 跳过不支持当前流类型的CPU
if (!snd_soc_dai_stream_valid(cpu_dai, substream->stream))
continue;
// 复制参数
tmp_params = *params;
// 构建CPU通道掩码
for_each_rtd_ch_maps(rtd, j, ch_maps)
if (ch_maps->cpu == i)
ch_mask |= ch_maps->ch_mask;
// 修复CPU通道数
if (ch_mask)
soc_pcm_codec_params_fixup(&tmp_params, ch_mask);
// 调用CPU DAI的hw_params
ret = snd_soc_dai_hw_params(cpu_dai, substream, &tmp_params);
if (ret < 0) goto out;
// 保存DAI参数
soc_pcm_set_dai_params(cpu_dai, &tmp_params);
// 更新DAPM DAI
snd_soc_dapm_update_dai(substream, &tmp_params, cpu_dai);
}
// 5. 调用component的hw_params
ret = snd_soc_pcm_component_hw_params(substream, params);
out:
if (ret < 0)
soc_pcm_hw_clean(rtd, substream, 1); // 失败时清理
return soc_pcm_ret(rtd, ret);
}6.3 hw_params 流程图
应用程序调用ioctl(SNDRV_PCM_IOCTL_HW_PARAMS)
|
v
soc_pcm_hw_params() [soc-pcm.c:1172]
|
v
__soc_pcm_hw_params() [soc-pcm.c:1070]
|
+---> soc_pcm_params_symmetry() 参数对称性检查
|
+---> snd_soc_link_hw_params() Machine driver hw_params
|
+---> for_each_rtd_codec_dais() 遍历Codec DAI
| |
| +---> snd_soc_dai_hw_params() Codec DAI配置
| |
| +---> soc_pcm_set_dai_params() 保存参数
| |
| +---> snd_soc_dapm_update_dai() 更新DAPM
|
+---> for_each_rtd_cpu_dais() 遍历CPU DAI
| |
| +---> snd_soc_dai_hw_params() CPU DAI配置
| |
| +---> soc_pcm_set_dai_params() 保存参数
| |
| +---> snd_soc_dapm_update_dai() 更新DAPM
|
+---> snd_soc_pcm_component_hw_params() Component hw_params
|
v
返回结果6.4 snd_soc_dapm_update_dai 函数
snd_soc_dapm_update_dai() 函数 (sound/soc/soc-dapm.c 相关部分):
此函数在hw_params阶段更新DAI widget的参数信息:
int snd_soc_dapm_update_dai(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream);
if (!w)
return 0;
// 更新widget的channel信息
w->channel = params_channels(params);
// 更新widget的active状态
w->active = 1;
return 0;
}7. 关键数据结构关联图
snd_soc_card
|
+---------------+---------------+
| | |
rtd_list dapm_widgets component_dev_list
| | |
snd_soc_pcm_runtime | snd_soc_component
| | |
+----+----+ | +---+----+
| | | | |
dai_link components edges[2] dapm dai_list
|
+-----+-----+-----+
| | | |
path path path path
| | | |
source sink source sink
| | | |
widget widget widget widget8. 知识点关联表格
| 知识点 | 源码位置 | 关键函数/结构体 | 关联模块 |
|---|---|---|---|
| DAPM上下文 | soc-dapm.c:44 | snd_soc_dapm_context | Bias Level管理 |
| Widget类型 | soc-dapm.h:422 | enum snd_soc_dapm_type | 电源序列 |
| Widget电源序列 | soc-dapm.c:74 | dapm_up_seq[] | 上电顺序 |
| DAPM路径 | soc-dapm.h:486 | struct snd_soc_dapm_path | 路径连接 |
| 路径添加 | soc-dapm.c:604 | dapm_add_path() | 路由建立 |
| 路径连接/断开 | soc-dapm.c:2621 | dapm_connect_path() | 动态路由 |
| Widget结构 | soc-dapm.h:516 | struct snd_soc_dapm_widget | 控件定义 |
| Widget事件 | soc-dapm.c:1824 | dapm_seq_check_event() | 事件处理 |
| Widget电源检查 | soc-dapm.c:1721 | dapm_widget_power_check() | 电源决策 |
| Kcontrol数据 | soc-dapm.c:383 | dapm_kcontrol_data | 控件数据 |
| Card结构 | soc.h:972 | struct snd_soc_card | 卡片抽象 |
| Card绑定 | soc-core.c:2163 | snd_soc_bind_card() | 组件绑定 |
| 组件探查 | soc-core.c:1602 | soc_probe_component() | 驱动加载 |
| DAI Link | soc.h:702 | struct snd_soc_dai_link | 链接抽象 |
| DAI匹配 | soc-core.c:273 | snd_soc_is_matching_dai() | 设备发现 |
| DAI查找 | soc-core.c:917 | snd_soc_find_dai() | 设备定位 |
| Runtime添加 | soc-core.c:1175 | snd_soc_add_pcm_runtime() | 运行时创建 |
| hw_params | soc-pcm.c:1070 | __soc_pcm_hw_params() | 参数配置 |
| DAI参数更新 | soc-dapm.c | snd_soc_dapm_update_dai() | DAPM同步 |
| DAPM同步 | soc-dapm.c:2252 | dapm_power_widgets() | 电源管理 |
| Bias Level | soc-dapm.c:1093 | snd_soc_dapm_set_bias_level() | 电源状态机 |
9. 调试与分析技巧
9.1 DAPM 调试
通过debugfs查看DAPM状态:
# 查看DAPM偏置级别
cat /sys/kernel/debug/asoc/xxx/bias_level
# 查看widget电源状态
cat /sys/kernel/debug/asoc/xxx/dapm/xxx_widget_name9.2 关键Tracepoints
snd_soc_dapm_path: 路径遍历追踪snd_soc_dapm_widget_power: Widget电源状态变化snd_soc_dapm_widget_event_start/end: Widget事件追踪
9.3 常用调试命令
# 查看注册的DAIs
cat /sys/kernel/debug/asoc/dais
# 查看注册的Components
cat /sys/kernel/debug/asoc/components参考源码文件
sound/soc/soc-dapm.c- DAPM核心实现sound/soc/soc-core.c- ASoC核心和Card绑定sound/soc/soc-pcm.c- PCM实现include/sound/soc-dapm.h- DAPM头文件include/sound/soc.h- ASoC核心头文件