Linux ALSA驱动框架(一)–ALSA架构简介

(1)ALSA简介
(1)
Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制
ALSA Library API:alsa 用户库接口,常见有 tinyalsa、alsa-libALSA CORE:alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC)ASoC CORE:asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec

(2)
alsa驱动框架核心层给我们干的活:创建声卡设备的控制接口和PCM设备
snd_soc_init()--->platform_driver_register(&soc_driver)--->soc_probe()--->snd_soc_register_card(card)--->注册自己的声卡设备 snd_soc_instantiate_card(card)--->
static int snd_soc_instantiate_card(struct snd_soc_card *card) { ret = snd_card_create();创建声卡设备-->snd_ctl_create()-->snd_ctl_dev_register创建声卡设备的控制接口函数-->snd_register_device()-->snd_register_device_for_dev() ret = soc_probe_link_dais(card, i, order); --->soc_new_pcm()创建一个新的PCM设备-->snd_pcm_new()--->_snd_pcm_new()--->snd_pcm_new_stream() ret = snd_card_register(card->snd_card);-->snd_pcm_dev_register()创建pcm设备文件-->snd_register_device_for_dev()创建pcm设备节点}
(3)
在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。
ls soundcore 该目录包含了ALSA驱动的中间层,它是整个ALSA驱动的核心部分core/oss 包含模拟旧的OSS架构的PCM和Mixer模块core/seq 有关音序器相关的代码include ALSA驱动的公共头文件目录,该目录的头文件需要导出给用户空间的应用程序使用,通常,驱动模块私有的头文件不应放置在这里drivers 放置一些与CPU、BUS架构无关的公用代码i2c ALSA自己的I2C控制代码pci pci声卡的顶层目录,子目录包含各种pci声卡的代码isa isa声卡的顶层目录,子目录包含各种isa声卡的代码soc 针对system-on-chip体系的中间层代码soc/codecs 针对soc体系的各种codec的代码,与平台无关

(4)
alsa驱动的设备文件结构: 字符设备 # ls /dev/snd/ -lhtotal 0crw-rw---- 1 root root 116, 0 Aug 21 16:01 controlC0crw-rw---- 1 root root 116, 24 Aug 21 16:01 pcmC0D0ccrw-rw---- 1 root root 116, 16 Aug 21 16:01 pcmC0D0pcrw-rw---- 1 root root 116, 25 Aug 21 16:01 pcmC0D1ccrw-rw---- 1 root root 116, 17 Aug 21 16:01 pcmC0D1pcrw-rw---- 1 root root 116, 26 Aug 21 16:01 pcmC0D2ccrw-rw---- 1 root root 116, 33 Aug 21 16:01 timercontrolC0 用于声卡的控制,例如通道选择,混音,麦克风的控制等pcmC0D0c 用于录音的pcm设备pcmC0D0p 用于播放的pcm设备timer 定时器C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。
(5)
sound/core/sound.cstatic int __init alsa_sound_init(void){ if (register_chrdev(major, "alsa", &snd_fops)) //注册alsa字符设备}static const struct file_operations snd_fops = { .owner = THIS_MODULE, .open = snd_open, .llseek = noop_llseek, }; static struct snd_minor *snd_minors[SNDRV_OS_MINORS];谁来设置snd_minors[]结构体数组,snd_register_device_for_dev函数里面有数组项snd_minors外,为了mdev或udev能自动创建设备节点,我们有class_create()函数创建类,下面创建设备device_create(),类在Sound_ core.c入口函数里面被创建,sound.c的入口函数里面注册字符设备函数,snd_register_device_for_dev()函数里面创建声卡逻辑设备时有device_create()
(6)
谁调用snd_register_device_for_dev()(两路调用)一路:声卡设备的控制接口另一路:声卡设备的数据接口声卡设备的控制接口 sound/core/control.csnd_card_create---> 创建一个snd_card结构体snd_ctl_create---> static int snd_ctl_dev_register(struct snd_device *device)--> //创建声卡设备的控制接口函数snd_register_device--->snd_register_device_for_dev()int snd_ctl_create(struct snd_card *card){ static struct snd_device_ops ops = { .dev_free = snd_ctl_dev_free, .dev_register = snd_ctl_dev_register, .dev_disconnect = snd_ctl_dev_disconnect, }; if (snd_BUG_ON(!card)) return -ENXIO; return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);}创建声卡设备的数据接口(声卡驱动) snd_pcm_new----> 创建一个新的PCM设备 _snd_pcm_new---> 创建播放流和录音流static int snd_pcm_dev_register(struct snd_device *device) //创建声卡设备的数据接口总结: (1)构造snd_card结构体,snd_card_create()构造snd_card结构体并自动创建控制接口。调用函数snd_ctrl_create (2)初始化;如snd_pcm_new(),创建逻辑设备(播放设备或录音设备) (3)注册 snd_card_register

(2)声卡的创建

(1)
struct snd_card是什么
snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card结构体。struct snd_card {struct list_head devices 记录该声卡下所有逻辑设备的链表struct list_head controls 记录该声卡下所有的控制单元的链表void *private_data 声卡的私有数据,可以在创建声卡时通过参数指定数据的大小}
snd_soc_init{ return platform_driver_register(&soc_driver);}static int soc_probe(struct platform_device *pdev) { return snd_soc_register_card(card); }int snd_soc_register_card(struct snd_soc_card *card){ ret = snd_soc_instantiate_card(card); }static int snd_soc_instantiate_card(struct snd_soc_card *card) { ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card); -->snd_ctl_create() ret = soc_probe_link_dais(card, i, order); --->soc_new_pcm()-->snd_pcm_new() ret = snd_card_register(card->snd_card);}snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);创建snd_card的一个实例index:一个整数值,该声卡的编号 id:字符串,声卡的标识符 第四个参数:该参数决定在创建snd_card实例时,需要同时额外分配的私有数据的大小,该数据的指针最终会赋值给snd_card的private_data数据成员card:返回所创建的snd_card实例的指针设置Driver的ID和名字static int __init alsa_sound_init(void) subsys_initcall(alsa_sound_init);snd_info_init()-->snd_card_info_init()-->snd_card_info_read()-->{snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",idx,card->id,card->driver,card->shortname);snd_iprintf(buffer, "%s\n",card->longname);}创建声卡的功能部件(逻辑设备),例如PCM,Mixer,MIDI等这时候可以创建声卡的各种功能部件了,还记得开头的snd_card结构体的devices字段吗?每一种部件的创建最终会调用snd_device_new()来生成一个snd_device实例,并把该实例链接到snd_card的devices链表中。通常,alsa-driver的已经提供了一些常用的部件的创建函数,而不必直接调用snd_device_new(),比如: PCM ---- snd_pcm_new() RAWMIDI -- snd_rawmidi_new() CONTROL -- snd_ctl_create() TIMER -- snd_timer_new() INFO -- snd_card_proc_new() JACK -- snd_jack_new()
注册声卡 static int snd_soc_instantiate_card(struct snd_soc_card *card) { ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card); ret = soc_probe_link_dais(card, i, order); --->soc_new_pcm()-->snd_pcm_new() ret = snd_card_register(card->snd_card);
}比如我们的例子:sound/soc/ingenic/asoc-board/phoenix_icdc.c static int snd_phoenix_probe(struct platform_device *pdev){ret = snd_soc_register_card(&phoenix);}



(2)
int snd_card_create(int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)
{ if (extra_size < 0) extra_size = 0; card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);//根据extra_size参数的大小分配内存,该内存区可以作为芯片的专有数据使用
if (xid) strlcpy(card->id, xid, sizeof(card->id));

if (idx < 0) { for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++) /* idx == -1 == 0xffff means: take any free slot */ if (~snd_cards_lock & idx & 1< if (module_slot_match(module, idx2)) { idx = idx2; break; } } } if (idx < 0) { for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++) /* idx == -1 == 0xffff means: take any free slot */ if (~snd_cards_lock & idx & 1< if (!slots[idx2] || !*slots[idx2]) { idx = idx2; break; } } }//拷贝声卡的ID字符串
card->number = idx; card->module = module; INIT_LIST_HEAD(&card->devices); init_rwsem(&card->controls_rwsem); rwlock_init(&card->ctl_files_rwlock); INIT_LIST_HEAD(&card->controls); INIT_LIST_HEAD(&card->ctl_files); spin_lock_init(&card->files_lock); INIT_LIST_HEAD(&card->files_list); init_waitqueue_head(&card->shutdown_sleep); atomic_set(&card->refcount, 0);#ifdef CONFIG_PM mutex_init(&card->power_lock); init_waitqueue_head(&card->power_sleep);#endif//初始化snd_card结构中必要的字段
err = snd_ctl_create(card); if (err < 0) { snd_printk(KERN_ERR "unable to register control minors\n"); goto __error; }//建立逻辑设备:Control
err = snd_info_card_create(card); if (err < 0) { snd_printk(KERN_ERR "unable to create card info\n"); goto __error_ctl; }//建立proc文件中的info节点:通常就是/proc/asound/card0
if (extra_size > 0) card->private_data = (char *)card + sizeof(struct snd_card); *card_ret = card; return 0;//分配的内存指针放入private_data字段中
}
int snd_card_register(struct snd_card *card){ if (!card->card_dev) { card->card_dev = device_create(sound_class, card->dev, MKDEV(0, 0), card, "card%i", card->number); if (IS_ERR(card->card_dev)) card->card_dev = NULL; }//创建sysfs下的设备
if ((err = snd_device_register_all(card)) < 0) return err;//通过snd_device_register_all()注册所有挂在该声卡下的逻辑设备,snd_device_register_all()实际上是通过snd_card的devices链表,遍历所有的snd_device,并且调用snd_device的ops->dev_register()来实现各自设备的注册的。
最后代码就是建立一些相应的proc和sysfs下的文件或属性节点
}
static int __init init_soundcore(void) subsys_initcall(init_soundcore){ int rc;
rc = init_oss_soundcore(); if (rc) return rc;
sound_class = class_create(THIS_MODULE, "sound"); if (IS_ERR(sound_class)) { cleanup_oss_soundcore(); return PTR_ERR(sound_class); }
sound_class->devnode = sound_devnode;
return 0;}
static char *sound_devnode(struct device *dev, umode_t *mode){ if (MAJOR(dev->devt) == SOUND_MAJOR) return NULL; return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));}//声卡的class将会出现在文件系统的/sys/class/sound/下面,并且,sound_devnode()也决定了相应的设备节点也将会出现在/dev/snd/下面。
# ls /dev/snd/ -lhtotal 0crw-rw---- 1 root root 116, 0 Aug 21 12:00 controlC0crw-rw---- 1 root root 116, 24 Aug 21 12:00 pcmC0D0ccrw-rw---- 1 root root 116, 16 Aug 21 12:00 pcmC0D0pcrw-rw---- 1 root root 116, 25 Aug 21 12:00 pcmC0D1ccrw-rw---- 1 root root 116, 17 Aug 21 12:00 pcmC0D1pcrw-rw---- 1 root root 116, 26 Aug 21 12:00 pcmC0D2ccrw-rw---- 1 root root 116, 33 Aug 21 12:00 timer

ls /sys/class/sound/ -lhtotal 0lrwxrwxrwx 1 root root 0 Aug 21 12:00 card0 -> ../../devices/platform/ingenic-alsa.0/sound/card0lrwxrwxrwx 1 root root 0 Aug 21 12:00 controlC0 -> ../../devices/platform/ingenic-alsa.0/sound/card0/controlC0lrwxrwxrwx 1 root root 0 Aug 21 12:00 pcmC0D0c -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D0clrwxrwxrwx 1 root root 0 Aug 21 12:00 pcmC0D0p -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D0plrwxrwxrwx 1 root root 0 Aug 21 12:00 pcmC0D1c -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D1clrwxrwxrwx 1 root root 0 Aug 21 12:00 pcmC0D1p -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D1plrwxrwxrwx 1 root root 0 Aug 21 12:00 pcmC0D2c -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D2clrwxrwxrwx 1 root root 0 Aug 21 12:00 timer -> ../../devices/virtual/sound/timer