2. 声卡驱动模块参数

任务目标

为声卡驱动添加标准ALSA模块参数index和id,使驱动在加载时能够动态指定声卡的编号(card index)和名称(card id),替代编译期硬编码。

核心实现

以下代码实现了一个最小ALSA声卡驱动模板,注册声卡时读取模块参数。完整代码可直接编译为内核模块。

// sound_card_params.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/initval.h>

/* ---- 模块参数 ---- */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;  // 默认{-1, -1, ...}
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;   // 默认{NULL, NULL, ...}

module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Card index (array, up to " __stringify(SNDRV_CARDS) ")");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "Card ID string (array, up to " __stringify(SNDRV_CARDS) ")");

/* ---- 平台驱动 ---- */
static int mycard_probe(struct platform_device *pdev)
{
    struct snd_card *card;
    int dev = pdev->id;  // 设备实例号,对应参数数组下标
    int err;

    /* 使用模块参数创建声卡:index[dev]为声卡编号,id[dev]为声卡名称 */
    err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, 0, &card);
    if (err < 0) {
        dev_err(&pdev->dev, "snd_card_new failed: %d\n", err);
        return err;
    }

    /* 此处可添加PCM/control等组件,本案例仅演示参数传递 */

    strcpy(card->driver, "mycard");
    strcpy(card->shortname, "My ALSA Card");
    strcpy(card->longname, "My ALSA Card (parameter demo)");

    err = snd_card_register(card);
    if (err < 0) {
        snd_card_free(card);
        return err;
    }

    platform_set_drvdata(pdev, card);
    return 0;
}

static int mycard_remove(struct platform_device *pdev)
{
    struct snd_card *card = platform_get_drvdata(pdev);
    if (card)
        snd_card_free(card);
    return 0;
}

static struct platform_driver mycard_driver = {
    .driver = {
        .name = "mycard",
    },
    .probe  = mycard_probe,
    .remove = mycard_remove,
};

/* ---- 平台设备(模拟硬件存在) ---- */
static struct platform_device *mycard_device;

static int __init mycard_init(void)
{
    int err;

    /* 注册平台驱动 */
    err = platform_driver_register(&mycard_driver);
    if (err < 0)
        return err;

    /* 注册一个设备实例,id=0对应参数数组第0个元素 */
    mycard_device = platform_device_register_simple("mycard", 0, NULL, 0);
    if (IS_ERR(mycard_device)) {
        platform_driver_unregister(&mycard_driver);
        return PTR_ERR(mycard_device);
    }

    return 0;
}

static void __exit mycard_exit(void)
{
    platform_device_unregister(mycard_device);
    platform_driver_unregister(&mycard_driver);
}

module_init(mycard_init);
module_exit(mycard_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("ALSA sound card with index/id module parameters");

编译与加载

# Makefile(与上述源码同目录)
obj-m += sound_card_params.o

# 编译(需内核头文件)
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载:指定声卡编号为2,名称为"mycard0"
sudo insmod sound_card_params.ko index=2 id="mycard0"

# 查看结果
cat /proc/asound/cards
# 预期输出中应包含:
#   2 [mycard0        ]: mycard - My ALSA Card
#                        My ALSA Card (parameter demo)

验证方法

  1. 参数生效验证:加载后执行cat /proc/asound/cards,确认第2行行首编号为2,方括号内为mycard0,与传入参数一致。
  2. 默认值验证:不传参数直接insmod sound_card_params.ko,系统自动分配第一个空闲编号(通常为0),id为空时内核自动生成。
  3. 参数读取验证:cat /sys/module/sound_card_params/parameters/index应显示2,-1,-1,...(数组长度由SNDRV_CARDS决定,通常为8或32)。

关键说明

  • SNDRV_DEFAULT_IDX展开为{[0 ... (SNDRV_CARDS-1)] = -1},-1表示由内核自动分配编号;SNDRV_DEFAULT_STR为全NULL。
  • 模块参数数组下标对应platform_device的id字段。本案例仅注册1个设备(id=0),如需多声卡可注册多个设备并分别传参。
  • 若指定编号已被占用,snd_card_new返回-EBUSY,驱动加载失败,可通过dmesg查看错误日志。