Skip to content

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.csound/soc/soc-core.csound/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)

c
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):

c
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_input0输入引脚
snd_soc_dapm_output1输出引脚
snd_soc_dapm_mux2从多个输入选择1个模拟信号
snd_soc_dapm_demux3将输入连接到多个输出之一
snd_soc_dapm_mixer4混合多个模拟信号
snd_soc_dapm_pga5可编程增益/衰减
snd_soc_dapm_adc6模数转换器
snd_soc_dapm_dac7数模转换器
snd_soc_dapm_hp8耳机
snd_soc_dapm_spk9扬声器
snd_soc_dapm_mic10麦克风
snd_soc_dapm_supply14电源/时钟供应
snd_soc_dapm_aif_in18音频接口输入
snd_soc_dapm_aif_out19音频接口输出
snd_soc_dapm_dai_in22DAI结构链接输入
snd_soc_dapm_dai_out23DAI结构链接输出
snd_soc_dapm_dai_link24两个DAI结构之间的链接

1.3 Widget 电源序列

上电序列 dapm_up_seq[] (sound/soc/soc-dapm.c:74-113):

c
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):

c
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):

c
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):

c
// 遍历指定方向的所有路径
#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):

c
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):

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

c
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):

c
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):

c
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):

c
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):

c
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);
}

struct snd_soc_dai_link (include/sound/soc.h:702-799):

c
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
    ...
};

struct snd_soc_dai_link_component (include/sound/soc.h:642-658):

c
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):

c
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):

c
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):

c
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):

c
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):

c
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的参数信息:

c
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 widget

8. 知识点关联表格

知识点源码位置关键函数/结构体关联模块
DAPM上下文soc-dapm.c:44snd_soc_dapm_contextBias Level管理
Widget类型soc-dapm.h:422enum snd_soc_dapm_type电源序列
Widget电源序列soc-dapm.c:74dapm_up_seq[]上电顺序
DAPM路径soc-dapm.h:486struct snd_soc_dapm_path路径连接
路径添加soc-dapm.c:604dapm_add_path()路由建立
路径连接/断开soc-dapm.c:2621dapm_connect_path()动态路由
Widget结构soc-dapm.h:516struct snd_soc_dapm_widget控件定义
Widget事件soc-dapm.c:1824dapm_seq_check_event()事件处理
Widget电源检查soc-dapm.c:1721dapm_widget_power_check()电源决策
Kcontrol数据soc-dapm.c:383dapm_kcontrol_data控件数据
Card结构soc.h:972struct snd_soc_card卡片抽象
Card绑定soc-core.c:2163snd_soc_bind_card()组件绑定
组件探查soc-core.c:1602soc_probe_component()驱动加载
DAI Linksoc.h:702struct snd_soc_dai_link链接抽象
DAI匹配soc-core.c:273snd_soc_is_matching_dai()设备发现
DAI查找soc-core.c:917snd_soc_find_dai()设备定位
Runtime添加soc-core.c:1175snd_soc_add_pcm_runtime()运行时创建
hw_paramssoc-pcm.c:1070__soc_pcm_hw_params()参数配置
DAI参数更新soc-dapm.csnd_soc_dapm_update_dai()DAPM同步
DAPM同步soc-dapm.c:2252dapm_power_widgets()电源管理
Bias Levelsoc-dapm.c:1093snd_soc_dapm_set_bias_level()电源状态机

9. 调试与分析技巧

9.1 DAPM 调试

通过debugfs查看DAPM状态:

bash
# 查看DAPM偏置级别
cat /sys/kernel/debug/asoc/xxx/bias_level

# 查看widget电源状态
cat /sys/kernel/debug/asoc/xxx/dapm/xxx_widget_name

9.2 关键Tracepoints

  • snd_soc_dapm_path: 路径遍历追踪
  • snd_soc_dapm_widget_power: Widget电源状态变化
  • snd_soc_dapm_widget_event_start/end: Widget事件追踪

9.3 常用调试命令

bash
# 查看注册的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核心头文件

基于 VitePress 构建