学习 ALSA 驱动时,首先需要了解其框架中的数据结构。理解了这些数据结构及其成员的含义后,再去看源码就会容易很多。
ASoC 是基于标准 ALSA 核心构建的一套软件体系,所以我们这里主要介绍 ALSA 核心的数据结构,而不涉及 ASoC 核心的数据结构。大部分 ALSA 核心的数据结构定义在 include/sound/core.h 和 sound/core/ 目录下的文件中。
1、struct snd_card
在 Linux 内核中,struct 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
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
- number: 声卡的编号,从 0 开始。通过这个编号可以在声卡数组中找到对应的声卡设备。
- id: 声卡的唯一标识符。
- driver: 驱动程序的名字。
- shortname: 设备的简短名称,主要用于显示信息。
- longname: 设备的全名,在驱动程序中设置,主要显示在
/proc/asound/cards文件中。 - irq_descr: 中断描述信息。
- mixername: 混音器的名称。
- components: 声卡组件的名称,用空格分隔。
- module: 顶层模块的名字。
- private_data: 声卡的私有数据。
- private_free: 释放私有数据的函数。
- devices: 声卡下所有逻辑设备的列表,每个设备是
struct snd_device类型。 - ctl_dev: 控制设备的内核结构体,父设备是
card_dev,类是sound_class,主设备号是 116。 - last_numid: 注册控制接口时分配的编号。
- controls_rwsem: 读写信号量,用于并发操作
controls列表。 - ctl_files_rwlock: 读写自旋锁,用于并发操作
ctl_files列表。 - controls_count: 控制列表的长度。
- user_ctl_count: 用户控制设备的数量。
- controls: 声卡下所有控件的列表,每个控件是
struct snd_kcontrol类型。 - ctl_files: 管理声卡下活跃的控制设备的列表,每个文件是
struct snd_ctl_file类型。 - proc_root: 声卡在
/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: 分配给声卡的设备,通常是平台设备。
- card_dev: 声卡的内核设备结构体,用于在系统中显示。它没有设备号。
- dev_groups: 分配给声卡的 sysfs 属性组。
- registered: 声卡设备是否已注册。
- remove_sleep: 等待队列头,用于等待移除操作。
- power_state: 电源状态。
- power_sleep: 电源等待队列头。
每一个声卡设备的创建都是通过 snd_card_new 函数实现的,声卡设备被注册后都会被添加到全局 snd_cards 指针数组中;
static struct snd_card *snd_cards[SNDRV_CARDS];2、struct snd_device
声卡通常有许多功能模块,比如录音播放(PCM)和声卡控制。为了更好地管理这些功能,ALSA 将每个功能模块视为一个逻辑设备。在代码中,这种逻辑设备用 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 */
};2
3
4
5
6
7
8
在 Linux 系统中,声卡设备由多个逻辑设备组成,每个逻辑设备都有特定的功能。创建这些逻辑设备时,会用到几个关键部分:
- list:用于将新创建的逻辑设备添加到声卡设备(
snd_card)的一个列表中。 - snd_card:代表这个逻辑设备所属的整体声卡。
- state:表示逻辑设备当前的状态。
- type:指明逻辑设备的种类,例如是 PCM 设备还是控制设备。
- device_data:存储具体功能模块的数据,比如对于 PCM 设备,这里保存的就是 PCM 实例。
- ops:定义了对这个逻辑设备可以执行的操作。
创建一个新的声卡逻辑设备时,最终会调用 snd_device_new 函数来生成一个设备实例,并将其加入到所属声卡的设备列表里。不过,通常我们不需要直接使用 snd_device_new,因为 Linux 内核已经提供了更简便的函数来创建常见的逻辑设备类型,如 snd_pcm_new 和 snd_ctl_create 用于创建 PCM 设备和控制设备。
每当成功注册了一个逻辑设备后,在系统的 /dev/snd 目录下就会自动出现对应的字符设备文件,这样用户就可以通过这些文件访问相应的音频功能了。
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);
};2
3
4
5
好的,下面是改写后的内容:
dev_free:这个函数用来释放声卡设备,在卸载声卡时会调用它。dev_register:这个函数用来注册声卡设备,在添加声卡时会调用它。dev_disconnect:这个函数用来断开声卡设备的连接,在关闭声卡时会调用它。
2.2、enum snd_device_stat
Linux 内核用 snd_device_state 来表示声卡逻辑设备的状态,这个定义在 include/sound/core.h 文件里。
enum snd_device_state {
SNDRV_DEV_BUILD, // 构建中
SNDRV_DEV_REGISTERED, // 已经准备并准备就绪
SNDRV_DEV_DISCONNECTED, // 已断开连接
};2
3
4
5
2.3、enum snd_device_type
Linux 内核用 snd_device_state 来表示声卡逻辑设备的状态。这个定义在 include/sound/core.h 文件中。
/* device allocation stuff */
/* type of the object used in snd_device_*()
* this also defines the calling order
*/
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 */
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
好的,这里简单地解释一下这些音频设备接口:
- SNDRV_DEV_LOWLEVEL:直接控制硬件的接口。
- SNDRV_DEV_INFO:用来查询设备信息的接口。
- SNDRV_DEV_BUS:连接不同总线(如 USB、PCI)的接口。
- SNDRV_DEV_CODEC:处理声音编码和解码的设备。
- SNDRV_DEV_PCM:处理音频输入输出和混音的设备。
- SNDRV_DEV_COMPRESS:压缩和解压缩音频数据的设备。
- SNDRV_DEV_RAWMIDI:处理原始 MIDI 信号的设备。
- SNDRV_DEV_TIMER:用于定时功能的设备。
- SNDRV_DEV_SEQUENCER:管理音频序列的设备。
- SNDRV_DEV_HWDEP:依赖特定硬件的设备。
- SNDRV_DEV_JACK:处理音频插孔连接的设备。
- SNDRV_DEV_CONTROL:控制其他设备的接口,必须放在最后。
3、struct snd_minor
在 Linux 内核中,snd_minor 用于存储声卡逻辑设备的信息。当你调用 snd_register_device 函数注册声卡逻辑设备时,snd_minor 会被初始化。之后,当声卡逻辑设备被使用时,你可以从 snd_minor 结构体中获取所需的信息。这个结构体定义在 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 */
};2
3
4
5
6
7
8
9
每个 snd_minor 设备的创建都是通过 snd_register_device 函数完成的,并被添加到全局 snd_minors 数组中。以下是这些设备的一些关键属性:
- type:设备类型,值为
SNDRV_DEVICE_TYPE_XXX。 - card:声卡逻辑设备所属的声卡编号。
- device:设备索引号。
- f_ops:文件操作集。
- private_data:传递给
f_ops->open函数的用户私有数据指针。 - dev:指向声卡逻辑设备的
struct device结构体指针。 - card_ptr:指向所属声卡设备的指针。
这样描述更简洁明了,易于理解。
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];4、数据结构关系
为了更加形象的表示 struct snd_card、struct snd_device、struct snd_minor 之间的关系,我们绘制了如下关系框图:
