3. 内核音频驱动框架
在 Linux ALSA 音频驱动中,snd_card、snd_pcm 和 snd_control 是三个最核心的内核结构体。它们共同构成了声卡驱动的骨架:snd_card 是顶层容器,代表一个物理或逻辑声卡;snd_pcm 负责音频数据流的传输(播放/录音);snd_control 负责混音器、音量、开关等控制通路。理解三者之间的层次关系与生命周期,是编写和调试声卡驱动的第一步。
调试要点: 在驱动探测(probe)阶段,如果
snd_card 创建失败,后续的 PCM 和 Control 设备均无法注册。排查时优先检查 snd_card_new 的返回值以及 card->number 是否冲突。
3.1 核心结构体概述
| 结构体 | 头文件 | 主要职责 | 生命周期 |
|---|---|---|---|
struct snd_card |
sound/core.h |
声卡顶层管理:设备号、ID、组件列表、电源管理 | 从 snd_card_new 到 snd_card_free |
struct snd_pcm |
sound/pcm.h |
PCM 设备:播放/捕获流、硬件参数、DMA 缓冲区 | 从 snd_pcm_new 到 snd_device_free |
struct snd_kcontrol |
sound/control.h |
控制通路:混音器、音量、开关、枚举控件 | 从 snd_ctl_new1 到 snd_ctl_remove |
关键关系: 一个
snd_card 可以包含多个 snd_pcm 实例(例如:播放 + 捕获,或 HDMI + 模拟),也可以包含多个 snd_kcontrol 实例。所有子设备通过 snd_device 链表挂接到 snd_card 的 devices 字段上。
3.2 结构体关系全景图
下面的 SVG 图展示了 snd_card、snd_pcm、snd_kcontrol 之间的包含与引用关系,以及它们与底层硬件、用户空间设备节点的映射。
3.3 snd_card:声卡顶层容器
struct snd_card 是 ALSA 驱动中最高级别的抽象。每个声卡驱动在探测函数中必须首先创建 snd_card 实例,然后在其上挂载 PCM、Control、MIDI 等子设备。
3.3.1 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
number |
int |
声卡编号,对应 /dev/snd/controlCX 中的 X |
id |
char[16] |
声卡标识字符串,如 "HDA Intel" |
driver |
char[16] |
驱动名称,用于匹配 |
devices |
struct list_head |
挂载所有子设备(snd_device)的链表头 |
private_data |
void * |
指向驱动私有数据结构,如硬件描述符 |
power_state |
unsigned int |
电源状态管理 |
3.3.2 创建与释放流程
/* 创建 snd_card */
struct snd_card *card;
int err;
err = snd_card_new(&pdev->dev, index, id, THIS_MODULE,
sizeof(struct my_chip), &card);
if (err < 0) {
dev_err(&pdev->dev, "snd_card_new failed: %d\n", err);
return err;
}
/* card->private_data 自动指向 my_chip 实例 */
/* ... 注册 PCM、Control 等子设备 ... */
/* 注册声卡,暴露给用户空间 */
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
/* 释放时 */
snd_card_free(card);
常见调试陷阱:
snd_card_new 的 index 参数如果传入 -1,内核会自动分配一个空闲编号。但如果驱动强制指定了一个已被占用的编号,snd_card_new 会返回 -ENODEV。调试时可通过 /proc/asound/cards 查看当前已注册的声卡编号。
3.4 snd_pcm:音频数据流引擎
struct snd_pcm 是 ALSA 中负责音频数据读写的核心对象。每个 PCM 实例可以包含最多两个流:播放(playback) 和 捕获(capture)。每个流又包含一个或多个 substream,对应不同的声道或设备实例。
3.4.1 PCM 层次结构
3.4.2 PCM 操作集
驱动通过 struct snd_pcm_ops 向 ALSA 核心注册硬件操作回调:
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream);
int (*close)(struct snd_pcm_substream *substream);
int (*ioctl)(struct snd_pcm_substream *substream,
unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*prepare)(struct snd_pcm_substream *substream);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
};
调试关键:
trigger 回调是中断处理的入口点。当 DMA 传输启动或停止时,trigger 被调用,驱动应在此时使能或禁用硬件中断。pointer 回调返回当前 DMA 硬件指针位置,用于 snd_pcm_period_elapsed 的中断处理。
3.5 snd_kcontrol:控制通路接口
struct snd_kcontrol 是 ALSA 控制接口的核心,用于暴露混音器、音量、开关、枚举选择器等用户可调节的参数。每个 kcontrol 通过 get、put、info 三个回调与硬件交互。
3.5.1 控制类型与回调
| 回调 | 功能 | 调用时机 |
|---|---|---|
info |
返回控件的类型、取值范围、名称等信息 | 用户空间调用 ioctl(..., SNDRV_CTL_IOCTL_ELEM_INFO) |
get |
读取当前硬件状态,填充到 snd_ctl_elem_value |
用户空间调用 ioctl(..., SNDRV_CTL_IOCTL_ELEM_READ) |
put |
将用户设置的值写入硬件寄存器 | 用户空间调用 ioctl(..., SNDRV_CTL_IOCTL_ELEM_WRITE) |
3.5.2 创建 kcontrol 示例
static const struct snd_kcontrol_new my_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Volume",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = my_ctl_info,
.get = my_ctl_get,
.put = my_ctl_put,
};
/* 在探测函数中注册 */
err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip));
if (err < 0) {
dev_err(card->dev, "failed to add control: %d\n", err);
return err;
}
调试技巧: 使用
amixer 或 alsamixer 可以列出所有注册的 kcontrol。如果驱动注册了控件但用户空间看不到,检查 snd_ctl_add 的返回值以及 iface 字段是否设置正确(常见错误:混音器控件使用了 SNDRV_CTL_ELEM_IFACE_CARD 而非 _MIXER)。
3.6 三者的协同工作流程
在声卡探测函数中,三个结构体的典型创建顺序如下:
- snd_card_new:创建顶层声卡容器,分配编号。
- snd_pcm_new:创建 PCM 设备,指定播放/捕获流数量,挂载到 card。
- snd_ctl_add:逐个添加控制控件(音量、开关等)。
- snd_card_register:将所有子设备注册到 ALSA 核心,创建设备节点。
在中断处理中,三者的协作关系如下:
- 硬件 DMA 完成一个 period 后触发中断。
- 中断服务程序调用
snd_pcm_period_elapsed(substream),通知 PCM 核心层。 - PCM 核心层唤醒等待的进程,触发
pointer回调更新硬件指针。 - 如果中断与控件相关(如音量旋钮中断),则调用对应 kcontrol 的
put回调更新状态,并通过snd_ctl_notify通知用户空间。
中断上下文注意事项:
snd_pcm_period_elapsed 可以在中断上下文中调用,但 snd_ctl_notify 需要谨慎——它可能会触发用户空间事件,建议在中断下半部(tasklet 或 threaded IRQ)中调用。
3.7 调试检查清单
| 检查项 | 命令/方法 | 预期结果 |
|---|---|---|
| 声卡是否注册 | cat /proc/asound/cards |
显示声卡编号和名称 |
| PCM 设备是否可见 | cat /proc/asound/pcm |
列出所有 PCM 设备及流方向 |
| 控件是否注册 | amixer controls |
显示所有 kcontrol 名称 |
| 设备节点是否存在 | ls -l /dev/snd/ |
存在 pcmC0D0p、controlC0 等 |
| 中断是否触发 | cat /proc/interrupts | grep audio |
中断计数持续增长 |