4. 声卡创建与注册
在 ALSA 框架中,声卡(Card)是音频设备的顶层抽象。一个物理声卡对应一个 struct snd_card 实例,它管理着该声卡下的所有 PCM 设备、控制接口、MIDI 设备、AC97 编解码器等子组件。本章深入剖析声卡生命周期的三个核心 API:创建(snd_card_new)、注册(snd_card_register) 和 释放(snd_card_free),并揭示其内部数据结构流转与内核交互细节。
4.1 声卡核心数据结构:struct snd_card
每个声卡在内核中由 struct snd_card 表示,该结构体是 ALSA 驱动开发者必须掌握的核心对象。下表列出其关键字段及作用:
| 字段 | 类型 | 说明 |
|---|---|---|
number | int | 声卡编号,由 ALSA 核心分配,对应 /dev/snd/controlC0 中的数字 |
id | char[64] | 声卡标识字符串,如 "HDA-Intel",用于 /proc/asound/cards 显示 |
driver | char[32] | 驱动名称,通常与模块名一致 |
shortname | char[32] | 声卡短名称,用于用户空间识别 |
longname | char[80] | 声卡长名称,包含更详细的硬件描述 |
devices | struct list_head | 挂载该声卡下所有 ALSA 设备(PCM、Control、MIDI 等)的链表头 |
controls | struct list_head | 声卡级控制项的链表头 |
private_data | void * | 驱动私有数据指针,通常指向芯片特定的结构体 |
power_state | unsigned int | 电源管理状态,如 SNDRV_CTL_POWER_D0(正常工作) |
关键理解:
struct snd_card 是声卡的"容器",它不直接操作硬件,而是通过挂载其下的 PCM 设备、控制设备等子组件与硬件交互。驱动开发者的首要任务就是正确创建并初始化这个容器。
4.2 声卡创建:snd_card_new
snd_card_new 是声卡生命周期的起点。它在内核堆中分配 struct snd_card 结构体,并完成基础初始化。其函数原型如下:
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret);
参数详解:
- parent:父设备指针,通常是 PCI 或平台设备的
&pdev->dev,用于设备模型关联。 - idx:声卡索引号,传
-1表示由 ALSA 核心自动分配。 - xid:声卡标识字符串,可为
NULL。 - module:驱动所属的内核模块,通常传
THIS_MODULE。 - extra_size:额外私有数据的大小,ALSA 核心会将
struct snd_card和私有数据一次性分配,提升缓存局部性。 - card_ret:输出参数,返回创建的
struct snd_card *指针。
内部执行流程:
- 调用
kzalloc分配sizeof(struct snd_card) + extra_size大小的内存。 - 初始化链表头:
devices、controls、mixer_list等。 - 初始化读写锁、电源管理相关字段。
- 如果
idx为-1,调用snd_card_assign_id自动分配编号。 - 将声卡加入全局声卡链表
snd_cards,并设置card->number。 - 将
extra_size对应的内存地址赋值给card->private_data。 - 返回
0表示成功,负值表示失败。
最佳实践: 使用
extra_size 参数将芯片私有数据与 snd_card 绑定分配,避免二次分配。访问私有数据时使用 snd_card_get_private_data(card) 宏。
4.3 声卡注册:snd_card_register
当声卡下的所有子设备(PCM、Control、MIDI 等)都已创建并添加到声卡后,驱动调用 snd_card_register 将声卡正式暴露给用户空间。函数原型:
int snd_card_register(struct snd_card *card);
注册过程的核心步骤:
- 创建设备节点:在
/dev/snd/下创建controlC0、pcmC0D0p等设备文件。 - 注册到设备模型:将声卡作为设备挂载到 Linux 设备模型树中。
- 创建 proc 文件:在
/proc/asound/下创建card0目录,包含id、pcm0p等状态文件。 - 通知用户空间:通过
kobject_uevent发送ADD事件,触发udev加载 ALSA 用户空间库。 - 遍历子设备:对挂载在
card->devices链表上的每个子设备调用其注册回调。
重要约束: 必须在调用
snd_card_register 之前完成所有子设备的创建和添加。注册后添加的设备不会自动暴露给用户空间,需要手动触发重新注册或使用 snd_device_register。
4.4 声卡释放:snd_card_free
当驱动卸载或发生致命错误时,调用 snd_card_free 释放声卡及其所有子设备资源。函数原型:
int snd_card_free(struct snd_card *card);
释放流程(按顺序执行):
- 设置断开标志:将
card->shutdown置为1,阻止新的 I/O 操作。 - 通知用户空间:发送
REMOVE事件,关闭所有打开的文件句柄。 - 释放子设备:遍历
card->devices链表,对每个设备调用其析构回调(snd_device_free)。 - 释放控制项:释放所有声卡级的控制项(
snd_ctl_free)。 - 移除 proc 文件:删除
/proc/asound/card0目录及其内容。 - 从全局链表移除:将声卡从
snd_cards链表中摘除。 - 释放内存:调用
kfree释放struct snd_card及私有数据。
异步释放: 如果在原子上下文(如中断处理)中需要释放声卡,应使用
snd_card_free_when_closed,它会等待所有文件句柄关闭后再执行实际释放。
4.5 声卡生命周期 SVG 流程图
下图展示了从 snd_card_new 到 snd_card_register 再到 snd_card_free 的完整生命周期,以及各阶段驱动的典型操作:
4.6 典型驱动代码模板
以下代码展示了在 PCI 驱动 probe 函数中使用这三个 API 的标准模式:
static int my_pci_probe(struct pci_dev *pci, const struct pci_device_id *id)
{
struct snd_card *card;
struct my_chip *chip;
int err;
/* 步骤1: 创建声卡,extra_size 包含私有数据 */
err = snd_card_new(&pci->dev, -1, NULL, THIS_MODULE,
sizeof(struct my_chip), &card);
if (err < 0)
return err;
chip = snd_card_get_private_data(card);
chip->card = card;
/* 步骤2: 初始化硬件,创建 PCM/Control 等子设备 */
err = snd_my_pcm_new(card, chip);
if (err < 0)
goto error;
err = snd_my_mixer_new(card, chip);
if (err < 0)
goto error;
/* 步骤3: 注册声卡,暴露给用户空间 */
err = snd_card_register(card);
if (err < 0)
goto error;
pci_set_drvdata(pci, card);
return 0;
error:
snd_card_free(card); /* 统一错误回滚 */
return err;
}
static void my_pci_remove(struct pci_dev *pci)
{
struct snd_card *card = pci_get_drvdata(pci);
snd_card_free(card); /* 模块卸载时释放 */
}
4.7 常见错误与调试建议
- 重复注册:对同一个
snd_card调用两次snd_card_register会导致内核警告。应在注册前检查card->registered标志。 - 内存泄漏:如果在
snd_card_new之后、snd_card_register之前发生错误,必须调用snd_card_free释放已分配的资源,不能直接返回。 - 子设备未添加:注册后用户空间无法看到任何 PCM 或控制设备,通常是因为忘记调用
snd_device_new将子设备挂载到声卡。 - 调试手段:检查
/proc/asound/cards确认声卡是否注册成功;使用cat /proc/asound/card0/pcm0p/sub0/hw_params查看 PCM 设备状态。
核心总结:
snd_card_new 分配容器,snd_card_register 暴露接口,snd_card_free 清理回滚。三者构成声卡驱动的"三段式"生命周期管理,是所有 ALSA 驱动的基础骨架。