4. ALSA 驱动初始化流程:模块入口、probe 函数、snd_card_new、snd_pcm_new、注册与释放
ALSA 音频驱动的初始化流程是驱动开发者必须掌握的核心路径。一个标准的 ALSA 驱动从内核模块加载开始,经过设备匹配、声卡对象创建、PCM 设备实例化,最终完成注册并暴露给用户空间。本章将拆解这一完整链路,并重点分析各阶段的关键 API 与资源管理策略。
4.1 模块入口与出口
每个 ALSA 驱动首先是一个内核模块,必须定义模块的加载与卸载函数。通常使用 module_init 和 module_exit 宏来注册入口和出口点。
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() |
从内核移除平台驱动 | 模块出口中调用 |
4.2 probe 函数:设备匹配与声卡创建起点
当平台驱动与设备树节点或平台设备名称匹配成功后,内核自动调用驱动的 probe 函数。这是 ALSA 驱动初始化的真正起点。在 probe 中,开发者需要完成以下核心步骤:
- 获取硬件资源:通过
platform_get_resource获取 I/O 内存、IRQ、DMA 等资源。 - 创建声卡对象:调用
snd_card_new分配并初始化struct snd_card。 - 创建 PCM 设备:调用
snd_pcm_new创建 PCM 实例,并绑定回调函数。 - 注册声卡:调用
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 之间,开发者可以自由添加子设备。
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 使用 |
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 自动释放 |
回收所有资源 |
snd_card_new 是声卡的生命起点,snd_card_register 是声卡对外可见的开关,snd_card_free 是资源回收的终点。掌握这三个函数的调用时机和错误处理,就掌握了 ALSA 驱动初始化的精髓。