3. PCM设备创建与销毁

任务说明

在Linux ALSA声卡驱动中,PCM设备是音频数据传输的核心接口。本案例目标是在驱动中创建PCM播放(playback)和捕获(capture)设备,并实现对应的释放函数,确保模块卸载时能够正确清理所有ALSA资源,避免内存泄漏或设备节点残留。

核心实现

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

#define DRIVER_NAME "demo_pcm"

static struct snd_card *card;
static struct snd_pcm *pcm_playback;
static struct snd_pcm *pcm_capture;

/* PCM播放操作集 */
static const struct snd_pcm_ops playback_ops = {
    .open       = NULL,  /* 本案例仅演示创建/销毁,不实现IO */
    .close      = NULL,
    .ioctl      = snd_pcm_lib_ioctl,
    .hw_params  = NULL,
    .hw_free    = NULL,
    .prepare    = NULL,
    .trigger    = NULL,
    .pointer    = NULL,
};

/* PCM捕获操作集 */
static const struct snd_pcm_ops capture_ops = {
    .open       = NULL,
    .close      = NULL,
    .ioctl      = snd_pcm_lib_ioctl,
    .hw_params  = NULL,
    .hw_free    = NULL,
    .prepare    = NULL,
    .trigger    = NULL,
    .pointer    = NULL,
};

static int __init demo_pcm_init(void)
{
    int err;

    /* 1. 创建声卡 */
    err = snd_card_new(&platform_device, -1, DRIVER_NAME,
                       THIS_MODULE, 0, &card);
    if (err < 0) {
        pr_err("snd_card_new failed: %d\n", err);
        return err;
    }

    /* 2. 创建播放PCM设备 */
    err = snd_pcm_new(card, "demo-playback", 0, 1, 0, &pcm_playback);
    if (err < 0) {
        pr_err("snd_pcm_new playback failed: %d\n", err);
        goto err_card;
    }
    snd_pcm_set_ops(pcm_playback, SNDRV_PCM_STREAM_PLAYBACK,
                    &playback_ops);
    strcpy(pcm_playback->name, "Demo PCM Playback");

    /* 3. 创建捕获PCM设备 */
    err = snd_pcm_new(card, "demo-capture", 1, 0, 1, &pcm_capture);
    if (err < 0) {
        pr_err("snd_pcm_new capture failed: %d\n", err);
        goto err_pcm_playback;
    }
    snd_pcm_set_ops(pcm_capture, SNDRV_PCM_STREAM_CAPTURE,
                    &capture_ops);
    strcpy(pcm_capture->name, "Demo PCM Capture");

    /* 4. 注册声卡 */
    err = snd_card_register(card);
    if (err < 0) {
        pr_err("snd_card_register failed: %d\n", err);
        goto err_pcm_capture;
    }

    pr_info("Demo PCM card registered\n");
    return 0;

err_pcm_capture:
    /* 释放函数:snd_pcm_free会清理PCM及其子设备 */
    snd_pcm_free(pcm_capture);
err_pcm_playback:
    snd_pcm_free(pcm_playback);
err_card:
    snd_card_free(card);
    return err;
}

static void __exit demo_pcm_exit(void)
{
    /* 卸载时先释放PCM设备,再释放声卡 */
    if (pcm_capture)
        snd_pcm_free(pcm_capture);
    if (pcm_playback)
        snd_pcm_free(pcm_playback);
    if (card)
        snd_card_free(card);
    pr_info("Demo PCM module unloaded\n");
}

module_init(demo_pcm_init);
module_exit(demo_pcm_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ALSA PCM device create/destroy demo");

编译与加载

# Makefile(与源码同目录)
obj-m := demo_pcm.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
# 编译并加载
make
sudo insmod demo_pcm.ko

# 查看设备节点
cat /proc/asound/cards
# 预期输出包含 "Demo PCM" 声卡条目

ls -l /dev/snd/
# 预期出现 pcmCxXx p 和 pcmCxXx c 设备节点
# 例如:pcmC1D0p(播放)、pcmC1D0c(捕获)

# 卸载模块
sudo rmmod demo_pcm

# 验证清理
cat /proc/asound/cards   # Demo PCM条目消失
ls -l /dev/snd/          # 对应设备节点被移除

验证要点

  • 加载成功:dmesg | tail 应显示 Demo PCM card registered,无错误信息。
  • 设备节点存在:/dev/snd/ 下出现播放(p后缀)和捕获(c后缀)两个PCM设备节点。
  • 卸载干净:rmmod 后,声卡条目和PCM设备节点全部消失,dmesg 无资源泄漏警告。

关键说明

  • snd_pcm_new 的第4、5个参数分别指定播放和捕获子流数量,本案例各创建1个。
  • 释放顺序必须为:先释放PCM设备(snd_pcm_free),再释放声卡(snd_card_free),否则可能导致悬空指针。
  • 操作集中的 ioctl 使用 snd_pcm_lib_ioctl 提供默认处理,其余回调置NULL仅用于演示创建/销毁流程;实际驱动需实现完整操作集。
  • 本案例使用 platform_device 作为父设备,实际驱动中应替换为具体总线设备。