瑞芯微-I2S|ALSA基础-3

原创 一口Linux 2024-04-19 11:50

点击左上方蓝色“一口Linux”,选择“设为星标

第一时间看干货文章 

【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式知识点-思维导图-免费获取
【就业】一个可以写到简历的基于Linux物联网综合项目
【就业】找工作简历模版



 1

一口君后面会陆续更新基于瑞芯微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

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的软件结构如下:

在这里插入图片描述

用户空间的 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 的目录如下:

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

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设备内核设备结构体,其parentcard_devclasssound_class,主设备号为116;
    last_numid:存储注册snd_control时为其分配的编号;
    controls_rwsem:读写信号量,用于并发操作controls链表;
    ctl_files_rwlock:读写自旋锁,用于并发操作ctl_files链表;
    controls_countcontrols链表的长度;
    user_ctl_count:用户控制设备的数量;
    controls:保存该声卡下所有控件(controls)的链表;该链表中存放的数据类型为struct snd_kcontrol
    ctl_files:用于管理该card下的activecontrol设备;链表中存放的数据类型为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的子类,其parentdevclasssound_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 *moduleint 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 之间的关系,我们绘制了如下关系框图:

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 操作。

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

end



一口Linux 


关注,回复【1024】海量Linux资料赠送

精彩文章合集

文章推荐

【专辑】ARM
【专辑】粉丝问答
【专辑】所有原创
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图

一口Linux 写点代码,写点人生!
评论
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 70浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 86浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 106浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 101浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 41浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 52浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 86浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 71浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 105浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 68浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 44浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦