3. PCM硬件参数配置

任务说明

实现ALSA驱动的hw_params回调函数,完成PCM硬件参数(采样率、通道数、采样格式)的配置与合法性检查。当用户空间通过snd_pcm_hw_params()发起参数设置时,内核驱动需要验证参数是否被硬件支持,并据此配置DMA传输格式、通道映射等底层细节。

核心实现

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

/* 硬件能力定义:采样率、通道数、格式 */
#define DRV_RATES     SNDRV_PCM_RATE_8000_48000
#define DRV_FORMATS   (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
#define DRV_CHANNELS_MIN  1
#define DRV_CHANNELS_MAX  2

struct drv_private_data {
    struct snd_soc_component *component;
    unsigned int rate;      /* 当前采样率 */
    unsigned int channels;  /* 当前通道数 */
    snd_pcm_format_t format; /* 当前格式 */
};

static int drv_hw_params(struct snd_pcm_substream *substream,
                         struct snd_pcm_hw_params *params)
{
    struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
    struct drv_private_data *priv = snd_soc_component_get_drvdata(rtd->component);
    unsigned int rate = params_rate(params);
    unsigned int channels = params_channels(params);
    snd_pcm_format_t format = params_format(params);

    /* 参数合法性检查 */
    if (rate < 8000 || rate > 48000) {
        dev_err(rtd->dev, "Unsupported rate: %u\n", rate);
        return -EINVAL;
    }
    if (channels < DRV_CHANNELS_MIN || channels > DRV_CHANNELS_MAX) {
        dev_err(rtd->dev, "Unsupported channels: %u\n", channels);
        return -EINVAL;
    }
    if (!(DRV_FORMATS & (1ULL << format))) {
        dev_err(rtd->dev, "Unsupported format: %d\n", format);
        return -EINVAL;
    }

    /* 保存参数到私有数据,供prepare/trigger等回调使用 */
    priv->rate = rate;
    priv->channels = channels;
    priv->format = format;

    /* 实际硬件配置(示例:配置DMA传输宽度) */
    switch (format) {
    case SNDRV_PCM_FORMAT_S16_LE:
        /* 16位传输,DMA总线宽度设为2字节 */
        break;
    case SNDRV_PCM_FORMAT_S24_LE:
        /* 24位传输,DMA总线宽度设为4字节(左对齐) */
        break;
    default:
        return -EINVAL;
    }

    dev_dbg(rtd->dev, "hw_params: rate=%u ch=%u fmt=%d\n",
            rate, channels, format);
    return 0;
}

static const struct snd_soc_component_driver drv_component_driver = {
    .name       = "drv-codec",
    .hw_params  = drv_hw_params,
};

static int drv_probe(struct platform_device *pdev)
{
    struct drv_private_data *priv;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    platform_set_drvdata(pdev, priv);
    return devm_snd_soc_register_component(&pdev->dev,
                        &drv_component_driver, NULL, 0);
}

static struct platform_driver drv_driver = {
    .driver = {
        .name = "drv-pcm",
    },
    .probe = drv_probe,
};
module_platform_driver(drv_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ALSA hw_params callback example");

关键点说明

  • 参数来源:params_rate()、params_channels()、params_format()从ALSA内核的hw_params结构体中提取用户空间请求的参数值。
  • 合法性检查:驱动必须拒绝硬件不支持的参数组合,返回-EINVAL。ALSA框架会将此错误传递给用户空间的snd_pcm_hw_params()调用。
  • 能力声明:DRV_RATES、DRV_FORMATS等宏需与snd_pcm_hw_constraint_*配合使用,在open回调中声明硬件能力范围,确保用户空间只能请求合法参数。

编译与验证

# 编译(内核源码树内)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- M=$(pwd) modules

# 加载模块
insmod drv-pcm.ko

# 用户空间验证(使用alsa-utils)
arecord -D hw:0,0 -f S16_LE -r 44100 -c 2 -d 1 /dev/null
# 预期:命令成功执行,无错误输出

# 非法参数测试(应返回错误)
arecord -D hw:0,0 -f S32_LE -r 44100 -c 2 -d 1 /dev/null
# 预期:返回"无法设置采样格式"错误

通过判定标准:合法参数(如16位/44.1kHz/双声道)能正常录音;非法格式(如32位)被拒绝并返回EINVAL;内核日志中可见hw_params: rate=44100 ch=2 fmt=2的调试输出。