Alsa 驱动分析
Guide
Revision History
Date |
Issue |
Description |
Author |
< 31/12 /200 8 > |
<0.5> |
First draft |
Wylhistory |
|
|
|
|
目录
1. Abstract
2. Introduction
3. 音频驱动框架介绍
3.1 音频设备的注册
3.2 音频驱动的注册
3.2.1 Probe 函数的调用
3.2.2 Soc_probe 函数
4. 通常的使用流程的分析
4.1.1 open 过程介绍
4.1.2 snd_pcm_hw_params 流程分析
4.1.3 prepare流程分析
4.1.4 write 的流程
4.1.5 使用流程的总结 t
5. Amixer 调用的相关逻辑
5.1.1 Amixer 调用的上层逻辑
5.1.2 Amixer 的内核流程
6. 总结
7. 未讨论
主要是讲 2.6.21 内核里面的 alsa 驱动的架构,以及在我们的平台上需要注意的东西。 .
分成几个部分 :
驱动整体框架,一个简单的播放流程介绍,以及我们的平台需要注意的地方;
这就是设备的注册了,设备本身非常简单,复杂的是这个设备的 drvdata , drvdata 里面包含了三部分,关于 machine 的,关于 platform 的,关于 codec 的,从大体上说 machine 主要是关于 cpu 这边的也可以说是关于 ssp 本身设置的,而 platform 是关于平台级别的,就是说这个平台本身实现相关的,而 codec 就是与我们所用的音频 codec 相关的;基本上这里就可以看出整个音频驱动的架构特点,就是从 alsa 层进入—— > 内核 alsa 层接口 ->core 层,这里再调用上面说的三个方面的函数来处理,先是 cpu 级别的,再是 platform 的,再是 codec 级别的,这几层做完了,工作也就做得差不多了,后面会详细讲讲,当然这个执行顺序不是固定的 ( 不知道是不是 marvel 写代码不专业导致的 ) ,但多半都包括了这三部分的工作 ;
前面讲了设备的注册,里面的设备的名字就是 ”soc-audio”, 而这里的 driver 的注册时名字也是 ” soc-audio” ,对于 platform 的设备匹配的原则是根据名字的,所以将会匹配成功,成功后就会执行 audio 驱动提供的 probe 函数 soc_probe;
这个函数本身架构很简单,和前面说的逻辑一样,先调用了 cpu 级别的 probe ,再是 codec 级别的,最后是 platform 的(这里三个的顺序就不一样),但是因为 cpu 级别的和 platform 级别的都为空,最后都调用了 codec 级别的 probe 函数,也就是 micco_soc_probe ,这个函数基本上就完成了所有应该完成的音频驱动的初始化了;简单的划分,分成两部分,对上和对下:对上主要是注册设备节点,以及这些设备节点对应的流的创建;对下主要是读写函数的设置, codec 本身的 dai 设置,初始化寄存器的设置,最重要的就是后面的 control 的创建和门的创建了,如下图所示:
第二部分就是创建卡和流,对于 alsa 驱动来说,是先分成卡 0 ,卡 1…, 然后对于每一个卡的每一个 cpu 支持的 dai ( digit audio interface )也就是 pcm 接口 或者 i2S 接口等都要建立对应的流,一个 dai 有可能包含两个流,一个是录的一个是 play 的,但在我们的平台上对于 i2S 的 dai 是没有录音功能的,所以我们的平台只有一个卡,三个流, pcm 的录和 play , i2S 的 play ;流的创建还是更多的考虑为上层服务的,它所提供的接口都是 soc 层的,这里非常重要的地方在于驱动的一个典型做法那就是如何把关键的内核数据结构和 export 到外部的 /dev 下的设备节点实现关联,比如 :
关键数据结构 struct snd_pcm ,是根据 cpu 所固有的 dai 创建的,而对于每一个 struct snd_pcm 又可 能用 到两个 substream (它们实现具体的流的播放等), 它们之间的链接是通过它的内部数据成员 struct snd_pcm_str streams[2]; 来连接的,而这个 snd_pcm 类型的指针是 在函数 snd_device_new 里面 通过 device_data 放到设备里面的,这个设备会在 snd_device_register_all
的时候注册到 /dev 下面,并且调用 dev_set_drvdata(preg->dev, private_data); 来把这个指针放到设备的私有数据里面;而在需要使用的时候通过 snd_pcm_playback_open 里面的 snd_lookup_minor_data 函数取得其私有数据并返回的,这样就实现了设备节点和对应的驱动的数据结构的关联,这是一种非常普遍的做法;有了这个数据结构它就可以根据一定的原则取得对应于这个需求的 substream ,于是一切的操作都可以交给这个 substream 了 ;
第三部分就是 control 的创建,这个函数比较简单,就是把表 micco_snd_controls 里面已经定义好的 controls 模板创建 controls ,然后加入到 card 的 controls 列表中去;本身功能很清晰,但是对于我们平台来说,需要非常小心,因为这里决定了各个 controls 的序号,而这个序号是 audio_controller 访问硬件的索引,所以千万要小心尽量要维持目前的 controls 的序号,如果要额外添加新的 controls 一定要记得要放在 micco_add_widgets 后面来做,这样可以做到兼容,否则 audio_controller 的工作量就大了 !
第四部分就是门的创建了,这个函数也是很清楚,就是把 codec 对应的门都加入到 codec->dapm_widgets 列表中去(这里的门的概念可以简单的理解为水管与水管之间的连接的地方,声音数据像水一样从水管里面流出来,源头可以是 CPU 了,也可以是 modem ,然后通过不同的门,流向不同的地方,比如 speaker ,比如蓝牙耳机等等),然后根据 micco_audio_map 把所有可能连在一起的门连接起来,这个表 micco_audio_map 的意思是 { 目的名字,控制点名字,源头名字 } ,然后函数 snd_soc_dapm_connect_input 会根据这些名字去查表 codec->dapm_widgets ( 先前已经把所有的门都加入了 ) 找到它们再根据不同的类型做不同的连接,比如是 mux 之间的连接, mux 和 pga 之间的连接等等,注意这里的连接其实只不过是说找到连接的可能性,它对于不同的门,找到其可能的 source 和 sink ,加入到对应的列表中去,具体细节如下:
首先,扫描整个 codec 所拥有的所有的门,如果它的名字和传入的 sink 的名字相同,则认为它就是这个路径的 sink ,如果它的名字和传入的 source 名字相同,则认为它是这个路径的 source ,如果源头或者 sink 没有找到都返回错误;然后分配一个 struct snd_soc_dapm_path ,这个数据结构的主要成分包括名字, source 门, sink 门,这条路径的 control ,这个源头和 sink 是否已经连接,是否已经走过(用在后面),这个数据结构会被挂在三个链表里面,一个是 source 的就是这个门会在很多的路径中,把它在这个路径中做 source 的 path 都连在一起,一个是 sink 的就是把这个门在所有这些由它做 sink 的 path 都连接在一起,一个是把所有的路径都需要连接在一起的这个是通过 codec 的 dapm_paths 来访问的;
list_add(&path->list, &codec->dapm_paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
需要注意的时候,这里把路径的 list_sink 加入到了 wsink 门的 sources 列表里面,而把路径的 list_source 加入到 wsource 的 sinks 列表里面,所以当访问的时候从 wsink 门的 sources 出发就可以找到连接这个门作为 sink 的所有的路径,而从 wsource 的 sinks 列表出发就可以找到所有以这个门作为 source 的路径;
第三步就是为这个数据结构赋值: source , sink ,初始化三个链表;第四步:如果 control 为空则把这个路径加入到相应的三个链表中去,并且路径设为已经连接,并返回;第五步:否则,根据 sink 的类型,如果是 adc , dac , input , output , micbias , vmid , pre , post ,则把路径加入到三个链表,设置已经连接的标志;如果是 snd_soc_dapm_mux 则调用 dapm_connect_mux 来处理;如果是 mixer 和 switch 则调用 dapm_connect_mixer 来处理,如果是 hp , mic , line , spk ,则把 path 加入到三个链表中去,但是设置成为连接的状态 。
大约就是这样的了。
也许您要问,为什么要这么做呢?
这个,我也有想过,甚至我认为在门比较少的时候,确实没什么必要,但是这么做的好处在于当要播放音乐的时候,它可以实现自动的寻找路径并且自动 poweron 那些门,不需要上层做任何的控制,因为它真的到达目的地的所有的路径,这样它可以自己选择走哪条路;如果不这么连接起来的话,就需要提供给上层连接的接口,完全由上层来决定该连接哪些门,也必须由上层来负责 poweron 和 off 这些门;
第五部分就是注册了,这里就是向 /dev 注册设备节点,因为这些设备节点会由 alsa 层来访问的,这些设备节点和驱动的连接我前面已经说了,主要是提供了对上的 alsa 接口,给 alsa 层调用。
待续