ALSA子系统

1.ALSA框架

1.1 ALSA概述

ALSA表示先进linux声音架构(Advanced Linux Sound Archiecture),它由一系列的内核驱动、应用程序编程接口(API)以及支持linux下声音的应用程序组成、

ALSA项目发起的原有是linux下的声卡驱动(OSS)没有获得积极的维护,而且落后于新的声卡技术。Jaroslav Kysela早先写了一个声卡驱动,并由此开始了ALSA项目,随后,更多的开发者加入到开发队伍中,更多的声卡获得支持,API的结构也获得了重组。目前已经成为了linux的主流音频体系结构。

1.2 音频硬件概述

1.2.1 Machine

Machine是指某一款机器,可以是某款设备,某款开发板,某款手机,由此可以看出machine几乎是不可以重用的,每个Machine上的硬件实现可能都不一样,可能是soc不一样,也可能是codec不一样,也可能是音频输入输出不同,Machine为cpu、codec、输入输出设备提供了了一个载体。

1.2.2 Platform

platform一般是指某个soc平台,比如mt6985,sm8974等等,和音频相关的通常包括soc时钟,DMA,I2S,PCN,I2C等等,只要指定了soc,那么我们就可以认为它有一个对应的platform,它之和soc相关,这样我们就可以把platform抽象出来,使得同一款soc不用做任何改动就可以在不同的machine上使用,实际上我们可以把platform当作soc更容易理解。

1.2.3 Codec

codec字面意思就是编解码器,codec里面包含了D/A、A/D、AIF、Mixer、PA、Line-out、Line-in,通常包含多种输入(Micphone, input, line-in, I2S, pcm等)和多种输出(Line-out, headset等等),Codec和Platform一样可以被多个Machine使用,嵌入式codec通常使用I2C/SPI对内部的寄存器进行控制,我们可以把codec理解成声卡芯片。

A/D:把mic拾取的模拟信号转化为数字信号

D/A:把音频I2S/PCM送给来的数据转化为模拟信号播放出来

AIF:音频数字接口(DAI),用于soc和codec中间传输数据的

Mixer:混音器,把多路输入信号转混合成单路输出

DRC:动态范围控制

LHPF:高低通滤波

PA:功率放大器

1.3 ALSA架构

ALSA子系统的软件框架

ALSA框架从上到下分别是应用程序、ALSA Library API、ALSA Core、ASoc Core、硬件驱动、硬件设备。

  ▪ 应用程序:tinyplay/tinycap/tinymix,这些应用程序直接调用alsa用户接口来实现放音/录音/控制。

  ▪ ALSA Library API:alsa用户库接口,对应用程序提供统一的API接口,这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度,常见有tinyalsa、alsa-lib;

  ▪ ALSA Core:alsa的核心层,向上提供逻辑设备(pcm,midi,ctrl,timer...)供系统调用,向下驱动硬件设备(machine,i2s,dma,codec)

  ▪ Hardware Driver:音频硬件的设备驱动,分为三部分组成,分别是machine,platform,codec

1.4 ASoC

既然有了ALSA Core为什么又需要ASoC Core呢?其实ASoC Core就是在标准的ALSA Core上做了一层封装,在ASoc子系统问世之前,内核对SoC音频有一些支持但是会有很多限制。

   ▪ 标准的alsa驱动框架中,soc和codec耦合过于紧密,不利于在多样化的平台上移植复用。

   ▪音频事件没有标准的方法来通知用户,例如耳机、麦克风的插拔和检测,这些事件在移动设备        中非常普通的,而且通常都需要特定于机器的代码进行重新对音频路径进行配置

   ▪ 当进行播放或录音时,驱动会让整个Codec处于上电状态,这对于PC没问题,但对于移动设备       来说,这意味着浪费大量的电量。同时也不支持通过改变过取样频率和偏置电流来达到省电的       目的

ASoC正是为了解决上述问题而提出,目前已经被整合到sound/soc目录下

   ▪ 独立的codec驱动,允许把codec驱动放到各个不同的平台和设备上

   ▪ 简单的i2s/pcm音频接口设置,使codec和soc之间的连接便于配置,每个soc接口和codec接口        都会将其音频接口能力和核心进行匹配,这些信息都会在应用程序硬件参数上确定后使用

   ▪ 动态音频管理DAPM,使得codec任何时候都工作在最低功耗的状态,同时负责音频路由的创建

   ▪ pops和click音抑制弱化处理,在asoc中通过正确的音频部件上下电次序来实现

   ▪ machine驱动的特定控制,比如耳机,mic的插拔检测,外放pa的开关

为了实现以上功能,ASoC将音频系统按照硬件做成拆封成多个可重用的组件驱动程序,分别是Machine/Platform/Codec,三者的关系如下:

1.4.1 Machine驱动

Machine Driver描述了如何控制platform、codec、cpu dai(Digital Audio Interface)和codec dai,使得它们配合在一起工作,比如播放声音的时候,通过Soc的DAI将数据发送给codec进行处理,然后由codec输出驱动扬声器。

Machine Driver通过dai_link将cpu_dai和codec_dai各个音频接口连接成一条条音频数据链路,然后注册snd_soc_card。

dai_link:machine driver定义的音频数据链路,它指定链路用到的platform,codec,platform dai,codec dai,这四者构成了一条音频链路,用于多媒体音的播放和录制。

一个系统可能有多个音频数据链路,比如media和voice,因此可以定义多个dai_link。如 WM8994 的典型设计,有三个 dai_link,分别是:

   ▪ AP<>AIF1 的 “HIFI”(多媒体声音链路)

   ▪ BP<>AIF2 的 “Voice”(通话语音链路)

   ▪ 以及 BT<>AIF3(蓝牙SCO语音链路)

1.4.2 Platform驱动

Platform主要分为三个部分:

1)cpu dai driver

在嵌入式系统里通常指soc的i2s,pcm总线控制器,负责把音频数据从i2s tx fifo搬运到codec(这是播放的场景,录制的场景相反)。每个cpu dai driver必须提供下列功能:

    ▪ DAI描述信息

    ▪ DAI配置信息

    ▪ PCM描述信息

    ▪ 系统时钟配置

    ▪ 挂起和恢复(可选)

2)dma driver

负责把dma buffer中的数据搬运到i2s fifo中,值得留意的是有的情况下是不需要dma工作的,比如modem直连codec的时候就不需要dma进行搬运,因为modem本身已经把数据送给fifo了,这时候只需要启动codec dai接收数据即可,DMA driver可以参考soc/pxa/pxa2xx-pcm.c;

3)dsp driver

每个dsp driver通常提供以下功能

   ▪ DAPM拓扑;

   ▪ Mixer controls;

   ▪ DMA IO to/from DSP buffers (if applicable);

   ▪ Definition of DSP front end (FE) PCM devices;

 1.4.3 codec驱动

codec driver应该不包含任何特定目标平台或设备的代码,所有特定于平台和设备的代码应该添加到platform和machine driver中,codec driver提供了配置编解码器、FM、modem、BT或者外部DSP,以提供音频捕获和播放功能。每个codec driver需要提供以下功能:

    ▪ codec dai和pcm的配置信息

    ▪ codec的控制接口,如i2c/spi

    ▪ mixer和其他音频控件

    ▪ codec的音频操作

    ▪ DAPM描述信息

    ▪ DAPM事件处理程序

有些情况codec还可以提供

    ▪DAC数字静音控制

1.5 目录结构

1.5.1 源码结构

linux内核将alsa驱动相关的代码放在sound/目录下

其中:

    ▪ i2s:存放的是alsa自己的i2s控制代码

    ▪ i2c:存放的是alsa自己的i2c控制代码

    ▪ core:如上述"ALSA子系统软件框架"中所说的ALSA Core,是alsa驱动的中间层,也是整个                        alsa驱动的核心层

    ▪ drivers:存放一些与cpu、bus无关的公共代码

    ▪ pci:pci声卡的顶层目录,子目录包含各种pci声卡的代码

    ▪ isa:isa声卡的顶层目录,子目录包含各种isa声卡的代码;

    ▪ soc:针对ASoC体系的中间层代码

    ▪ soc/codec:针对SoC体系的各种codec代码和平台无关

ALSA API可以分解成以下几个接口:

    ▪ 声卡和设备管理接口,提供声卡注册和请求可用设备的通用接口

    ▪ PCM接口:管理数字音频的播放和录制的接口

    ▪ Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器,这些          API提供对声卡上MIDI的访问

    ▪ Proc信息接口(Proc Info API)

    ▪ 定时器(timer)接口,为同步音频事件提供对声卡上时间处理硬件的访问

    ▪ 混音器(mixer)接口

1.5.2 ALSA设备文件

可以看到音频设备文件结构,可以看到这些字符设备的主设备号都是116

root@zhengyang:~# ll /dev/snd
crw-rw----+  1 root audio 116,  6 Jul  6 18:39 controlC0
crw-rw----+  1 root audio 116,  5 Jul  6 18:39 midiC0D0
crw-rw----+  1 root audio 116,  3 Jul  6 18:39 pcmC0D0c
crw-rw----+  1 root audio 116,  2 Jul  6 18:39 pcmC0D0p
crw-rw----+  1 root audio 116,  4 Jul  6 18:39 pcmC0D1p
crw-rw----+  1 root audio 116,  1 Jul  6 18:39 seq
crw-rw----+  1 root audio 116, 33 Jul  6 18:39 timer

controlC0:用于声卡的控制,例如通道选择,混音,mic控制,音量加减,开关等

midiC0D0:用于播放midi音频

pcmC0D0c:用于录音的设备

pcmC0D0p:用于播放的设备

seq:音序器

timer:定时器  

C0D0代表声卡0中的设备0,pcmC0D0c中最后一个字符”c“代表capture,表示用于录音的设备,相应的”p“就代表playback,表示用于播放的设备。从上面的列表可以看出声卡0上挂载了5个设备,根据声卡的实际挂载能力,驱动其实可以挂载更多的设备,我们一般比较关心control和pcm设备,默认一个声卡挂载一个control设备。

2.ALSA核心数据结构

由于ASoC是建立在标准ALSA CORE上的一套软件体系,所以咱们重点介绍ALSA Core中的数据结构,并不涉及ASoC中的数据结构。ALSA Core中的数据结构大多都在include/sound/core.h以及sound/core中的文件中

2.1 struct snd_card

linux内核使用snd_card来表示ALSA音频驱动中的声卡设备,snd_card可以说是整个ALSA音频驱动最顶层的一个数据结构,比如我们就会为我们使用的声卡设备AL5651分配一个struct  snd_card数据结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card结构体;数据结构定义在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 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;

#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
};

   ▪ num:设备号

   ▪ id:声卡设备标识符

   ▪ driver:驱动名称

   ▪ shortname:设备简称,一般用于打印

   ▪ longname:设备全程,一般会在具体驱动中设置,主要反映在proc/asound/cards中

   ▪ irqdescr:中断描述信息

   ▪ mixer:混音器名曾

   ▪ components:声卡组件名称,由空格分隔

   ▪ module:顶层模块;

   ▪ private_data:声卡的私有数据;

   ▪ private_free:释放私有数据的回调函数

   ▪ devices:保存该声卡下所有逻辑设备的链表,链表中存放的数据类型为struct snd_device

   ▪ ctl_dev:声卡Control设备内核设备结构体,其parent为card_dev,class为sound_class,主设       备号为116

   ▪ last_numid:存储注册snd_control时为其分配的编号;

   ▪ controls_rwsem:读写信号量,用于并发操作controls链表;

   ▪ ctl_files_rwlock:读写自旋锁,用于并发操作ctl_files链表

   ▪ controls_count:controls链表的长度;

   ▪ user_ctl_count:用户控制设备的数量;

   ▪ controls:保存该声卡下所有控件(controls)的链表,该链表中存放的数据类型为struct              snd_kcontrol

   ▪ ctl_files:用于管理该card下的active的control设备,链表中存放的数据类型为                        struct snd_ctl_file;

   ▪ proc_root:声卡设备在proc文件系统的根目录;即/proc/asound/card%d目录

   ▪ proc_root_link:指向/proc/asound/card%d的链接文件,文件名为id

   ▪ files_list:保存此声卡相关的所有文件的链表;链表中存放的数据类型为struct       snd_monitor_file;

   ▪ s_f_ops:关机状态下的文件操作

   ▪ files_lock:自旋锁

   ▪ shutdown:此声卡正在关闭

   ▪ release_completion:释放完成

   ▪ dev:分配给此声卡的设备,一般为平台设备的device

   ▪ card_dev:声卡设备的内核设备结构体,card用于在sys中显示,用于代表该card;把snd_card看做是device的子类,其parent为dev,class为sound_class,未设置设备号devt(默认就是0)

   ▪ dev_groups:分配的sysfs属性组

   ▪ registered:声卡设备card_dev是否已注册

   ▪ remove_sleep:等待队列头

   ▪ power_state:电源状态

   ▪ power_sleep:电源等待队列头

每一个声卡设备都会由snd_card_new函数实现,声卡设备注册后都会被添加到snd_cards指针数组上中;

static struct snd_card *snd_cards[SNDRV_CARDS];

在终端的proc/asound目录下可以看到card0设备 

holi:/proc/asound # ls
card0  cards  devices  holiqrdsku1sndc  hwdep  pcm  timers  version

2.2 struct snd_device

声卡设备一般包含很多功能模块,例如PCM(录音和播放)、control(声卡控制),因此ALSA将声卡将功能模块又抽象成逻辑设备,与之对用的数据结构就是struct snd_device,定义在include/sound/​​​​​​core.h

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:用于构建双向链表节点,该节点会添加到snd_card中的devices链表中

    ▪ card:表示当前逻辑设备属于哪个声卡设备

    ▪ state:表示该逻辑设备的状态

    ▪ type:表示该逻辑设备的类型,比如PCM,Control

    ▪ device_data:一般用于存放具体的功能模块的逻辑设备的结构,比如pcm设备就是snd_pcm实例

    ▪ ops:声卡逻辑设备的操作集

每一个声卡逻辑设备的创建都是由snd_device_new()来创建一个snd_device实例,并把该实例连接到snd_card的链表中,通常linux内核提供了一些常用的功能模块逻辑设备的创建函数,而不必调用snd_device_new(),例如:snd_pcm_new()、snd_ctl_create()。

在终端设备中snd_device体现在dev/snd目录下

holi:/dev/snd # ls
comprC0D11  comprC0D7  pcmC0D10c  pcmC0D15p  pcmC0D20c  pcmC0D2c   pcmC0D33p  pcmC0D38c  pcmC0D43c  pcmC0D9c
comprC0D24  controlC0  pcmC0D12c  pcmC0D16c  pcmC0D21c  pcmC0D2p   pcmC0D34c  pcmC0D39p  pcmC0D44c  pcmC0D9p
comprC0D25  hwC0D1000  pcmC0D12p  pcmC0D17c  pcmC0D22c  pcmC0D30c  pcmC0D34p  pcmC0D3c   pcmC0D4p   timer
comprC0D26  hwC0D3013  pcmC0D13c  pcmC0D18c  pcmC0D23c  pcmC0D30p  pcmC0D35c  pcmC0D3p   pcmC0D5p
comprC0D27  hwC0D3029  pcmC0D13p  pcmC0D19c  pcmC0D23p  pcmC0D31c  pcmC0D35p  pcmC0D40p  pcmC0D6c
comprC0D28  pcmC0D0c   pcmC0D14p  pcmC0D1c   pcmC0D29c  pcmC0D32c  pcmC0D37c  pcmC0D41c  pcmC0D8c
comprC0D36  pcmC0D0p   pcmC0D15c  pcmC0D1p   pcmC0D29p  pcmC0D33c  pcmC0D37p  pcmC0D42p  pcmC0D8p

2.2.1 struct snd_device_ops

linux中用snd_device_ops来代表逻辑设备的操作集,定义在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:声卡逻辑设备释放函数,在卸载声卡设备的时候调用

    ▪ dev_register:声卡逻辑设备注册函数,在声卡设备注册的时候调用

    ▪ dev_disconnect:声卡逻辑设备断开函数,在关闭声卡设备的时候调用

2.2.2 enum snd_device_state

linux用snd_device_state来描述声卡逻辑设备的状态,定义在include/sound/core.h

enum snd_device_state {
	SNDRV_DEV_BUILD,        //构建中
	SNDRV_DEV_REGISTERED,   //已经准备并准备就绪
	SNDRV_DEV_DISCONNECTED, //已经断开连接
};

2.2.3 enum snd_device_type

linux用snd_device_type来描述声卡逻辑设备的类型,定义在include/sound/core.h

enum snd_device_type {
	SNDRV_DEV_LOWLEVEL,    //低级别硬件访问接口
	SNDRV_DEV_INFO,    //信息查询接口
	SNDRV_DEV_BUS,     //总线接口,如usb、pcm等
	SNDRV_DEV_CODEC,    //编解码器设备
	SNDRV_DEV_PCM,    //pcm设备包括输入输出和混音器等
	SNDRV_DEV_COMPRESS,    //压缩和解压缩设备
	SNDRV_DEV_RAWMIDI,    //原始MIDI设备
	SNDRV_DEV_TIMER,    //定时器设备
	SNDRV_DEV_SEQUENCER,    //音序器设备
	SNDRV_DEV_HWDEP,    //硬件依赖设备
	SNDRV_DEV_JACK,    //jack音频连接设备
	SNDRV_DEV_CONTROL,	//control设备 /* NOTE: this must be the last one */
};

2.3 struct snd_minor

linux使用snd_minor来表示声卡逻辑设备的上下文信息,它在调用snd_register_device注册逻辑设备的时候被初始化,在声卡逻辑设备被使用时就可以得到相应的信息,定义在include/sound/core.h

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:设备类型,取值为 SNDRV_DEVICE_TYPE_XXX

    ▪ card:声卡逻辑设备所属的声卡设备的编号;

    ▪ device:设备索引;

    ▪ f_ops:文件操作集。

    ▪ private_data:用户提供给 f_ops->open函数的私有数据指针;

    ▪ dev:声卡逻辑设备对应的 struct device 结构体指针;

    ▪ card_ptr:指向所属声卡设备;

每一个snd_minor的创建都是通过snd_register_device函数实现的,并被添加到全局snd_minors指针数组中;

sound/core/sound.c

static struct snd_minor *snd_minors[SNDRV_OS_MINORS];

2.4 数据结构关系

3.声卡的创建和注册

3.1 创建声卡设备

linux内核提供了snd_card_new函数创建和初始化snd_card数据结构,函数定义在sound/core/init.c

/**
 *  snd_card_new - create and initialize a soundcard structure
 *  @parent: the parent device object
 *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
 *  @xid: card identification (ASCII string)
 *  @module: top level module for locking
 *  @extra_size: allocate this extra size after the main soundcard structure
 *  @card_ret: the pointer to store the created card instance
 *
 *  Creates and initializes a soundcard structure.
 *
 *  The function allocates snd_card instance via kzalloc with the given
 *  space for the driver to use freely.  The allocated struct is stored
 *  in the given card_ret pointer.
 *
 *  Return: Zero if successful or a negative error code.
 */
int snd_card_new(struct device *parent, int idx, const char *xid,
                    struct module *module, int extra_size,
                    struct snd_card **card_ret)
{
        struct snd_card *card;
        int err;

        if (snd_BUG_ON(!card_ret))
                return -EINVAL;
        *card_ret = NULL;

        if (extra_size < 0)
                extra_size = 0;
        card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);      // 动态分配snd_crad数据结构,以及额外空间
        if (!card)
                return -ENOMEM;
        if (extra_size > 0)
                card->private_data = (char *)card + sizeof(struct snd_card);  // 设置私有数据指向额外分配的空间
        if (xid)
                strlcpy(card->id, xid, sizeof(card->id));    // 设置声卡设备的标识
        err = 0;
        mutex_lock(&snd_card_mutex);              // 互斥锁
        if (idx < 0) /* first check the matching module-name slot */
                idx = get_slot_from_bitmask(idx, module_slot_match, module);  // 用于为声卡设备分配一个索引,索引编号范围为0~SNDRV_CARDS-1
        if (idx < 0) /* if not matched, assign an empty slot */
                idx = get_slot_from_bitmask(idx, check_empty_slot, module);
        if (idx < 0)
                err = -ENODEV;
        else if (idx < snd_ecards_limit) {
                if (test_bit(idx, snd_cards_lock))      // 测试该索引是否已经被使用
                        err = -EBUSY;   /* invalid */
        } else if (idx >= SNDRV_CARDS)                   // 大于最大声卡数量
                err = -ENODEV;
        if (err < 0) {
                mutex_unlock(&snd_card_mutex);
                dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n",
                         idx, snd_ecards_limit - 1, err);
                kfree(card);
                return err;
        }
        set_bit(idx, snd_cards_lock);           /* lock it 将指定索引idx对应的位位置1 */
        if (idx >= snd_ecards_limit)
                snd_ecards_limit = idx + 1;     /* increase the limit */
        mutex_unlock(&snd_card_mutex);          // 释放互斥锁
        card->dev = parent;                     // 初始化成员变量
        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);      // 初始化双向链表头节点
#ifdef CONFIG_PM
        init_waitqueue_head(&card->power_sleep);
#endif
        init_waitqueue_head(&card->remove_sleep);  // 初始化等待队列头

        device_initialize(&card->card_dev);        // 设备初始化
        card->card_dev.parent = parent;             //   设置设备成员变量
        card->card_dev.class = sound_class;
        card->card_dev.release = release_card_device;
        card->card_dev.groups = card->dev_groups;
        card->dev_groups[0] = &card_dev_attr_group;
        err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);  // 设置名称card%d
        if (err < 0)
                goto __error;

        snprintf(card->irq_descr, sizeof(card->irq_descr), "%s:%s",  // 设置中断描述符
                 dev_driver_string(card->dev), dev_name(&card->card_dev));

        /* the control interface cannot be accessed from the user space until */
        /* snd_cards_bitmask and snd_cards are set with snd_card_register */
        err = snd_ctl_create(card);   // 创建control设备
        if (err < 0) {
                dev_err(parent, "unable to register control minors\n");
                goto __error;
        }
        err = snd_info_card_create(card);  // 创建声卡的proc文件
        if (err < 0) {
                dev_err(parent, "unable to create card info\n");
                goto __error_ctl;
        }
        *card_ret = card;  // 保存声卡设备
        return 0;

      __error_ctl:
        snd_device_free_all(card);
      __error:
        put_device(&card->card_dev);
        return err;
}

函数入参:

    ▪ parent:父设备对象,一般为platform设备

    ▪ idx:声卡的索引

    ▪ xid:声卡设备的标识

    ▪ module:用于锁定的顶层模块

    ▪ extra_size:分配给驱动程序额外使用的空间大小,该空间用于存储声卡的私有数据private_data

    ▪ card_set:指向存储创建的snd_card实例的指针

3.1.1 snd_cards_lock

/* locked for registering/using */
36static DECLARE_BITMAP(snd_cards_lock, SNDRV_CARDS);
37static struct snd_card *snd_cards[SNDRV_CARDS];

该位图用于跟踪哪些声卡已经被其他驱动程序使用,以免发生冲突,声明时采用了 DECLARE_BITMAP 宏,这意味着变量会被分配到内核代码段中。除此之外,还需要指定该位图的大小,这里的大小为 SNDRV_CARDS(默认为8),即支持的声卡的最大数量。比如:snd_card_lock第6位被设置为1,就表示id为6的声卡设备已经被分配了,对应的声卡设备存储在snd_cards指针数组中,每一个元素指向一个snd_crad。

3.1.2 snd_ecards_limit

snd_ecards_limit定义在sound/core/sound.c

/* this one holds the actual max. card number currently available.
 * as default, it's identical with cards_limit option.  when more
 * modules are loaded manually, this limit number increases, too.
 */
int snd_ecards_limit;
EXPORT_SYMBOL(snd_ecards_limit);

 这个变量是用来跟踪已经分配给各个驱动程序的声卡设备编号,以便在创建新的声卡设备时分配未被使用的编号

3.1.3 sound_class

sound_class是在init_soundcore函数中进行的初始化,位于sound/sound_core.c

struct class *sound_class;
EXPORT_SYMBOL(sound_class);

static int __init init_soundcore(void)
{
	int rc;

	rc = init_oss_soundcore();
	if (rc)
		return rc;

	sound_class = class_create(THIS_MODULE, "sound");  //会在/sys/class/目录下创建一个新的文件夹,/sys/class/sound;
	if (IS_ERR(sound_class)) {
		cleanup_oss_soundcore();
		return PTR_ERR(sound_class);
	}

	sound_class->devnode = sound_devnode;

	return 0;
}

 可以看到在init_soundcore中创建了一个sound_class,名字是"sound",然后在下面将sound_class->devnode指向sound_devnode

static char *sound_devnode(struct device *dev, umode_t *mode)
{
	if (MAJOR(dev->devt) == SOUND_MAJOR) // 如果主设备号为14,就不会创建设备节点
		return NULL;
	return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev)); //设置dev下设备节点名称  /dev/snd/xxx
}

那么udev就会根据devnode的返回值来决定创建的设备节点文件的相对路径。同时,udev还会为这些设备节点文件设置相应的权限、所属用户和组等信息,以确保用户可以正确访问这些设备节点文件。

3.1.4 创建声卡proc文件

snd_info_card_create函数会在proc文件系统下/proc/asound目录下创建card%d文件夹,函数定义在sound/core/info.c

/*
 * create a card proc file
 * called from init.c
 */
int snd_info_card_create(struct snd_card *card)
{
        char str[8];
        struct snd_info_entry *entry;

        if (snd_BUG_ON(!card))
                return -ENXIO;

        sprintf(str, "card%i", card->number);    
        entry = create_subdir(card->module, str);     // 创建crd%d目录
        if (!entry)
                return -ENOMEM;
        card->proc_root = entry;  // 保存card entry

        return snd_card_ro_proc_new(card, "id", card, snd_card_id_read); // 在card%d目录下下创建id文件,并设置文件的read函数(返回card->id)
}

snd_info_card_create函数接受一个指向struct snd_card 结构体的指针作为参数,用于表示需要创建proc文件的声卡设备。该函数:

    ▪ 通过sprintf 函数生成一个字符数组,命名为"card%d",其中%d为该声卡设备的编号;

    ▪ 使用create_subdir 函数在/prco/asound目录下创建一个名为"card%d"的文件夹,并返回对应的snd_info_entry结构体指针。如果创建失败,则函数返回 -ENOMEM 错误码;

    ▪ 调用snd_card_ro_proc_new函数在/proc/asound/card%d目录下创建一个名为 "id" 的RO 类型 proc 文件,并设置id文件的read回调函数为snd_card_id_read;

3.2 注册声卡设备

snd_card声卡设备是通过snd_card_register函数来完成的,函数定义在sound/core/init.c

/**
 *  snd_card_register - register the soundcard
 *  @card: soundcard structure
 *
 *  This function registers all the devices assigned to the soundcard.
 *  Until calling this, the ALSA control interface is blocked from the
 *  external accesses.  Thus, you should call this function at the end
 *  of the initialization of the card.
 *
 *  Return: Zero otherwise a negative error code if the registration failed.
 */
int snd_card_register(struct snd_card *card)
{
        int err;

        if (snd_BUG_ON(!card))
                return -EINVAL;

        if (!card->registered) {     //  如果没有注册,则将card_dev注册到内核
                err = device_add(&card->card_dev);
                if (err < 0)
                        return err;
                card->registered = true;  // 设备标志位
        }

        if ((err = snd_device_register_all(card)) < 0)  // 遍历devices链表,注册声卡所有的逻辑设备
                return err;
        mutex_lock(&snd_card_mutex);
        if (snd_cards[card->number]) {    // 由于声卡设备未注册,所以这里为NULL
                /* already registered */
                mutex_unlock(&snd_card_mutex);
                return snd_info_card_register(card); /* register pending info */
        }
        if (*card->id) {    // 如果设置了声卡设备的标识
                /* make a unique id name from the given string */
                char tmpid[sizeof(card->id)];
                memcpy(tmpid, card->id, sizeof(card->id));
                snd_card_set_id_no_lock(card, tmpid, tmpid);
        } else {
                /* create an id from either shortname or longname */
                const char *src;
                src = *card->shortname ? card->shortname : card->longname;
                snd_card_set_id_no_lock(card, src,
                                        retrieve_id_from_card_name(src));
        }
        snd_cards[card->number] = card;     // 保存声卡设备
        mutex_unlock(&snd_card_mutex);
        err = snd_info_card_register(card);
        if (err < 0)
                return err;

#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
        if (snd_mixer_oss_notify_callback)
                snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
        return 0;
}

主要完成了以下操作:

    ▪ 调用device_add将声卡设备card_dev注册到linux内核系统的设备驱动程序模型;会在/sys/class/sound类文件下创建card%d链接文件,但是由于未设置设备号devt(默认就是0),不会在文件系统创建设备节点/dev/snd/card%d;

    ▪ 调用snd_device_register_all注册所有的逻辑设备;

    ▪ 将当前的声卡设备保存到指针数组snd_cards中;

    ▪ 调用snd_info_card_register注册声卡设备的proc文件;在/proc/asound/下创建card%d的链接文件,文件名为声卡设备的标识id;

在proc/asound/目录下可以看到holiqrdsku1sndc指向了声卡设备在proc文件系统的根目录proc/asound/card0

holi:/proc/asound # ls -l
total 0
dr-xr-xr-x 5 root root 0 1970-01-08 06:48 card0
-r--r--r-- 1 root root 0 2024-08-24 15:40 cards
-r--r--r-- 1 root root 0 2024-08-24 15:40 devices
lrwxrwxrwx 1 root root 5 2024-08-24 15:40 holiqrdsku1sndc -> card0
-r--r--r-- 1 root root 0 2024-08-24 15:40 hwdep
-r--r--r-- 1 root root 0 2024-08-24 15:40 pcm
-r--r--r-- 1 root root 0 2024-08-24 15:40 timers
-r--r--r-- 1 root root 0 2024-08-24 15:40 version

4. 声卡逻辑设备的创建和注册

我们知道声卡设备一般包含许多功能模块,比如PCM(录音和播放)、Control(声卡控制),因此ALSA将声卡的功能模块又抽象为一个逻辑设备,这个逻辑设备在ALSA中使用struct snd_device数据结构表示。

ALSA为我们提供了snd_device_new函数可以为声卡逻辑设备分配一个snd_device数据结构,并将该实例链接到 snd_card 的 devices 链表中,同时也提供了逻辑设备的注册函数snd_register_device。

根据声卡功能模块的种类不同,ALSA封装了一些常用的部件的创建函数,而不必直接调用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()

4.1 创建声卡逻辑设备

snd_device_new用来创建一个snd_device实例,并将实例注册到声卡设备snd_card中的逻辑设备链表devices中,函数定义在sound/core/device.c

/**
 * snd_device_new - create an ALSA device component
 * @card: the card instance
 * @type: the device type, SNDRV_DEV_XXX
 * @device_data: the data pointer of this device
 * @ops: the operator table
 *
 * Creates a new device component for the given data pointer.
 * The device will be assigned to the card and managed together
 * by the card.
 *
 * The data pointer plays a role as the identifier, too, so the
 * pointer address must be unique and unchanged.
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_device_new(struct snd_card *card, enum snd_device_type type,
                   void *device_data, struct snd_device_ops *ops)
{
        struct snd_device *dev;
        struct list_head *p;

        if (snd_BUG_ON(!card || !device_data || !ops))
                return -ENXIO;
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);  // 动态分配snd_device数据结构
        if (!dev)
                return -ENOMEM;
        INIT_LIST_HEAD(&dev->list);      // 初始化链表节点
        dev->card = card;                // 设置所属的声卡设备 
        dev->type = type;                // 设置类型
        dev->state = SNDRV_DEV_BUILD;    // 设置状态  
        dev->device_data = device_data;  // 设置额外数据
        dev->ops = ops;                  // 设置操作集  

        /* insert the entry in an incrementally sorted list */
        list_for_each_prev(p, &card->devices) {   // 遍历声卡设备的逻辑设备链表devices,将dev插入到适当的位置,保证链表按照设备类型的顺序排列
                struct snd_device *pdev = list_entry(p, struct snd_device, list);
                if ((unsigned int)pdev->type <= (unsigned int)type)
                        break;
        }

        list_add(&dev->list, p);  // 在p节点后插入dev_list节点
        return 0;
}

该函数有四个入参:

    ▪ card:为声卡硬件设备所分配的snd_card数据结构

    ▪ type:表示该设备的类型,可以是 SNDRV_DEV_XXX 几种类型之一

    ▪ device_data: 是一个指针,允许开发者传入设备使用的额外数据

    ▪ ops:是一个 struct snd_device_ops结构体指针,其中包含了该设备使用的回调函数;

4.2 注册声卡逻辑设备

snd_register_device函数用于注册一个声卡设备,该函数sound/core/sound.c,接受六个参数:

    ▪ type:设备类型,比如control设备该参数设置为SNDRV_DEVICE_TYPE_CONTROL、pcm设备该参数设置为SNDRV_DEVICE_TYPE_PCM_PLAYBACK,SNDRV_DEVICE_TYPE_PCM_CAPTURE;

    ▪ card:声卡设备所属的snd_card结构体指针;

    ▪ dev:设备逻辑设备的编号,大多数的情况为0;

    ▪ f_ops:文件操作集指针,比如control设备该参数设置为snd_ctl_f_ops,pcm设备为snd_pcm_f_ops;

    ▪ private_data:用户提供给f_ops->open()函数的私有数据指针;比如pcm设备该参数设置为snd_pcm实例

    ▪ device:声卡逻辑设备对应的struct device结构体指针;比如pcm设备该参数设置为&pcm->streams[i].dev;

函数定义在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);   // 动态分配struct snd_minor数据结构
        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     
        device->devt = MKDEV(major, minor);     // 设置设备号 主设备号是静态变量,值为116
        err = device_add(device);               // 将声卡逻辑设备的device注册到linux内核系统的设备驱动程序模型,
                                                // 以control设备为例,会在/sys/class/sound类文件下创建controlCxx链接文件,同时会在文件系统创建设备节点/dev/snd/controlCxx;
                                                // 以pcm设备为例,会在/sys/class/sound类文件下创建pcmCxxDxxp或pcmCxxDxxc链接文件,同时会在文件系统创建设备节点/dev/snd/pcmCxxDxxp或pcmCxxDxxc;

        if (err < 0)
                goto error;

        snd_minors[minor] = preg;    // 保存到全局变量中
 error:
        mutex_unlock(&sound_mutex);  // 释放互斥锁
        if (err < 0)
                kfree(preg);
        return err;
}

    ▪ 首先通过kmalloc函数申请了一块内存来存放snd_minor结构体,然后将所有相关信息填充到该结构体中,包括设备类型、所属声卡设备的编号、设备索引、文件操作集、私有数据指针、以及设备对应的 struct device 结构体指针等;

    ▪ 函数调用snd_find_free_minor函数获取一个可用的次设备号并分配给该设备;

    ▪ 调用device_add将声卡逻辑设备的device注册到linux内核系统的设备驱动程序模型,以control设备为例,会在/sys/class/sound类文件下创建controlC%d链接文件,同时会在文件系统创建设备节点/dev/snd/controlC%d;

    ▪ 函数将snd_minor信息存储到全局变量snd_minors的对应索引处;

4.2.1 字符设备驱动

这里我们需要说一下音频字符设备驱动"alsa"的注册,其在alsa_sound_init函数中完成的,函数位于sound/core/sound.c

/*
 *  INIT PART
 */

static int __init alsa_sound_init(void)
{
        snd_major = major;
        snd_ecards_limit = cards_limit;
        if (register_chrdev(major, "alsa", &snd_fops)) {
                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;
        }

#ifdef CONFIG_SND_DEBUG
        sound_debugfs_root = debugfs_create_dir("sound", NULL);
#endif
#ifndef MODULE
        pr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endif
        return 0;
}

函数内部调用register_chrdev完成字符设备驱动程序的注册,主设备号设置为major,即116,所有次设备共用一个操作集为snd_fops。

static const struct file_operations snd_fops =
{
        .owner =        THIS_MODULE,
        .open =         snd_open,
        .llseek =       noop_llseek,
};
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];        // 获取到具体的声卡逻辑设备上下文,即比如control、pcm设备等
        if (mptr == NULL) {
                mptr = autoload_device(minor);
                if (!mptr) {
                        mutex_unlock(&sound_mutex);
                        return -ENODEV;
                }
        }
        new_fops = fops_get(mptr->f_ops);  // 获取mptr的f_ops文件结构体
        mutex_unlock(&sound_mutex);
        if (!new_fops)
                return -ENODEV;
        replace_fops(file, new_fops);       // 用new_fops替换file->f_op

        if (file->f_op->open)
                err = file->f_op->open(inode, file);  // 执行该次设备的文件open函数
        return err;
}

如上述注释所述,在snd_open函数中利用次设备号根据全局数组snd_minors找到相应的snd_minor数据结构,然后从snd_minor结构中取出文件操作集f_ops,并且把file->f_op替换为snd_minor的f_ops,紧接着直接调用file的f_ops->open,然后返回。因为file->f_op已经被替换,以后应用程序的所有read/write/ioctl调用都会进入声卡逻辑设备自己的回调函数中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值