2. ALSA声卡驱动基础框架实现

任务目标

在Linux内核驱动中,为声卡创建PCM设备实例,并注册基本的open/close回调函数。本案例基于ALSA框架,使用snd_pcm_new()创建PCM设备,通过snd_pcm_set_ops()绑定操作集,最终在模块卸载时销毁设备。

环境与依赖

  • 内核源码树已编译,且CONFIG_SND与CONFIG_SND_PCM已启用
  • 本案例代码为独立模块,不依赖具体硬件平台,仅验证PCM设备生命周期管理

核心实现

#include <linux/module.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>

static struct snd_card *card;
static struct snd_pcm *pcm;

static int my_pcm_open(struct snd_pcm_substream *substream)
{
    pr_info("my_pcm: open called\n");
    return 0;
}

static int my_pcm_close(struct snd_pcm_substream *substream)
{
    pr_info("my_pcm: close called\n");
    return 0;
}

static const struct snd_pcm_ops my_pcm_ops = {
    .open = my_pcm_open,
    .close = my_pcm_close,
};

static int __init my_pcm_init(void)
{
    int ret;

    /* 创建声卡,card_idx=0,id字符串可自定义 */
    ret = snd_card_new(&platform_device, -1, "mycard", THIS_MODULE, 0, &card);
    if (ret < 0) {
        pr_err("my_pcm: snd_card_new failed: %d\n", ret);
        return ret;
    }

    /* 创建PCM设备:device=0,PCM类型为播放,设备编号为0 */
    ret = snd_pcm_new(card, "my_pcm_dev", 0, 1, 0, &pcm);
    if (ret < 0) {
        pr_err("my_pcm: snd_pcm_new failed: %d\n", ret);
        snd_card_free(card);
        return ret;
    }

    /* 绑定操作集:PCM播放方向使用my_pcm_ops */
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &my_pcm_ops);

    /* 注册声卡,使设备在/dev/snd/下可见 */
    ret = snd_card_register(card);
    if (ret < 0) {
        pr_err("my_pcm: snd_card_register failed: %d\n", ret);
        snd_card_free(card);
        return ret;
    }

    pr_info("my_pcm: PCM device created successfully\n");
    return 0;
}

static void __exit my_pcm_exit(void)
{
    /* 释放声卡时会自动销毁其下所有PCM设备 */
    snd_card_free(card);
    pr_info("my_pcm: PCM device destroyed\n");
}

module_init(my_pcm_init);
module_exit(my_pcm_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal ALSA PCM device create/destroy example");

编译与加载

# 使用内核构建系统编译为模块
obj-m := my_pcm.o
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载模块
sudo insmod my_pcm.ko

# 卸载模块
sudo rmmod my_pcm

验证方法

  1. 加载后检查设备节点:执行cat /proc/asound/cards,应能看到名为mycard的声卡条目。
  2. 检查PCM设备列表:执行cat /proc/asound/mycard/pcm0p/info,应显示设备名my_pcm_dev,且状态为closed。
  3. 验证回调触发:使用aplay -D hw:mycard,0 /dev/zero播放空数据,然后执行dmesg | tail,应看到my_pcm: open called;播放结束后(Ctrl+C或播放完毕),再次查看dmesg,应看到my_pcm: close called。
  4. 卸载验证:执行sudo rmmod my_pcm后,再次查看/proc/asound/cards,mycard条目应消失。

判定标准

  • 模块加载无错误,/proc/asound/cards中出现新声卡
  • PCM设备信息节点存在且可读
  • 调用aplay时dmesg中依次出现open和close日志,证明回调已正确绑定并执行
  • 模块卸载后所有设备节点被清理,无内存泄漏或内核错误

关键说明

  • snd_card_new()的第一个参数传入&platform_device仅为满足接口要求,实际驱动应传入对应的总线设备指针;本案例不依赖具体硬件,因此使用platform_device占位。
  • PCM创建时参数playback_count=1表示支持1个播放子流,capture_count=0表示不支持录音。若需双向支持,可相应调整。
  • 销毁PCM设备无需单独调用snd_pcm_free(),snd_card_free()会递归释放声卡下所有PCM实例。