/*****************************************************************************************************************/
声明: 本博客内容均由https://blog.csdn.net/weixin_47702410原创,转载or引用请注明出处,谢谢!
创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢
/*****************************************************************************************************************/
本文讲一下从HAL层的TinyALSA调到ASOC的platform、codec、machine的完整流程,也就是如何从Tinyalsa调到驱动程序中。
学习本文需要提前掌握ASOC架构、声卡注册过程、ALSA结构体的关系等知识,基础内容不逐个介绍(不然讲不完,后续再出相关教程),就以Tinyalsa中的pcm_open为例讲解。参考的安卓版本为Android 12_r8官方源码,Linux版本为Linux Kernel 5.10官方源码
当我们open的设备类似为非ALSA plug的时候,实际上调用的open函数应该是pcm_hw_open函数。
// file path : external/tinyalsa/pcm.c
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config)
{
//...
pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
//...
}
//open非ALSA plug时,pcm->ops->open函数就是pcm_hw_open函数
//file path : external/tinyalsa/pcm_hw.c
static int pcm_hw_open(unsigned int card, unsigned int device,
unsigned int flags, void **data,
__attribute__((unused)) void *node)
{
//...
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
flags & PCM_IN ? 'c' : 'p');
fd = open(fn, O_RDWR|O_NONBLOCK);
//...
}
在Linux设备中,音频节点的主设备号都是116,可以用CMD查看这个音频节点"/dev/snd/pcmC%uD%u%c",例如:
# ls -l /dev/snd/pcmC0D0p
crw-rw---- 1 system audio 116, 2 20xx-xx-xx xx:xx /dev/snd/pcmC0D0p
可以见到主设备号是116,次设备号是2
由此需要找Kernel中主设备号为116的设备,看看这个设备的ops是怎么样操作的
在Linux Kernel 5.10中,主设备号为116的设备是如下注册的:
//file path : sound\core\sound.c
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {
/*
这个主设备号major的定义是:
static int major = CONFIG_SND_MAJOR;
这个宏定义在下面:
#define CONFIG_SND_MAJOR 116 // standard configuration
*/
pr_err("ALSA core: unable to register native major device number %d\n", major);
return -EIO;
}
if (snd_info_init() < 0) {
unregister_chrdev(major, "alsa");
return -ENOMEM;
}
#ifndef MODULE
pr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endif
return 0;
}
那么对应的open就是:
//file path :sound\core\sound.c
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
注意这个file_operations有open函数,但没有close、write、read函数的实现,这个是为什么?
因为后续会在snd_open函数中根据次设备号来替换这个原先的file_operations。
替换file_operations的过程如下:
//file path :sound\core\sound.c
static int snd_open(struct inode *inode, struct file *file)
{
// 获取设备的次设备号
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *new_fops;
int err = 0;
// 检查次设备号是否在有效范围内
if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
// 加锁,防止并发操作
mutex_lock(&sound_mutex);
// 获取对应次设备号的设备信息
mptr = snd_minors[minor];
// 如果设备信息为空,则尝试自动加载设备
if (mptr == NULL) {
mptr = autoload_device(minor);
if (!mptr) {
// 如果自动加载失败,则解锁并返回错误
mutex_unlock(&sound_mutex);
return -ENODEV;
}
}
// 获取设备的文件操作函数集
new_fops = fops_get(mptr->f_ops);
// 解锁
mutex_unlock(&sound_mutex);
// 如果获取文件操作函数集失败,则返回错误
if (!new_fops)
return -ENODEV;
// 替换file结构体中的文件操作函数集
replace_fops(file, new_fops);
// 如果设备的打开函数存在,则调用该函数打开设备
if (file->f_op->open)
err = file->f_op->open(inode, file);
// 返回结果
return err;
}
总结:声卡设备的open函数有统一的入口–snd_open函数,任何HW音频函数均是通过这个函数作为跳板访问硬件。
但是问题来了,snd_open替换完file_operations后,紧跟着
file->f_op->open(inode, file);这个就是调用次设备的open,那么次设备的open函数在哪里呢?
先说结论:在写声卡驱动的时候,会调用snd_soc_register_card函数去注册一个声卡,snd_soc_register_card函数会填充snd_minors[minor]数组,最终主设备号在这个数组中取出相应次设备号的file_operations就完成了主设备号ops向次设备的ops转变。次设备号的ops是如下的内容,类似于主设备也是统一入口(这节的主要内容就是这个结论,需要熟悉声卡注册的流程,不懂或者不理解可以记住结论):
// file path: /sound/core/pcm_native.c
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.write_iter = snd_pcm_writev,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_poll,
.unlocked_ioctl = snd_pcm_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.read_iter = snd_pcm_readv,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_poll,
.unlocked_ioctl = snd_pcm_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
本节内容会先分析次设备的file_operations为何是snd_pcm_f_ops。
然后举例当执行open设备的时候,分析从snd_pcm_f_ops中的snd_pcm_playback_open函数如何进一步调度到底层的驱动函数。
这个要说一下声卡注册的流程了(后续会出一个系列讲声卡是如何加载上去的)。
首先声卡注册的函数是snd_soc_register_card函数,这个的函数的内容比较丰富,但对于本文需要掌握如下的调用流程:
snd_soc_register_card
--snd_soc_bind_card
--soc_init_pcm_runtime
--soc_new_pcm
--snd_pcm_new
--_snd_pcm_new
--.dev_register =snd_pcm_dev_register,// 定义设备操作函数集
--snd_card_register
--snd_device_register_all
--__snd_device_register
--dev->ops->dev_register(dev);//创建dev_ops,调用的是snd_pcm_dev_register
综上,在声卡注册的时候,次设备的ops由snd_pcm_dev_register函数去创建,分析这个函数的内容:
//file path : /sound/core/pcm.c
static int snd_pcm_dev_register(struct snd_device *device)
{
//...
// 遍历PCM设备的所有流
for (cidx = 0; cidx < 2; cidx++) {
int devtype = -1;
if (pcm->streams[cidx].substream == NULL)
continue;
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
/* register pcm */
err = snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm,
&pcm->streams[cidx].dev);
/* 注意这个参数!snd_pcm_f_ops[cidx]! */
if (err < 0) {
list_del_init(&pcm->list);
goto unlock;
}
// 这里是初始化PCM设备的所有流的定时器
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
snd_pcm_timer_init(substream);
}
//...
}
//这个传入的参数为:
//file path :/sound/core/pcm_native.c
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.write_iter = snd_pcm_writev,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_poll,
.unlocked_ioctl = snd_pcm_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.read_iter = snd_pcm_readv,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_poll,
.unlocked_ioctl = snd_pcm_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
上面有两个ops,一个是播放使用的,一个是录音使用的。
本文就以录音为例继续分析。录音调用的open函数是snd_pcm_playback_open函数。
snd_pcm_playback_open的调用流程如下:
snd_pcm_playback_open
--snd_pcm_open
--snd_pcm_open_file
--snd_pcm_open_substream
--substream->ops->open(substream)
分析到这个语句:substream->ops->open(substream),substream->ops是在注册声卡的时候被指定的,声卡注册的函数是snd_soc_register_card函数,对应流程如下:
snd_soc_register_card
--snd_soc_bind_card
--soc_init_pcm_runtime
--soc_new_pcm:主要内容在这里,分析这个函数
//file path:/sound/soc/soc-pcm.c
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
//...
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {
// 使用了DAPM架构,用下面的ops
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_params = dpcm_fe_dai_hw_params;
rtd->ops.prepare = dpcm_fe_dai_prepare;
rtd->ops.trigger = dpcm_fe_dai_trigger;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_fe_dai_close;
rtd->ops.pointer = soc_pcm_pointer;
} else {
//不使用DAPM架构,用下面的ops
rtd->ops.open = soc_pcm_open;
rtd->ops.hw_params = soc_pcm_hw_params;
rtd->ops.prepare = soc_pcm_prepare;
rtd->ops.trigger = soc_pcm_trigger;
rtd->ops.hw_free = soc_pcm_hw_free;
rtd->ops.close = soc_pcm_close;
rtd->ops.pointer = soc_pcm_pointer;
}
//...
if (playback)
//如果设备支持播放,设置substream ops为上述的ops
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
//如果设备支持录音,设置substream ops为上述的ops
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
//...
}
//snd_pcm_set_ops的实现
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
const struct snd_pcm_ops *ops)
{
struct snd_pcm_str *stream = &pcm->streams[direction];
struct snd_pcm_substream *substream;
for (substream = stream->substream; substream != NULL; substream = substream->next)
//这里指定了substream->ops = ops
substream->ops = ops;
}
综上可以知道:
如果使用了DAPM架构,则substream->ops->open就是dpcm_fe_dai_open,
如果不使用DAPM架构,则substream->ops->open就是soc_pcm_open。
可以关注一下,后续将会推出DAPM的详解,本文不讲述DAPM的相关知识。
以不使用DAPM架构为例:
substream->ops->open就是soc_pcm_open函数,对它进行分析:
soc_pcm_open
--soc_pcm_components_open
--snd_soc_component_open
--component->driver->open 遍历component,如果有open调用open, 一般指的是CPU端的open函数
--snd_soc_link_startup
--rtd->dai_link->ops->startup 调用DAI_LINK的startup,位于ASOC的machine端
--snd_soc_dai_startup
--dai->driver->ops->startup 调用codec端的startup函数
总结,soc_pcm_open会依次调用了CPU端的open函数、machine端的startup函数、codec端的startup函数。这三个函数均是驱动定义的。