一口君后面會陸續(xù)更新基于瑞芯微rk3568的I2S系列文章。
預(yù)計10篇左右。有對語音感興趣的朋友,可以收藏該專題。
《瑞芯微-I2S | 音頻驅(qū)動調(diào)試基本命令和工具-基于rk3568-2》
針對音頻設(shè)備,linux內(nèi)核中包含了兩類音頻設(shè)備驅(qū)動框架;
OSS:開放聲音系統(tǒng),包含dsp和mixer字符設(shè)備接口,應(yīng)用訪問底層硬件是直接通過sound設(shè)備節(jié)點實現(xiàn)的;
ALSA:先進linux聲音架構(gòu)(Advanced Linux Sound Archiecture),以card和組件(PCM、mixer等)為組件,應(yīng)用是通過ALSA提供的alsa-lib庫訪問底層硬件的操作,不再訪問sound設(shè)備節(jié)點了
1.ALSA概述
ALSA由一系列的內(nèi)核驅(qū)動、應(yīng)用程序編程接口(API)以及支持linux下聲音的應(yīng)用程序組成、
ALSA項目發(fā)起的原因是linux下的聲卡驅(qū)動(OSS)沒有獲得積極的維護,而且落后于新的聲卡技術(shù)。
Jaroslav Kysela早先寫了一個聲卡驅(qū)動,并由此開始了ALSA項目,隨后,更多的開發(fā)者加入到開發(fā)隊伍中,更多的聲卡獲得支持,API的結(jié)構(gòu)也獲得了重組。目前已經(jīng)成為了linux的主流音頻體系結(jié)構(gòu)。
ALSA的官網(wǎng):https://www.alsa-project.org/wiki/Main_Page
2. ALSA組件
ALSA系統(tǒng)包括:
1、alsa-driver:alsa系統(tǒng)驅(qū)動。
2、alsa-lib:alsa庫,用戶空間調(diào)用,和內(nèi)核空間交互。
3、alsa-utils:命令行工具。
4、alsa-plugin:alsa插件。
5、alsa-tools:alsa工具。
在應(yīng)用層,ALSA 為我們提供了 alsa-lib,在 Linux 內(nèi)核設(shè)備驅(qū)動層,ALSA 提供了 alsa-driver
。
Linux 應(yīng)用程序只需要調(diào)用 alsa-lib 提供的 API,即可完成對底層音頻硬件的控制。
linux內(nèi)核中alsa的軟件結(jié)構(gòu)如下:
用戶空間的 alsa-lib
對應(yīng)用程序提供統(tǒng)一的 API 接口,隱藏了驅(qū)動層的實際細節(jié),簡化了應(yīng)用程序的實現(xiàn)難度
但是由于 alsa-lib 也由于過大,因此在 android 等下也經(jīng)常使用 tiny-alsa
。
如上圖所示,在 Linux 內(nèi)核中,有對 alsa-driver 進一步的封裝,即 alsa-soc
。
ALSA框架從上到下依次為應(yīng)用程序、ALSA Library API、ALSA CORE、ASoC CORE、硬件驅(qū)動程序、硬件設(shè)備;
應(yīng)用程序:
tinyplay/tinycap/tinymix,這些用戶程序直接調(diào)用alsa用戶庫接口來實現(xiàn)放音、錄音、控制;
ALSA Library API:
alsa用戶庫接口,對應(yīng)用程序提供統(tǒng)一的API接口,這樣可以隱藏了驅(qū)動層的實現(xiàn)細節(jié),簡化了應(yīng)用程序的實現(xiàn)難度;常見有tinyalsa、alsa-lib;
ALSA CORE:
alsa核心層,向上提供邏輯設(shè)備(PCM/CTL/MIDI/TIMER/…)系統(tǒng)調(diào)用,向下驅(qū)動硬件設(shè)備(Machine/I2S/DMA/CODEC);
ASoC CORE:
建立在標(biāo)準(zhǔn)ALSA CORE基礎(chǔ)上,為了更好支持嵌入式系統(tǒng)和應(yīng)用于移動設(shè)備的音頻Codec的一套軟件體系,它將音頻硬件設(shè)備驅(qū)動劃分為Codec、Platform 和 Machine;
Hardware Driver:
音頻硬件設(shè)備驅(qū)動,由三大部分組成,分別是 Machine、Platform、Codec;
3. ?ALSA 設(shè)備文件
Linux 系統(tǒng)下看到的設(shè)備文件結(jié)構(gòu)如下:
rk3568_r:/?#?cd?/dev/snd/
rk3568_r:/dev/snd?#?ls?-l
total?0
crw-rw----?1?system?audio?116,???4?2024-02-18?15:28?controlC0
crw-rw----?1?system?audio?116,???6?2024-02-18?15:28?controlC1
crw-rw----?1?system?audio?116,???3?2024-02-18?15:28?pcmC0D0c
crw-rw----?1?system?audio?116,???2?2024-02-18?15:28?pcmC0D0p
crw-rw----?1?system?audio?116,???5?2024-02-18?15:28?pcmC1D0p
crw-rw----?1?system?audio?116,??33?2024-02-18?15:28?timer
從上面能看到有如下設(shè)備文件:
controlC0?-->??????????????用于聲卡的控制,例如通道選擇,混音,麥克控制,音量加減,開關(guān)等
controlC1
pcmC0D0c?-->???????????????用于錄音的pcm設(shè)備
pcmC0D0p?-->???????????????用于播放的pcm設(shè)備
pcmC1D0p
timer????-->???????????????定時器
有的平臺還有以下設(shè)備節(jié)點:
midiC0D0??-->??????????????用于播放midi音頻
seq??????-->???????????????音序器
其中,C0、D0代表的是聲卡0中的設(shè)備0
pcmC0D0c最后一個c代表capture
pcmC0D0p最后一個p代表playback
這些都是alsa-driver中的命名規(guī)則,從上面的列表可以看出,聲卡下掛了6個設(shè)備
根據(jù)聲卡的實際能力,驅(qū)動實際上可以掛載更多種類的設(shè)備,在include/sound/core.h
中,定義了以下設(shè)備類型,通常更關(guān)心的是 pcm
和 control
這兩種設(shè)備,Default 一個聲卡對應(yīng)一個 Control 設(shè)備。
#define????SNDRV_DEV_TOPLEVEL????((__force?snd_device_type_t)?0)
#define????SNDRV_DEV_CONTROL????((__force?snd_device_type_t)?1)
#define????SNDRV_DEV_LOWLEVEL_PRE????((__force?snd_device_type_t)?2)
#define????SNDRV_DEV_LOWLEVEL_NORMAL?((__force?snd_device_type_t)?0x1000)
#define????SNDRV_DEV_PCM????????((__force?snd_device_type_t)?0x1001)
#define????SNDRV_DEV_RAWMIDI????((__force?snd_device_type_t)?0x1002)
#define????SNDRV_DEV_TIMER????????((__force?snd_device_type_t)?0x1003)
#define????SNDRV_DEV_SEQUENCER????((__force?snd_device_type_t)?0x1004)
#define????SNDRV_DEV_HWDEP????????((__force?snd_device_type_t)?0x1005)
#define????SNDRV_DEV_INFO????????((__force?snd_device_type_t)?0x1006)
#define????SNDRV_DEV_BUS????????((__force?snd_device_type_t)?0x1007)
#define????SNDRV_DEV_CODEC????????((__force?snd_device_type_t)?0x1008)
#define????SNDRV_DEV_JACK??????????((__force?snd_device_type_t)?0x1009)
#define????SNDRV_DEV_COMPRESS????((__force?snd_device_type_t)?0x100A)
#define????SNDRV_DEV_LOWLEVEL????((__force?snd_device_type_t)?0x2000)
4. Linux ALSA 源碼目錄結(jié)構(gòu)
在 Linux 源碼中 ALSA 架構(gòu)的代碼在 /sound
下,Linux 5.0 的目錄如下:
其中各主要子目錄的作用如下:
core???????該目錄包含了ALSA?驅(qū)動的中間層,它是整個?ALSA?驅(qū)動的核心部分
core/oss???包含模擬舊的?OSS?架構(gòu)的?PCM?和?Mixer?模塊
core/seq???有關(guān)音序器相關(guān)的代碼
drivers????放置一些與CPU、BUS架構(gòu)無關(guān)的公用代碼
i2c????????ALSA自己的I2C控制代碼
pci????????pci聲卡的頂層目錄,子目錄包含各種pci聲卡的代碼
isa????????isa聲卡的頂層目錄,子目錄包含各種isa聲卡的代碼
soc????????針對?system-on-chip?體系的中間層代碼,即ASOC代碼
soc/rockchip?瑞芯微平臺i2s控制器驅(qū)動代碼
soc/codecs???針對soc?體系的各種?codec?的代碼,與平臺無關(guān)?
include????ALSA驅(qū)動的公共頭文件目錄,其路徑為?/include/sound,該目錄的頭文件需要導(dǎo)出給用戶空間的應(yīng)用程序使用,通常,驅(qū)動模塊私有的頭文件不應(yīng)放置在這里
更多目錄結(jié)構(gòu)信息可以參考:?https://www.kernel.org/doc/html/latest/sound/kernel-api/writing-an-alsa-driver.html。
5. ?ALSA核心數(shù)據(jù)結(jié)構(gòu)
由于ASoC是建立在標(biāo)準(zhǔn)ALSA CORE上的一套軟件體系,因此本篇博客重點介紹的都是ALSA CORE中的數(shù)據(jù)結(jié)構(gòu),并不涉及到ASoC CORE中的數(shù)據(jù)結(jié)構(gòu)。
ALSA CORE中的數(shù)據(jù)結(jié)構(gòu)大部分定義在include/sound/core.h、以及sound/core/目錄下的文件中。
1) struct snd_card
linux內(nèi)核中使用struct snd_card表示ALSA音頻驅(qū)動中的聲卡設(shè)備
struct snd_card可以說是整個ALSA音頻驅(qū)動最頂層的一個結(jié)構(gòu),整個聲卡的軟件邏輯結(jié)構(gòu)開始于該結(jié)構(gòu),
幾乎所有與聲音相關(guān)的邏輯設(shè)備都是在snd_card的管理之下
聲卡驅(qū)動的第一個動作通常就是創(chuàng)建一個snd_card結(jié)構(gòu)體。
@include/sound/core.h
????
/*?main?structure?for?soundcard?*/
struct?snd_card?{
?int?number;???/*?number?of?soundcard?(index?to
????????snd_cards)?*/
?char?id[16];???/*?id?string?of?this?card?*/
?char?driver[16];??/*?driver?name?*/
?char?shortname[32];??/*?short?name?of?this?soundcard?*/
?char?longname[80];??/*?name?of?this?soundcard?*/
?char?irq_descr[32];??/*?Interrupt?description?*/
?char?mixername[80];??/*?mixer?name?*/
?char?components[128];??/*?card?components?delimited?with
????????space?*/
?struct?module?*module;??/*?top-level?module?*/
?void?*private_data;??/*?private?data?for?soundcard?*/
?void?(*private_free)?(struct?snd_card?*card);?/*?callback?for?freeing?of
????????private?data?*/
?struct?list_head?devices;?/*?devices?*/
?struct?device?ctl_dev;??/*?control?device?*/
?unsigned?int?last_numid;?/*?last?used?numeric?ID?*/
?struct?rw_semaphore?controls_rwsem;?/*?controls?list?lock?*/
?rwlock_t?ctl_files_rwlock;?/*?ctl_files?list?lock?*/
?int?controls_count;??/*?count?of?all?controls?*/
?int?user_ctl_count;??/*?count?of?all?user?controls?*/
?struct?list_head?controls;?/*?all?controls?for?this?card?*/
?struct?list_head?ctl_files;?/*?active?control?files?*/
?struct?snd_info_entry?*proc_root;?/*?root?for?soundcard?specific?files?*/
?struct?snd_info_entry?*proc_id;?/*?the?card?id?*/
?struct?proc_dir_entry?*proc_root_link;?/*?number?link?to?real?id?*/
?struct?list_head?files_list;?/*?all?files?associated?to?this?card?*/
?struct?snd_shutdown_f_ops?*s_f_ops;?/*?file?operations?in?the?shutdown
????????state?*/
?spinlock_t?files_lock;??/*?lock?the?files?for?this?card?*/
?int?shutdown;???/*?this?card?is?going?down?*/
?struct?completion?*release_completion;
?struct?device?*dev;??/*?device?assigned?to?this?card?*/
?struct?device?card_dev;??/*?cardX?object?for?sysfs?*/
?const?struct?attribute_group?*dev_groups[4];?/*?assigned?sysfs?attr?*/
?bool?registered;??/*?card_dev?is?registered??*/
?wait_queue_head_t?remove_sleep;
?int?offline;???/*?if?this?sound?card?is?offline?*/
?unsigned?long?offline_change;
?wait_queue_head_t?offline_poll_wait;
#ifdef?CONFIG_PM
?unsigned?int?power_state;?/*?power?state?*/
?wait_queue_head_t?power_sleep;
#endif
#if?IS_ENABLED(CONFIG_SND_MIXER_OSS)
?struct?snd_mixer_oss?*mixer_oss;
?int?mixer_oss_change_count;
#endif
};
@include/sound/core.h
struct?snd_card?{
????number:?聲卡設(shè)備編號,通常從0開始,通過編號可以在snd_cards指針數(shù)組中找到對應(yīng)的聲卡設(shè)備;
????id[16]:聲卡設(shè)備的標(biāo)識符;
??? driver:驅(qū)動名稱;
??? shortname:設(shè)備簡稱,更多地用于打印信息;
??? longname:設(shè)備名稱,會在具體驅(qū)動中設(shè)置,主要反映在/proc/asound/cards中;
??? irq_descr:中斷描述信息;
??? mixername:混音器名稱;
??? components:聲卡組件名稱,由空格分隔;
????module:頂層模塊;
??? private_data:聲卡的私有數(shù)據(jù);
??? private_free:釋放私有數(shù)據(jù)的回調(diào)函數(shù);
??? devices:保存該聲卡下所有邏輯設(shè)備的鏈表;鏈表中存放的數(shù)據(jù)類型為struct?snd_device;
????ctl_dev:聲卡Control設(shè)備內(nèi)核設(shè)備結(jié)構(gòu)體,其parent為card_dev,class為sound_class,主設(shè)備號為116;
????last_numid:存儲注冊snd_control時為其分配的編號;
????controls_rwsem:讀寫信號量,用于并發(fā)操作controls鏈表;
????ctl_files_rwlock:讀寫自旋鎖,用于并發(fā)操作ctl_files鏈表;
????controls_count:controls鏈表的長度;
????user_ctl_count:用戶控制設(shè)備的數(shù)量;
????controls:保存該聲卡下所有控件(controls)的鏈表;該鏈表中存放的數(shù)據(jù)類型為struct?snd_kcontrol;
????ctl_files:用于管理該card下的active的control設(shè)備;鏈表中存放的數(shù)據(jù)類型為struct?snd_ctl_file;
????proc_root:聲卡設(shè)備在proc文件系統(tǒng)的根目錄;即/proc/asound/card%d目錄;
????proc_root_link:指向/proc/asound/card%d的鏈接文件,文件名為id;
????files_list:保存此聲卡相關(guān)的所有文件的鏈表;鏈表中存放的數(shù)據(jù)類型為struct?snd_monitor_file;
????s_f_ops:關(guān)機狀態(tài)下的文件操作;
????files_lock:自旋鎖;
????shutdown:此聲卡正在關(guān)閉;
????release_completion:釋放完成;
????dev:分配給此聲卡的設(shè)備,一般為平臺設(shè)備的device;
????card_dev:聲卡設(shè)備的內(nèi)核設(shè)備結(jié)構(gòu)體,card用于在sys中顯示,用于代表該card;把snd_card看做是device的子類,其parent為dev,class為sound_class,未設(shè)置設(shè)備號devt(默認就是0);
????dev_groups:分配的sysfs屬性組;
????registered:聲卡設(shè)備card_dev是否已注冊;
????remove_sleep:等待隊列頭;
????power_state:電源狀態(tài);
????power_sleep:電源等待隊列頭;
};
每一個聲卡設(shè)備的創(chuàng)建都是通過snd_card_new函數(shù)實現(xiàn)的,聲卡設(shè)備被注冊后都會被添加到全局snd_cards指針數(shù)組中;
@include/sound/core.h
int?snd_card_new(struct?device?*parent,?int?idx,?const?char?*xid,
???struct?module?*module,?int?extra_size,
???struct?snd_card?**card_ret);
@sound/core/init.c???
static?struct?snd_card?*snd_cards[SNDRV_CARDS];
2) struct snd_device
聲卡設(shè)備一般包含許多功能模塊,比如PCM(錄音和播放)、Control(聲卡控制),因此ALSA將聲卡的功能模塊又抽象為一個邏輯設(shè)備,與之對應(yīng)的數(shù)據(jù)結(jié)構(gòu)就是struct snd_device;
@sound/core/device.c
struct?snd_device?{
????????struct?list_head?list;??????????/*?list?of?registered?devices?*/
????????struct?snd_card?*card;??????????/*?card?which?holds?this?device?*/
????????enum?snd_device_state?state;????/*?state?of?the?device?*/
????????enum?snd_device_type?type;??????/*?device?type?*/
????????void?*device_data;??????????????/*?device?structure?*/
????????struct?snd_device_ops?*ops;?????/*?operations?*/
};
????list:用于構(gòu)建雙向鏈表節(jié)點,該節(jié)點會添加到聲卡設(shè)備snd_card的devices鏈表中;
??? snd_card:表示當(dāng)前聲卡邏輯設(shè)備所屬的聲卡設(shè)備;
??? state:表示當(dāng)前聲卡邏輯設(shè)備的狀態(tài);
??? type:表示當(dāng)前聲卡邏輯設(shè)備的類型,比如pcm、control設(shè)備;
??? device_data:一般用于存放具體的功能模塊邏輯設(shè)備的結(jié)構(gòu),比如對于pcm邏輯設(shè)備存放的就是snd_pcm實例;
??? ops:聲卡邏輯設(shè)備的操作集;
每一個聲卡邏輯設(shè)備的創(chuàng)建最終會調(diào)用snd_device_new來生成一個snd_device實例,并把該實例鏈接到snd_card的devices鏈表中。
通常,linux內(nèi)核已經(jīng)提供了一些常用的功能模塊邏輯設(shè)備的創(chuàng)建函數(shù),而不必直接調(diào)用snd_device_new,比如:snd_pcm_new、snd_ctl_create。
需要注意的是:聲卡邏輯設(shè)備注冊后會在**/dev/snd**目錄下生成對應(yīng)的字符設(shè)備文件。
3) struct snd_device_ops
linux中使用snd_device_ops來表示聲卡邏輯設(shè)備的操作集;
@include/sound/core.h
struct?snd_device_ops?{
?int?(*dev_free)(struct?snd_device?*dev);
?int?(*dev_register)(struct?snd_device?*dev);
?int?(*dev_disconnect)(struct?snd_device?*dev);
};
其中:
- dev_free:聲卡邏輯設(shè)備釋放函數(shù),在卸載聲卡設(shè)備時被調(diào)用;dev_register:聲卡邏輯設(shè)備注冊函數(shù),在注冊聲卡設(shè)備被調(diào)用;dev_disconnect:聲卡邏輯設(shè)備斷開連接函數(shù),在關(guān)閉聲卡設(shè)備時被調(diào)用。
4) enum snd_device_state
linux內(nèi)核使用snd_device_state表示聲卡邏輯設(shè)備的狀態(tài)
@include/sound/core.h
enum?snd_device_state?{
????????SNDRV_DEV_BUILD,?????????//?構(gòu)建中
????????SNDRV_DEV_REGISTERED,????//?已經(jīng)準(zhǔn)備并準(zhǔn)備就緒
????????SNDRV_DEV_DISCONNECTED,??//?已斷開連接
};
5) enum snd_device_type
linux內(nèi)核使用snd_device_state表示聲卡邏輯設(shè)備的類型
@include/sound/core.h
enum?snd_device_type?{
?SNDRV_DEV_LOWLEVEL,
?SNDRV_DEV_INFO,
?SNDRV_DEV_BUS,
?SNDRV_DEV_CODEC,
?SNDRV_DEV_PCM,
?SNDRV_DEV_COMPRESS,
?SNDRV_DEV_RAWMIDI,
?SNDRV_DEV_TIMER,
?SNDRV_DEV_SEQUENCER,
?SNDRV_DEV_HWDEP,
?SNDRV_DEV_JACK,
?SNDRV_DEV_CONTROL,?/*?NOTE:?this?must?be?the?last?one?*/
};
其中:
??? SNDRV_DEV_LOWLEVEL:低級別硬件訪問接口;
??? SNDRV_DEV_INFO:信息查詢接口;
??? SNDRV_DEV_BUS:總線接口,如USB、PCI等;
??? SNDRV_DEV_CODEC:編解碼器設(shè)備;
??? SNDRV_DEV_PCM:PCM 設(shè)備,包括輸入輸出設(shè)備以及混音器等;
??? SNDRV_DEV_COMPRESS:壓縮和解壓縮設(shè)備;
??? SNDRV_DEV_RAWMIDI:原始MIDI設(shè)備;
??? SNDRV_DEV_TIMER:定時器設(shè)備;
??? SNDRV_DEV_SEQUENCER:序列器設(shè)備;
??? SNDRV_DEV_HWDEP:硬件依賴設(shè)備;
??? SNDRV_DEV_JACK:JACK音頻連接設(shè)備;
??? SNDRV_DEV_CONTROL:control設(shè)備,此項必須放在最后;
6) struct snd_minor
linux內(nèi)核使用snd_minor表示聲卡邏輯設(shè)備上下文信息,它在調(diào)用snd_register_device函數(shù)注冊聲卡邏輯設(shè)備時被初始化,在聲卡邏輯設(shè)備被使用時就可以從該結(jié)構(gòu)體中得到相應(yīng)的信息。
struct?snd_minor?{
????????int?type;???????????????????????/*?SNDRV_DEVICE_TYPE_XXX?*/
????????int?card;???????????????????????/*?card?number?*/
????????int?device;?????????????????????/*?device?number?*/
????????const?struct?file_operations?*f_ops;????/*?file?operations?*/
????????void?*private_data;?????????????/*?private?data?for?f_ops->open?*/
????????struct?device?*dev;?????????????/*?device?for?sysfs?*/
????????struct?snd_card?*card_ptr;??????/*?assigned?card?instance?*/
};
其中:
- type:設(shè)備類型,取值為 SNDRV_DEVICE_TYPE_XXX;card:聲卡邏輯設(shè)備所屬的聲卡設(shè)備的編號;device:設(shè)備索引;f_ops:文件操作集。private_data:用戶提供給 f_ops->open函數(shù)的私有數(shù)據(jù)指針;dev:聲卡邏輯設(shè)備對應(yīng)的 struct device 結(jié)構(gòu)體指針;card_ptr:指向所屬聲卡設(shè)備;
每一個snd_minor的創(chuàng)建都是通過snd_register_device函數(shù)實現(xiàn)的,并被添加到全局snd_minors指針數(shù)組中;
static?struct?snd_minor?*snd_minors[SNDRV_OS_MINORS];
6. alsa數(shù)據(jù)結(jié)構(gòu)之間關(guān)系
為了更加形象的表示struct snd_card、struct snd_device、struct snd_minor 之間的關(guān)系,我們繪制了如下關(guān)系框圖:
7. 內(nèi)核中alsa幾個主要函數(shù)
1) snd_register_device()
Linux 內(nèi)核 ALSA 音頻框架的其它部分需要創(chuàng)建音頻設(shè)備文件時,調(diào)用 snd_register_device() 函數(shù)為聲卡注冊 ALSA 設(shè)備文件,該函數(shù)定義 (位于 sound/core/sound.c)
/**
?*?snd_register_device?-?Register?the?ALSA?device?file?for?the?card
?*?@type:?the?device?type,?SNDRV_DEVICE_TYPE_XXX
?*?@card:?the?card?instance
?*?@dev:?the?device?index
?*?@f_ops:?the?file?operations
?*?@private_data:?user?pointer?for?f_ops->open()
?*?@device:?the?device?to?register
?*
?*?Registers?an?ALSA?device?file?for?the?given?card.
?*?The?operators?have?to?be?set?in?reg?parameter.
?*
?*?Return:?Zero?if?successful,?or?a?negative?error?code?on?failure.
?*/
int?snd_register_device(int?type,?struct?snd_card?*card,?int?dev,
???const?struct?file_operations?*f_ops,
???void?*private_data,?struct?device?*device)
?int?minor;
?int?err?=?0;
?struct?snd_minor?*preg;
?if?(snd_BUG_ON(!device))
??return?-EINVAL;
?preg?=?kmalloc(sizeof?*preg,?GFP_KERNEL);
?if?(preg?==?NULL)
??return?-ENOMEM;
?preg->type?=?type;
?preg->card?=?card???card->number?:?-1;
?preg->device?=?dev;
?preg->f_ops?=?f_ops;
?preg->private_data?=?private_data;
?preg->card_ptr?=?card;
?mutex_lock(&sound_mutex);
?minor?=?snd_find_free_minor(type,?card,?dev);
?if?(minor?<?0)?{
??err?=?minor;
??goto?error;
?}
?preg->dev?=?device;
?device->devt?=?MKDEV(major,?minor);
?err?=?device_add(device);
?if?(err?<?0)
??goto?error;
?snd_minors[minor]?=?preg;
?error:
?mutex_unlock(&sound_mutex);
?if?(err?<?0)
??kfree(preg);
?return?err;
}
EXPORT_SYMBOL(snd_register_device);
參數(shù)說明如下
type:??設(shè)備類型
card:??聲卡實例
dev:???設(shè)備索引
f_ops:?文件操作。用戶空間程序?qū)υO(shè)備文件的各種操作,都將由這里傳入的文件操作執(zhí)行。
private_data:f_ops->open()?要用到的用戶指針
device:要注冊的設(shè)備
snd_register_device() 函數(shù)的執(zhí)行過程如下:
- 分配 struct snd_minor 對象,并初始化它;為要注冊的設(shè)備尋找可用的從設(shè)備號。這有兩種策略,一種是動態(tài)從設(shè)備號,另一種是靜態(tài)從設(shè)備號。無論是哪種策略,SEQUENCER 和 TIMER 類型的設(shè)備的從設(shè)備號都是固定的 1 和 33。其它類型的設(shè)備,在動態(tài)從設(shè)備號策略中,順序查找可用的從設(shè)備號;在靜態(tài)從設(shè)備號策略中,根據(jù)聲卡索引、設(shè)備類型和設(shè)備索引構(gòu)造從設(shè)備號;構(gòu)造包含主設(shè)備號和從設(shè)備號的設(shè)備號;向設(shè)備層次體系結(jié)構(gòu)添加設(shè)備;將 struct snd_minor 對象指針保存在 struct snd_minor 對象指針數(shù)組中。
2) device_add()
snd_register_device() 函數(shù)調(diào)用 device_add() 函數(shù)向設(shè)備層次體系結(jié)構(gòu)添加設(shè)備,這個函數(shù)定義 (位于 drivers/base/core.c) 如下:
int?device_add(struct?device?*dev)
{
?struct?device?*parent;
?struct?kobject?*kobj;
?struct?class_interface?*class_intf;
?int?error?=?-EINVAL;
?struct?kobject?*glue_dir?=?NULL;
?dev?=?get_device(dev);
?if?(!dev)
??goto?done;
?if?(!dev->p)?{
??error?=?device_private_init(dev);
??if?(error)
???goto?done;
?}
?/*
??*?for?statically?allocated?devices,?which?should?all?be?converted
??*?some?day,?we?need?to?initialize?the?name.?We?prevent?reading?back
??*?the?name,?and?force?the?use?of?dev_name()
??*/
?if?(dev->init_name)?{
??dev_set_name(dev,?"%s",?dev->init_name);
??dev->init_name?=?NULL;
?}
?/*?subsystems?can?specify?simple?device?enumeration?*/
?if?(!dev_name(dev)?&&?dev->bus?&&?dev->bus->dev_name)
??dev_set_name(dev,?"%s%u",?dev->bus->dev_name,?dev->id);
?if?(!dev_name(dev))?{
??error?=?-EINVAL;
??goto?name_error;
?}
?pr_debug("device:?'%s':?%sn",?dev_name(dev),?__func__);
?parent?=?get_device(dev->parent);
?kobj?=?get_device_parent(dev,?parent);
?if?(IS_ERR(kobj))?{
??error?=?PTR_ERR(kobj);
??goto?parent_error;
?}
?if?(kobj)
??dev->kobj.parent?=?kobj;
?/*?use?parent?numa_node?*/
?if?(parent?&&?(dev_to_node(dev)?==?NUMA_NO_NODE))
??set_dev_node(dev,?dev_to_node(parent));
?/*?first,?register?with?generic?layer.?*/
?/*?we?require?the?name?to?be?set?before,?and?pass?NULL?*/
?error?=?kobject_add(&dev->kobj,?dev->kobj.parent,?NULL);
?if?(error)?{
??glue_dir?=?get_glue_dir(dev);
??goto?Error;
?}
?/*?notify?platform?of?device?entry?*/
?error?=?device_platform_notify(dev,?KOBJ_ADD);
?if?(error)
??goto?platform_error;
?error?=?device_create_file(dev,?&dev_attr_uevent);
?if?(error)
??goto?attrError;
?error?=?device_add_class_symlinks(dev);
?if?(error)
??goto?SymlinkError;
?error?=?device_add_attrs(dev);
?if?(error)
??goto?AttrsError;
?error?=?bus_add_device(dev);
?if?(error)
??goto?BusError;
?error?=?dpm_sysfs_add(dev);
?if?(error)
??goto?DPMError;
?device_pm_add(dev);
?if?(MAJOR(dev->devt))?{
??error?=?device_create_file(dev,?&dev_attr_dev);
??if?(error)
???goto?DevAttrError;
??error?=?device_create_sys_dev_entry(dev);
??if?(error)
???goto?SysEntryError;
??devtmpfs_create_node(dev);
?}
?/*?Notify?clients?of?device?addition.??This?call?must?come
??*?after?dpm_sysfs_add()?and?before?kobject_uevent().
??*/
?if?(dev->bus)
??blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
??????????BUS_NOTIFY_ADD_DEVICE,?dev);
?kobject_uevent(&dev->kobj,?KOBJ_ADD);
?/*
??*?Check?if?any?of?the?other?devices?(consumers)?have?been?waiting?for
??*?this?device?(supplier)?to?be?added?so?that?they?can?create?a?device
??*?link?to?it.
??*
??*?This?needs?to?happen?after?device_pm_add()?because?device_link_add()
??*?requires?the?supplier?be?registered?before?it's?called.
??*
??*?But?this?also?needs?to?happen?before?bus_probe_device()?to?make?sure
??*?waiting?consumers?can?link?to?it?before?the?driver?is?bound?to?the
??*?device?and?the?driver?sync_state?callback?is?called?for?this?device.
??*/
?if?(dev->fwnode?&&?!dev->fwnode->dev)?{
??dev->fwnode->dev?=?dev;
??fw_devlink_link_device(dev);
?}
?bus_probe_device(dev);
?if?(parent)
??klist_add_tail(&dev->p->knode_parent,
??????????&parent->p->klist_children);
?if?(dev->class)?{
??mutex_lock(&dev->class->p->mutex);
??/*?tie?the?class?to?the?device?*/
??klist_add_tail(&dev->p->knode_class,
??????????&dev->class->p->klist_devices);
??/*?notify?any?interfaces?that?the?device?is?here?*/
??list_for_each_entry(class_intf,
????????&dev->class->p->interfaces,?node)
???if?(class_intf->add_dev)
????class_intf->add_dev(dev,?class_intf);
??mutex_unlock(&dev->class->p->mutex);
?}
done:
?put_device(dev);
?return?error;
?SysEntryError:
?if?(MAJOR(dev->devt))
??device_remove_file(dev,?&dev_attr_dev);
?DevAttrError:
?device_pm_remove(dev);
?dpm_sysfs_remove(dev);
?DPMError:
?bus_remove_device(dev);
?BusError:
?device_remove_attrs(dev);
?AttrsError:
?device_remove_class_symlinks(dev);
?SymlinkError:
?device_remove_file(dev,?&dev_attr_uevent);
?attrError:
?device_platform_notify(dev,?KOBJ_REMOVE);
platform_error:
?kobject_uevent(&dev->kobj,?KOBJ_REMOVE);
?glue_dir?=?get_glue_dir(dev);
?kobject_del(&dev->kobj);
?Error:
?cleanup_glue_dir(dev,?glue_dir);
parent_error:
?put_device(parent);
name_error:
?kfree(dev->p);
?dev->p?=?NULL;
?goto?done;
}
EXPORT_SYMBOL_GPL(device_add);
device_add() 函數(shù)調(diào)用 device_create_file() 和 devtmpfs_create_node() 等函數(shù)在 sysfs 和 devtmpfs 文件系統(tǒng)中創(chuàng)建文件。
內(nèi)核各模塊通過 devtmpfs_create_node() 函數(shù)創(chuàng)建 devtmpfs 文件,這個函數(shù)定義 (位于 drivers/base/devtmpfs.c) 如下:
static?int?devtmpfs_submit_req(struct?req?*req,?const?char?*tmp)
{
?init_completion(&req->done);
?spin_lock(&req_lock);
?req->next?=?requests;
?requests?=?req;
?spin_unlock(&req_lock);
?wake_up_process(thread);
?wait_for_completion(&req->done);
?kfree(tmp);
?return?req->err;
}
int?devtmpfs_create_node(struct?device?*dev)
{
?const?char?*tmp?=?NULL;
?struct?req?req;
?if?(!thread)
??return?0;
?req.mode?=?0;
?req.uid?=?GLOBAL_ROOT_UID;
?req.gid?=?GLOBAL_ROOT_GID;
?req.name?=?device_get_devnode(dev,?&req.mode,?&req.uid,?&req.gid,?&tmp);
?if?(!req.name)
??return?-ENOMEM;
?if?(req.mode?==?0)
??req.mode?=?0600;
?if?(is_blockdev(dev))
??req.mode?|=?S_IFBLK;
?else
??req.mode?|=?S_IFCHR;
?req.dev?=?dev;
?return?devtmpfs_submit_req(&req,?tmp);
}
這個函數(shù)通過 device_get_devnode() 函數(shù)獲得 devtmpfs 設(shè)備文件的文件名,創(chuàng)建一個 devtmpfs 設(shè)備文件創(chuàng)建請求,并提交。在 devtmpfs_submit_req() 函數(shù)中,可以看到所有的請求由單鏈表維護,新的請求被放在單鏈表的頭部。
device_get_devnode() 函數(shù)定義 (位于 drivers/base/core.c) 如下:
const?char?*device_get_devnode(struct?device?*dev,
??????????umode_t?*mode,?kuid_t?*uid,?kgid_t?*gid,
??????????const?char?**tmp)
{
?char?*s;
?*tmp?=?NULL;
?/*?the?device?type?may?provide?a?specific?name?*/
?if?(dev->type?&&?dev->type->devnode)
??*tmp?=?dev->type->devnode(dev,?mode,?uid,?gid);
?if?(*tmp)
??return?*tmp;
?/*?the?class?may?provide?a?specific?name?*/
?if?(dev->class?&&?dev->class->devnode)
??*tmp?=?dev->class->devnode(dev,?mode);
?if?(*tmp)
??return?*tmp;
?/*?return?name?without?allocation,?tmp?==?NULL?*/
?if?(strchr(dev_name(dev),?'!')?==?NULL)
??return?dev_name(dev);
?/*?replace?'!'?in?the?name?with?'/'?*/
?s?=?kstrdup(dev_name(dev),?GFP_KERNEL);
?if?(!s)
??return?NULL;
?strreplace(s,?'!',?'/');
?return?*tmp?=?s;
}
device_get_devnode() 函數(shù)按照一定的優(yōu)先級,嘗試從幾個地方獲得設(shè)備文件名:
- 設(shè)備的設(shè)備類型 struct device_type 的 devnode 操作;設(shè)備的總線 struct class 的 devnode 操作;設(shè)備名字。對于音頻設(shè)備,我們在 sound/sound_core.c 文件中看到,其總線 struct class 的 devnode 操作定義如下:
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));
}
3) snd_unregister_device()
注銷 ALSA 設(shè)備文件
當(dāng)不再需要某個 ALSA 設(shè)備文件時,可以注銷它,這通過 snd_unregister_device() 函數(shù)完成。snd_unregister_device() 函數(shù)定義 (位于 sound/core/sound.c)
int?snd_unregister_device(struct?device?*dev)
{
?int?minor;
?struct?snd_minor?*preg;
?mutex_lock(&sound_mutex);
?for?(minor?=?0;?minor?<?ARRAY_SIZE(snd_minors);?++minor)?{
??preg?=?snd_minors[minor];
??if?(preg?&&?preg->dev?==?dev)?{
???snd_minors[minor]?=?NULL;
???device_del(dev);
???kfree(preg);
???break;
??}
?}
?mutex_unlock(&sound_mutex);
?if?(minor?>=?ARRAY_SIZE(snd_minors))
??return?-ENOENT;
?return?0;
}
EXPORT_SYMBOL(snd_unregister_device);
這個函數(shù)根據(jù)傳入的設(shè)備,查找對應(yīng)的 struct snd_minor 對象,找到時,則從系統(tǒng)中刪除設(shè)備,這包括刪除 devtmpfs 文件系統(tǒng)中的設(shè)備文件等,并釋放 struct snd_minor 對象。
在 snd_register_device() 函數(shù)中可以看到,是給設(shè)備計算了設(shè)備號的,這里不能獲取設(shè)備號,并根據(jù)設(shè)備號在 struct snd_minor 對象指針數(shù)組中快速查找么?
音頻設(shè)備文件的文件操作
注冊音頻字符設(shè)備時,綁定的文件操作是 snd_fops,這個文件操作只定義了 open 和 llseek 兩個操作,其中 llseek 操作 noop_llseek 的定義 (位于 fs/read_write.c) 如下:
loff_t?noop_llseek(struct?file?*file,?loff_t?offset,?int?whence)
{
?return?file->f_pos;
}
EXPORT_SYMBOL(noop_llseek);
這個操作基本上什么也沒做。
open 操作 snd_open 的定義 (位于 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;
?replace_fops(file,?new_fops);
?if?(file->f_op->open)
??err?=?file->f_op->open(inode,?file);
?return?err;
}
這個函數(shù):
- 從 struct inode 中獲得音頻設(shè)備文件的從設(shè)備號;根據(jù)從設(shè)備號,在 struct snd_minor 對象指針數(shù)組中,找到對應(yīng)的 struct snd_minor 對象;從 struct snd_minor 對象獲得它的文件操作,即注冊 ALSA 設(shè)備文件時傳入的文件操作;將 struct file 的文件操作替換為獲得的文件操作;執(zhí)行新的文件操作的 open 操作。
具體詳細的函數(shù),后面驅(qū)動分析章節(jié)會繼續(xù)分析討論。