4. ALSA 驱动初始化流程:模块入口、probe 函数、snd_card_new、snd_pcm_new、注册与释放

ALSA 音频驱动的初始化流程是驱动开发者必须掌握的核心路径。一个标准的 ALSA 驱动从内核模块加载开始,经过设备匹配、声卡对象创建、PCM 设备实例化,最终完成注册并暴露给用户空间。本章将拆解这一完整链路,并重点分析各阶段的关键 API 与资源管理策略。

4.1 模块入口与出口

每个 ALSA 驱动首先是一个内核模块,必须定义模块的加载与卸载函数。通常使用 module_init 和 module_exit 宏来注册入口和出口点。

关键点: 模块入口函数负责调用平台驱动注册 API(如 platform_driver_register),而出口函数则执行反注册。驱动本身不在此处创建声卡,真正的初始化延迟到 probe 阶段。
/* 模块入口 */
static int __init my_audio_driver_init(void)
{
    return platform_driver_register(&my_audio_driver);
}
module_init(my_audio_driver_init);

/* 模块出口 */
static void __exit my_audio_driver_exit(void)
{
    platform_driver_unregister(&my_audio_driver);
}
module_exit(my_audio_driver_exit);
宏 / 函数 作用 调用时机
module_init() 注册模块入口函数 insmod / 内核启动加载时
module_exit() 注册模块出口函数 rmmod 时
platform_driver_register() 向内核注册平台驱动 模块入口中调用
platform_driver_unregister() 从内核移除平台驱动 模块出口中调用
模块入口与出口流程 module_init() platform_driver_register() module_exit() platform_driver_unregister()

4.2 probe 函数:设备匹配与声卡创建起点

当平台驱动与设备树节点或平台设备名称匹配成功后,内核自动调用驱动的 probe 函数。这是 ALSA 驱动初始化的真正起点。在 probe 中,开发者需要完成以下核心步骤:

  1. 获取硬件资源:通过 platform_get_resource 获取 I/O 内存、IRQ、DMA 等资源。
  2. 创建声卡对象:调用 snd_card_new 分配并初始化 struct snd_card。
  3. 创建 PCM 设备:调用 snd_pcm_new 创建 PCM 实例,并绑定回调函数。
  4. 注册声卡:调用 snd_card_register 将声卡暴露给用户空间。
最佳实践: 在 probe 函数中,建议使用 devm_ 系列资源管理 API(如 devm_snd_card_new),可以自动处理错误路径和释放逻辑,减少内存泄漏风险。
static int my_audio_probe(struct platform_device *pdev)
{
    struct snd_card *card;
    struct snd_pcm *pcm;
    int err;

    /* 步骤1: 创建声卡 */
    err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, sizeof(struct my_chip), &card);
    if (err < 0)
        return err;

    /* 步骤2: 创建PCM设备 */
    err = snd_pcm_new(card, "my_audio_pcm", 0, 1, 1, &pcm);
    if (err < 0)
        goto err_free_card;

    /* 步骤3: 设置PCM操作回调 */
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &my_playback_ops);
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &my_capture_ops);

    /* 步骤4: 注册声卡 */
    err = snd_card_register(card);
    if (err < 0)
        goto err_free_card;

    platform_set_drvdata(pdev, card);
    return 0;

err_free_card:
    snd_card_free(card);
    return err;
}

4.3 snd_card_new:声卡对象的诞生

snd_card_new 是 ALSA 驱动中最重要的初始化函数之一。它分配并初始化 struct snd_card,该结构体是声卡的顶层容器,管理所有子设备(PCM、Control、MIDI 等)。

参数 类型 说明
parent struct device * 父设备指针,通常为 &pdev->dev
idx int 声卡编号,-1 表示自动分配
xid const char * 声卡 ID 字符串,可为 NULL
module struct module * 所属模块,通常为 THIS_MODULE
extra_size int 额外私有数据大小,用于扩展 chip 结构
card_ret struct snd_card ** 输出参数,返回创建的声卡指针
注意: snd_card_new 只分配内存并初始化基本字段,不会注册设备到用户空间。注册必须通过后续的 snd_card_register 完成。在 snd_card_new 和 snd_card_register 之间,开发者可以自由添加子设备。
snd_card_new 分配的核心结构 struct snd_card 声卡顶层容器 PCM 设备列表 Control 设备 MIDI / 其他 private_data (extra_size 指定大小)

4.4 snd_pcm_new:PCM 设备的实例化

snd_pcm_new 在声卡对象下创建一个 PCM 设备实例。PCM 设备是音频数据流的核心通道,每个 PCM 实例可以包含一个 playback 流和一个 capture 流。

int snd_pcm_new(struct snd_card *card, const char *id, int device,
                int playback_count, int capture_count,
                struct snd_pcm **rpcm);
参数 说明
card 所属声卡指针
id PCM 设备名称标识
device 设备编号,通常从 0 开始
playback_count playback 子流数量(0 表示不支持播放)
capture_count capture 子流数量(0 表示不支持录音)
rpcm 输出参数,返回 PCM 实例指针

创建 PCM 设备后,必须通过 snd_pcm_set_ops 为每个流方向设置操作回调结构体。回调结构体 struct snd_pcm_ops 包含 open、close、ioctl、hw_params、prepare、trigger、pointer 等关键方法。

典型配置: 对于只支持播放的简单设备,设置 playback_count = 1, capture_count = 0。对于全双工设备,两者都设为 1。多声道设备可以设置大于 1 的子流数量。

4.5 注册与释放:snd_card_register 与 snd_card_free

当所有子设备(PCM、Control、MIDI 等)都添加到声卡后,调用 snd_card_register 将声卡注册到 ALSA 核心,并创建对应的设备文件节点(如 /dev/snd/pcmC0D0p)。

释放路径通常发生在驱动卸载或 probe 失败时。snd_card_free 会递归释放声卡下的所有子设备,并注销设备文件。

函数 功能 调用场景
snd_card_register(card) 注册声卡,创建用户空间设备节点 probe 成功,所有子设备添加完毕后
snd_card_free(card) 释放声卡及其所有子设备 probe 失败、remove 函数、模块卸载
devm_snd_card_free(card) 设备资源管理的自动释放版本 配合 devm_ 系列 API 使用
ALSA 驱动初始化与释放完整流程 probe 函数入口 snd_card_new() snd_pcm_new() snd_card_register() 设备就绪 remove / 错误 snd_card_free()

4.6 错误处理与资源释放最佳实践

ALSA 驱动初始化过程中,每一步都可能失败。良好的错误处理机制是驱动稳定性的关键。推荐使用 devm_ 系列 API 简化资源管理:

  • devm_snd_card_new:自动在设备分离时调用 snd_card_free。
  • devm_platform_ioremap_resource:自动管理 I/O 内存映射。
  • devm_request_irq:自动释放中断请求。
常见错误: 在 probe 中调用 snd_card_new 后,如果后续步骤失败,必须调用 snd_card_free 释放声卡。忘记释放会导致声卡对象泄漏,且无法再次加载驱动。使用 devm_snd_card_new 可以有效避免此问题。
/* 使用 devm_ 系列 API 的简化 probe */
static int my_audio_probe_devm(struct platform_device *pdev)
{
    struct snd_card *card;
    struct snd_pcm *pcm;
    int err;

    /* devm_snd_card_new 自动管理释放 */
    err = devm_snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE,
                            sizeof(struct my_chip), &card);
    if (err < 0)
        return err;

    err = snd_pcm_new(card, "my_audio_pcm", 0, 1, 1, &pcm);
    if (err < 0)
        return err;  /* devm 机制会自动释放 card */

    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &my_playback_ops);

    err = snd_card_register(card);
    if (err < 0)
        return err;

    platform_set_drvdata(pdev, card);
    return 0;
}

4.7 初始化流程总结

阶段 关键函数 输出 / 状态
模块加载 module_init → platform_driver_register 驱动注册到内核
设备匹配 内核自动调用 probe 进入驱动初始化
声卡创建 snd_card_new / devm_snd_card_new 获得 struct snd_card *
PCM 创建 snd_pcm_new + snd_pcm_set_ops 获得 struct snd_pcm *
声卡注册 snd_card_register 创建设备节点,用户空间可见
释放 snd_card_free / devm 自动释放 回收所有资源
核心记忆点: ALSA 驱动初始化遵循“先创建、后注册、再使用”的原则。snd_card_new 是声卡的生命起点,snd_card_register 是声卡对外可见的开关,snd_card_free 是资源回收的终点。掌握这三个函数的调用时机和错误处理,就掌握了 ALSA 驱动初始化的精髓。