瑞芯微-I2S | ALSA基础

瑞芯微-I2S | ALSA基础一口君后面会陆续更新基于瑞芯微 rk3568 的 I2S 系列文章 预计 10 篇左右 有对语音感兴趣的朋友 可以收藏该专题

大家好,欢迎来到IT知识分享网。

一口君后面会陆续更新基于瑞芯微rk3568的I2S系列文章。

预计10篇左右。有对语音感兴趣的朋友,可以收藏该专题。

瑞芯微 | I2S-音频基础 -1

《瑞芯微-I2S | 音频驱动调试基本命令和工具-基于rk3568-2》

针对音频设备,linux内核中包含了两类音频设备驱动框架;

  • OSS:开放声音系统包含dsp和mixer字符设备接口,应用访问底层硬件是直接通过sound设备节点实现的;
  • ALSA:先进linux声音架构(Advanced Linux Sound Archiecture)以card和组件(PCM、mixer等)为组件,应用是通过ALSA提供的alsa-lib库访问底层硬件的操作,不再访问sound设备节点了

1.ALSA概述

ALSA由一系列的内核驱动、应用程序编程接口(API)以及支持linux下声音的应用程序组成、

ALSA项目发起的原因是linux下的声卡驱动(OSS)没有获得积极的维护,而且落后于新的声卡技术。

Jaroslav Kysela早先写了一个声卡驱动,并由此开始了ALSA项目,随后,更多的开发者加入到开发队伍中,更多的声卡获得支持,API的结构也获得了重组。目前已经成为了linux的主流音频体系结构。

ALSA的官网:

https://www.alsa-project.org/wiki/Main_Page 
瑞芯微-I2S | ALSA基础

2. ALSA组件

ALSA系统包括:

  • 1、alsa-driver:alsa系统驱动。
  • 2、alsa-lib:alsa库,用户空间调用,和内核空间交互。
  • 3、alsa-utils:命令行工具。
  • 4、alsa-plugin:alsa插件。
  • 5、alsa-tools:alsa工具。

在应用层,ALSA 为我们提供了 alsa-lib,在 Linux 内核设备驱动层,ALSA 提供了 alsa-driver

Linux 应用程序只需要调用 alsa-lib 提供的 API,即可完成对底层音频硬件的控制。

linux内核中alsa的软件结构如下:

瑞芯微-I2S | ALSA基础

在这里插入图片描述

用户空间的 alsa-lib 对应用程序提供统一的 API 接口,隐藏了驱动层的实际细节,简化了应用程序的实现难度

但是由于 alsa-lib 也由于过大,因此在 android 等下也经常使用 tiny-alsa

如上图所示,在 Linux 内核中,有对 alsa-driver 进一步的封装,即 alsa-soc

ALSA框架从上到下依次为应用程序、ALSA Library API、ALSA CORE、ASoC CORE、硬件驱动程序、硬件设备

  • 应用程序:tinyplay/tinycap/tinymix,这些用户程序直接调用alsa用户库接口来实现放音、录音、控制;
  • ALSA Library API:alsa用户库接口,对应用程序提供统一的API接口,这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度;常见有tinyalsa、alsa-lib;
  • ALSA CORE:alsa核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC);
  • ASoC CORE:建立在标准ALSA CORE基础上,为了更好支持嵌入式系统和应用于移动设备的音频Codec的一套软件体系,它将音频硬件设备驱动划分为Codec、Platform 和 Machine;
  • Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec;

3. ALSA 设备文件

Linux 系统下看到的设备文件结构如下:

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 

从上面能看到有如下设备文件:

controlC0 --> 用于声卡的控制,例如通道选择,混音,麦克控制,音量加减,开关等 controlC1 pcmC0D0c --> 用于录音的pcm设备 pcmC0D0p --> 用于播放的pcm设备 pcmC1D0p timer --> 定时器 有的平台还有以下设备节点: midiC0D0 --> 用于播放midi音频 seq --> 音序器 

其中,C0、D0代表的是声卡0中的设备0

pcmC0D0c最后一个c代表capture

pcmC0D0p最后一个p代表playback

这些都是alsa-driver中的命名规则,从上面的列表可以看出,声卡下挂了6个设备

根据声卡的实际能力,驱动实际上可以挂载更多种类的设备,在include/sound/core.h 中,定义了以下设备类型,通常更关心的是 pcmcontrol 这两种设备,Default 一个声卡对应一个 Control 设备。

#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 源码目录结构

在 Linux 源码中 ALSA 架构的代码在 /sound 下,Linux 5.0 的目录如下:

瑞芯微-I2S | ALSA基础

其中各主要子目录的作用如下:

core 该目录包含了ALSA 驱动的中间层,它是整个 ALSA 驱动的核心部分 core/oss 包含模拟旧的 OSS 架构的 PCM 和 Mixer 模块 core/seq 有关音序器相关的代码 drivers 放置一些与CPU、BUS架构无关的公用代码 i2c ALSA自己的I2C控制代码 pci pci声卡的顶层目录,子目录包含各种pci声卡的代码 isa isa声卡的顶层目录,子目录包含各种isa声卡的代码 soc 针对 system-on-chip 体系的中间层代码,即ASOC代码 soc/rockchip 瑞芯微平台i2s控制器驱动代码 soc/codecs 针对soc 体系的各种 codec 的代码,与平台无关 include ALSA驱动的公共头文件目录,其路径为 /include/sound,该目录的头文件需要导出给用户空间的应用程序使用,通常,驱动模块私有的头文件不应放置在这里 

更多目录结构信息可以参考:

 https://www.kernel.org/doc/html/latest/sound/kernel-api/writing-an-alsa-driver.html。

5. ALSA核心数据结构

由于ASoC是建立在标准ALSA CORE上的一套软件体系,因此本篇博客重点介绍的都是ALSA CORE中的数据结构,并不涉及到ASoC CORE中的数据结构。

ALSA CORE中的数据结构大部分定义在include/sound/core.h、以及sound/core/目录下的文件中。

1) struct snd_card

linux内核中使用struct snd_card表示ALSA音频驱动中的声卡设备

struct snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,

几乎所有与声音相关的逻辑设备都是在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 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: 声卡设备编号,通常从0开始,通过编号可以在snd_cards指针数组中找到对应的声卡设备; id[16]:声卡设备的标识符; driver:驱动名称; shortname:设备简称,更多地用于打印信息; longname:设备名称,会在具体驱动中设置,主要反映在/proc/asound/cards中; irq_descr:中断描述信息; mixername:混音器名称; 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指针数组中;

@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

声卡设备一般包含许多功能模块,比如PCM(录音和播放)、Control(声卡控制),因此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 */ }; 
 list:用于构建双向链表节点,该节点会添加到声卡设备snd_card的devices链表中; snd_card:表示当前声卡逻辑设备所属的声卡设备; state:表示当前声卡逻辑设备的状态; type:表示当前声卡逻辑设备的类型,比如pcm、control设备; device_data:一般用于存放具体的功能模块逻辑设备的结构,比如对于pcm逻辑设备存放的就是snd_pcm实例; ops:声卡逻辑设备的操作集; 

每一个声卡逻辑设备的创建最终会调用snd_device_new来生成一个snd_device实例,并把该实例链接到snd_card的devices链表中。

通常,linux内核已经提供了一些常用的功能模块逻辑设备的创建函数,而不必直接调用snd_device_new,比如:snd_pcm_new、snd_ctl_create

需要注意的是:声卡逻辑设备注册后会在/dev/snd目录下生成对应的字符设备文件。

3) 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:声卡逻辑设备断开连接函数,在关闭声卡设备时被调用。

4) 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, // 已断开连接 }; 

5) enum snd_device_type

linux内核使用snd_device_state表示声卡逻辑设备的类型

@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:编解码器设备; 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设备,此项必须放在最后; 

6) struct snd_minor

linux内核使用snd_minor表示声卡逻辑设备上下文信息,它在调用snd_register_device函数注册声卡逻辑设备时被初始化,在声卡逻辑设备被使用时就可以从该结构体中得到相应的信息。

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指针数组中;

static struct snd_minor *snd_minors[SNDRV_OS_MINORS]; 

6. alsa数据结构之间关系

为了更加形象的表示struct snd_card、struct snd_device、struct snd_minor 之间的关系,我们绘制了如下关系框图:

瑞芯微-I2S | ALSA基础

7. 内核中alsa几个主要函数

1) snd_register_device()

Linux 内核 ALSA 音频框架的其它部分需要创建音频设备文件时,调用 snd_register_device() 函数为声卡注册 ALSA 设备文件,该函数定义 (位于 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); 

参数说明如下

type: 设备类型 card: 声卡实例 dev: 设备索引 f_ops: 文件操作。用户空间程序对设备文件的各种操作,都将由这里传入的文件操作执行。 private_data:f_ops->open() 要用到的用户指针 device:要注册的设备 

snd_register_device() 函数的执行过程如下:

  1. 分配 struct snd_minor 对象,并初始化它;
  2. 为要注册的设备寻找可用的从设备号。这有两种策略,一种是动态从设备号,另一种是静态从设备号。无论是哪种策略,SEQUENCER 和 TIMER 类型的设备的从设备号都是固定的 1 和 33。其它类型的设备,在动态从设备号策略中,顺序查找可用的从设备号;在静态从设备号策略中,根据声卡索引、设备类型和设备索引构造从设备号;
  3. 构造包含主设备号和从设备号的设备号;
  4. 向设备层次体系结构添加设备;
  5. 将 struct snd_minor 对象指针保存在 struct snd_minor 对象指针数组中。

2) device_add()

snd_register_device() 函数调用 device_add() 函数向设备层次体系结构添加设备,这个函数定义 (位于 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': %s\n", 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() 函数调用 device_create_file() 和 devtmpfs_create_node() 等函数在 sysfs 和 devtmpfs 文件系统中创建文件。

内核各模块通过 devtmpfs_create_node() 函数创建 devtmpfs 文件,这个函数定义 (位于 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); } 

这个函数通过 device_get_devnode() 函数获得 devtmpfs 设备文件的文件名,创建一个 devtmpfs 设备文件创建请求,并提交。在 devtmpfs_submit_req() 函数中,可以看到所有的请求由单链表维护,新的请求被放在单链表的头部。

device_get_devnode() 函数定义 (位于 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() 函数按照一定的优先级,尝试从几个地方获得设备文件名:

  1. 设备的设备类型 struct device_type 的 devnode 操作;
  2. 设备的总线 struct class 的 devnode 操作;
  3. 设备名字。对于音频设备,我们在 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 设备文件 当不再需要某个 ALSA 设备文件时,可以注销它,这通过 snd_unregister_device() 函数完成。snd_unregister_device() 函数定义 (位于 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); 

这个函数根据传入的设备,查找对应的 struct snd_minor 对象,找到时,则从系统中删除设备,这包括删除 devtmpfs 文件系统中的设备文件等,并释放 struct snd_minor 对象。

在 snd_register_device() 函数中可以看到,是给设备计算了设备号的,这里不能获取设备号,并根据设备号在 struct snd_minor 对象指针数组中快速查找么?

音频设备文件的文件操作 注册音频字符设备时,绑定的文件操作是 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; } 

这个函数:

  1. 从 struct inode 中获得音频设备文件的从设备号;
  2. 根据从设备号,在 struct snd_minor 对象指针数组中,找到对应的 struct snd_minor 对象;
  3. 从 struct snd_minor 对象获得它的文件操作,即注册 ALSA 设备文件时传入的文件操作;
  4. 将 struct file 的文件操作替换为获得的文件操作;
  5. 执行新的文件操作的 open 操作。

具体详细的函数,后面驱动分析章节会继续分析讨论。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/162691.html

(0)
上一篇 2024-12-16 17:26
下一篇 2024-12-16 17:33

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信