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的调试输出。