alsa驅(qū)動分析(2.6.21內(nèi)核)之一
Alsa驅(qū)動分析
Guide
RevisionHistory
Date
Issue
Description
Author
First draft
Wylhistory
?
?
?
?
目錄
1.??? Abstract.. 3
2.??? Introduction.. 3
3.??? 音頻驅(qū)動框架介紹.... 3
3.1????? 音頻設(shè)備的注冊... 3
3.2???? 音頻驅(qū)動的注冊... 4
3.2.1?????? Probe函數(shù)的調(diào)用... 4
3.2.2?????? Soc_probe函數(shù)... 4
4.??? 通常的使用流程的分析.... 6
4.1.1?????? open過程介紹... 6
4.1.2?????? snd_pcm_hw_params流程分析... 8
4.1.3?????? prepare流程分析... 9
4.1.4?????? write的流程... 15
4.1.5?????? 使用流程的總結(jié)t18
5.??? Amixer調(diào)用的相關(guān)邏輯.... 18
5.1.1?????? Amixer調(diào)用的上層邏輯... 19
5.1.2?????? Amixer的內(nèi)核流程... 20
6.??? 總結(jié).... 21
7.??? 未討論.... 21
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
1.??????????????Abstract
主要是講2.6.21內(nèi)核里面的alsa驅(qū)動的架構(gòu),以及在我們的平臺上需要注意的東西。.
2.??????????????Introduction
分成幾個部分:
驅(qū)動整體框架,一個簡單的播放流程介紹,以及我們的平臺需要注意的地方;
3.??????????????音頻驅(qū)動框架介紹 3.1??????????????音頻設(shè)備的注冊
這就是設(shè)備的注冊了,設(shè)備本身非常簡單,復(fù)雜的是這個設(shè)備的drvdata,drvdata里面包含了三部分,關(guān)于machine的,關(guān)于platform的,關(guān)于codec的,從大體上說machine主要是關(guān)于cpu這邊的也可以說是關(guān)于ssp本身設(shè)置的,而platform是關(guān)于平臺級別的,就是說這個平臺本身實現(xiàn)相關(guān)的,而codec就是與我們所用的音頻codec相關(guān)的;基本上這里就可以看出整個音頻驅(qū)動的架構(gòu)特點,就是從alsa層進入——>內(nèi)核alsa層接口->core層,這里再調(diào)用上面說的三個方面的函數(shù)來處理,先是cpu級別的,再是platform的,再是codec級別的,這幾層做完了,工作也就做得差不多了,后面會詳細(xì)講講,當(dāng)然這個執(zhí)行順序不是固定的(不知道是不是marvel寫代碼不專業(yè)導(dǎo)致的),但多半都包括了這三部分的工作;
3.2???????????音頻驅(qū)動的注冊 3.2.1?????????Probe函數(shù)的調(diào)用??????
???????? ????????
?????????????????? 前面講了設(shè)備的注冊,里面的設(shè)備的名字就是”soc-audio”,而這里的driver的注冊時名字也是” soc-audio”,對于platform的設(shè)備匹配的原則是根據(jù)名字的,所以將會匹配成功,成功后就會執(zhí)行audio驅(qū)動提供的probe函數(shù)soc_probe;
3.2.2?????????Soc_probe函數(shù)
這個函數(shù)本身架構(gòu)很簡單,和前面說的邏輯一樣,先調(diào)用了cpu級別的probe,再是codec級別的,最后是platform的(這里三個的順序就不一樣),但是因為cpu級別的和platform級別的都為空,最后都調(diào)用了codec級別的probe函數(shù),也就是micco_soc_probe,這個函數(shù)基本上就完成了所有應(yīng)該完成的音頻驅(qū)動的初始化了;簡單的劃分,分成兩部分,對上和對下:對上主要是注冊設(shè)備節(jié)點,以及這些設(shè)備節(jié)點對應(yīng)的流的創(chuàng)建;對下主要是讀寫函數(shù)的設(shè)置,codec本身的dai設(shè)置,初始化寄存器的設(shè)置,最重要的就是后面的control的創(chuàng)建和門的創(chuàng)建了,如下圖所示:
這里面的第一部分就是負(fù)責(zé)初始化的;
?
第二部分就是創(chuàng)建卡和流,對于alsa驅(qū)動來說,是先分成卡0,卡1…,然后對于每一個卡的每一個cpu支持的dai(digit audio interface)也就是pcm接口或者i2S接口等都要建立對應(yīng)的流,一個dai有可能包含兩個流,一個是錄的一個是play的,但在我們的平臺上對于i2S的dai是沒有錄音功能的,所以我們的平臺只有一個卡,三個流,pcm的錄和play,i2S的play;流的創(chuàng)建還是更多的考慮為上層服務(wù)的,它所提供的接口都是soc層的,這里非常重要的地方在于驅(qū)動的一個典型做法那就是如何把關(guān)鍵的內(nèi)核數(shù)據(jù)結(jié)構(gòu)和export到外部的/dev下的設(shè)備節(jié)點實現(xiàn)關(guān)聯(lián),比如:
?
關(guān)鍵數(shù)據(jù)結(jié)構(gòu)structsnd_pcm,是根據(jù)cpu所固有的dai創(chuàng)建的,而對于每一個struct ?snd_pcm又可能用到兩個substream(它們實現(xiàn)具體的流的播放等),它們之間的鏈接是通過它的內(nèi)部數(shù)據(jù)成員struct snd_pcm_str streams[2];來連接的,而這個snd_pcm類型的指針是在函數(shù)snd_device_new里面通過device_data放到設(shè)備里面的,這個設(shè)備會在snd_device_register_all
的時候注冊到/dev下面,并且調(diào)用dev_set_drvdata(preg->dev, private_data);來把這個指針放到設(shè)備的私有數(shù)據(jù)里面;而在需要使用的時候通過snd_pcm_playback_open里面的snd_lookup_minor_data函數(shù)取得其私有數(shù)據(jù)并返回的,這樣就實現(xiàn)了設(shè)備節(jié)點和對應(yīng)的驅(qū)動的數(shù)據(jù)結(jié)構(gòu)的關(guān)聯(lián),這是一種非常普遍的做法;有了這個數(shù)據(jù)結(jié)構(gòu)它就可以根據(jù)一定的原則取得對應(yīng)于這個需求的substream,于是一切的操作都可以交給這個substream了;
?
第三部分就是control的創(chuàng)建,這個函數(shù)比較簡單,就是把表micco_snd_controls里面已經(jīng)定義好的controls模板創(chuàng)建controls,然后加入到card的controls列表中去;本身功能很清晰,但是對于我們平臺來說,需要非常小心,因為這里決定了各個controls的序號,而這個序號是audio_controller訪問硬件的索引,所以千萬要小心盡量要維持目前的controls的序號,如果要額外添加新的controls一定要記得要放在micco_add_widgets后面來做,這樣可以做到兼容,否則audio_controller的工作量就大了!
?
第四部分就是門的創(chuàng)建了,這個函數(shù)也是很清楚,就是把codec對應(yīng)的門都加入到codec->dapm_widgets列表中去(這里的門的概念可以簡單的理解為水管與水管之間的連接的地方,聲音數(shù)據(jù)像水一樣從水管里面流出來,源頭可以是CPU了,也可以是modem,然后通過不同的門,流向不同的地方,比如speaker,比如藍牙耳機等等),然后根據(jù)micco_audio_map把所有可能連在一起的門連接起來,這個表micco_audio_map的意思是{目的名字,控制點名字,源頭名字},然后函數(shù)snd_soc_dapm_connect_input會根據(jù)這些名字去查表codec->dapm_widgets(先前已經(jīng)把所有的門都加入了)找到它們再根據(jù)不同的類型做不同的連接,比如是mux之間的連接,mux和pga之間的連接等等,注意這里的連接其實只不過是說找到連接的可能性,它對于不同的門,找到其可能的source和sink,加入到對應(yīng)的列表中去,具體細(xì)節(jié)如下:
首先,掃描整個codec所擁有的所有的門,如果它的名字和傳入的sink的名字相同,則認(rèn)為它就是這個路徑的sink,如果它的名字和傳入的source名字相同,則認(rèn)為它是這個路徑的source,如果源頭或者sink沒有找到都返回錯誤;然后分配一個struct snd_soc_dapm_path,這個數(shù)據(jù)結(jié)構(gòu)的主要成分包括名字,source門,sink門,這條路徑的control,這個源頭和sink是否已經(jīng)連接,是否已經(jīng)走過(用在后面),這個數(shù)據(jù)結(jié)構(gòu)會被掛在三個鏈表里面,一個是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列表里面,所以當(dāng)訪問的時候從wsink門的sources出發(fā)就可以找到連接這個門作為sink的所有的路徑,而從wsource的sinks列表出發(fā)就可以找到所有以這個門作為source的路徑;
第三步就是為這個數(shù)據(jù)結(jié)構(gòu)賦值:source,sink,初始化三個鏈表;第四步:如果control為空則把這個路徑加入到相應(yīng)的三個鏈表中去,并且路徑設(shè)為已經(jīng)連接,并返回;第五步:否則,根據(jù)sink的類型,如果是adc,dac,input,output,micbias,vmid,pre,post,則把路徑加入到三個鏈表,設(shè)置已經(jīng)連接的標(biāo)志;如果是snd_soc_dapm_mux則調(diào)用dapm_connect_mux來處理;如果是mixer和switch則調(diào)用dapm_connect_mixer來處理,如果是hp,mic,line,spk,則把path加入到三個鏈表中去,但是設(shè)置成為連接的狀態(tài)。
大約就是這樣的了。
也許您要問,為什么要這么做呢?
這個,我也有想過,甚至我認(rèn)為在門比較少的時候,確實沒什么必要,但是這么做的好處在于當(dāng)要播放音樂的時候,它可以實現(xiàn)自動的尋找路徑并且自動poweron那些門,不需要上層做任何的控制,因為它真的到達目的地的所有的路徑,這樣它可以自己選擇走哪條路;如果不這么連接起來的話,就需要提供給上層連接的接口,完全由上層來決定該連接哪些門,也必須由上層來負(fù)責(zé)poweron和off這些門;
第五部分就是注冊了,這里就是向/dev注冊設(shè)備節(jié)點,因為這些設(shè)備節(jié)點會由alsa層來訪問的,這些設(shè)備節(jié)點和驅(qū)動的連接我前面已經(jīng)說了,主要是提供了對上的alsa接口,給alsa層調(diào)用。??