Linux ALSA 之五:ALSA Proc Info

一、概述

Linux系统上的 /proc 目录是一种文件系统,即 proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。

基于 /proc 文件系统如上所述的特殊性,其内的文件也常被称作虚拟文件,并具有一些独特的特点。例如,其中有些文件虽然使用查看命令查看时会返回大量信息,但文件本身的大小却会显示为0字节。此外,这些特殊文件中大多数文件的时间及日期属性通常为当前系统时间和日期,这跟它们随时会被刷新(存储于RAM中)有关。

其中 ALSA 有自己的 proc tree,/proc/asound. 在这个目录下可以找到许多关于
nd card 的详细信息。

二、Proc Files of Alsa Driver

该节主要用于讲解在 alsa core 中创建的 /proc/asound 目录树以及如何实现的。(以下截图中显示的 card 详细信息均以 mstar alsa 为例)

1、/proc/asound/xxx 简述

在 /proc/asound 下执行 'ls -l' 可以看到在 sound proc 目录下涉及的信息如下:
在这里插入图片描述
其中主要的节点如下:

  • cardx 本机当前注册的 sound card;
  • cards 显示当前配置的 Alsa Drivers,index,the id string,short and long descriptions;
  • version 显示版本字符串;
  • devices 列举本机设备映射;
  • pcm 列举当前可用的 pcm devides,格式如下:<card>-<device>: <id>: <name> : <sub-streams>

2、创建 /proc/asound 目录树

在 sound.c alsa_sound_init() 中会 call snd_info_init() 函数创建 /proc/asound dir,并将该 entry 保存在全局变量 snd_proc_root(即作为 sound proc root entry),代码如下:

int __init snd_info_init(void)
{
    
    
	//1、创建 alsa proc root entry;
	snd_proc_root = snd_info_create_entry("asound", NULL);	
	if (!snd_proc_root)
		return -ENOMEM;
	snd_proc_root->mode = S_IFDIR | 0555;
	//2、创建 dir: /proc/asound
	snd_proc_root->p = proc_mkdir("asound", NULL);	
	if (!snd_proc_root->p)
		goto error;
#ifdef CONFIG_SND_OSSEMUL
	snd_oss_root = create_subdir(THIS_MODULE, "oss");
	if (!snd_oss_root)
		goto error;
#endif
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
	snd_seq_root = create_subdir(THIS_MODULE, "seq");
	if (!snd_seq_root)
		goto error;
#endif
	if (snd_info_version_init() < 0 ||	//3、创建 file: /proc/asound/version
	    snd_minor_info_init() < 0 ||	//4、创建 file: /proc/asound/devices
	    snd_minor_info_oss_init() < 0 ||
	    snd_card_info_init() < 0 ||		//5、创建 file: /proc/asound/cards
	    snd_info_minor_register() < 0)
		goto error;
	return 0;

 error:
	snd_info_free_entry(snd_proc_root);
	return -ENOMEM;
}

2.1 /proc/asound/version 文件

如上代码所示,在 snd_info_init() 函数中除了创建 /proc/asound dir 时,紧接着在 snd_proc_root entry 下(即以 snd_proc_root enter 作为 parent entry) 创建 “version” 文件(即 /proc/asound/version),并提供了 read() 方法,代码详解略,cat /proc/asound/version 如下:
在这里插入图片描述

2.2 /proc/asound/devices 文件

接着在 snd_proc_root entry 下创建 “devices” 文件(即 /proc/asound/devices),提供了 read() 方法,cat /proc/asound/devices 如下:
在这里插入图片描述
对于 devices 的 print 格式如下:

  • control “minor: [card_id] : control”
  • pcm “minor: [card_id- device_id]: digital audio playback/capture”
  • timer “minor: : timer”

2.3 /proc/asound/cards 文件

接着在 snd_proc_root entry 下创建 “cards” 文件(即 /proc/asound/cards),提供了 read() 方法,cat /proc/asound/cards 如下:
在这里插入图片描述
对于 cards 的 print 格式如下:

card_id [card_id_string	]:	card_driver - card_shortname
							card_longname

2.4 /proc/asound/cardx 目录

我们都知道在 alsa driver 开始的时候需要调用 snd_card_new() 创建声卡 card,与此同时也会调用 sound/core/info.c snd_info_card_create() 函数在 snd_proc_root 下创建 “cardx” dir(即 /proc/asound/cardx),并将对应的 entry 保存在 card->proc_root,以便后面基于该声卡的 snd device 等节点均以其作为 parent entry,snd_info_card_create() 函数如下:

/*
 * 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);	//创建 card%i dir
	if (!entry)
		return -ENOMEM;
	card->proc_root = entry;	//保存 card entry
	return 0;
}

在 cardx dir 下主要会对每个 pcm device 创建对应的 pcmxp/c dir,创建方式如下:
我们都知道在 alsa driver 中创建 pcm device 时都需要调用 snd_pcm_new() 函数,在该函数中也分别会对 Playback & Capture 调用 snd_pcm_new_stream(PLAYBACK/CAPTURE) 定义 playback & capture dev name,然后则会调用 snd_pcm_stream_proc_init() 函数,在该函数中会在 card->proc_root entry 下创建 pcmxp/c dir(即 /proc/asound/cardx/pcmxp | pcmxc),并将对应的 entry 保存在 pcm->streams[PLAYBACK/CAPTURE]->proc_root;同时也会在 pcm->streams[]->proc_root 下创建 info 文件(即 /proc/asound/cardx/pcmxp|c/info),并提供 read() 方法,代码如下:

static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr)
{
    
    
	struct snd_pcm *pcm = pstr->pcm;
	struct snd_info_entry *entry;
	char name[16];

	// 创建 pcmxp/c dir
	sprintf(name, "pcm%i%c", pcm->device, 
		pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
	entry = snd_info_create_card_entry(pcm->card, name,
					   pcm->card->proc_root);
	if (!entry)
		return -ENOMEM;
	entry->mode = S_IFDIR | 0555;
	if (snd_info_register(entry) < 0) {
    
    
		snd_info_free_entry(entry);
		return -ENOMEM;
	}
	pstr->proc_root = entry;

	// 在 pcmxp|c dir 下创建 info 文件
	entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root);
	if (entry) {
    
    
		snd_info_set_text_ops(entry, pstr, snd_pcm_stream_proc_info_read);
		if (snd_info_register(entry) < 0) {
    
    
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	pstr->proc_info_entry = entry;

	return 0;
}

经过上述创建 dir 以及 cat /proc/asound/cardx/pcmxp|c/info 如下所示(其中涉及 pcmxp|c/info read 详细参数看 Code):
在这里插入图片描述
由前面学习知道对于 pcm 播放的最小单元是 substream,故接着在 snd_pcm_new_stream() 会对该 pcm playback/capture stream 下所有的 substreams (substream_count 一般都是 1)调用 snd_pcm_substream_proc_init() 函数,即在 pcm->streams[]->proc root 下创建 subx dir(即 /proc/asound/cardx/pcmxp|c/sub0),并将对应的 entry 保存在 pcm->streams[]->substream[0]->proc_root;同时会在 pcm->streams[]->substream[0]->proc_root 下创建 info,hw_params,sw_params,status 文件(即 /proc/asound/cardx/pcmxp|c/pcmxp|c/sub0/info | hw_params |sw_params | status),并提供 read() 方法,分别执行 cat 后如下:
在这里插入图片描述
**Note:**如上所示,对于 hw_params & sw_params & status 由于均用到了 runtime,故只有在播放的时候才能 print info.

特别地,在 alsa 驱动中当需要为 pcm dma 分配内存 allocate pages 时(即有调用 snd_pcm_lib_preallocate_pages_for_all(size,max))时,则会在 pcm->streams[]->substream[0]->proc_root 下创建 prealloc、prealloc_max 文件(即 /proc/asound/cardx/pcmxp|c/sub0/prealloc | prealloc_max),并提供 read() [all] & write() [only for prealloc] 方法,代码如下:

static inline void preallocate_info_init(struct snd_pcm_substream *substream)
{
    
    
	struct snd_info_entry *entry;

	if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
    
    
		entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
		entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
		entry->mode |= 0200;
		entry->private_data = substream;
		if (snd_info_register(entry) < 0) {
    
    
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	substream->proc_prealloc_entry = entry;
	if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", substream->proc_root)) != NULL) {
    
    
		entry->c.text.read = snd_pcm_lib_preallocate_max_proc_read;
		entry->private_data = substream;
		if (snd_info_register(entry) < 0) {
    
    
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	substream->proc_prealloc_max_entry = entry;
}

对该节点执行 cat & echo 如下(详细略):
在这里插入图片描述

2.5 /proc/asound/pcm 文件

在 core/pcm.c 下 alsa_pcm_info() 中会调用 snd_pcm_proc_init() 函数,即会在 snd_proc_root 下创建 pcm 文件(即 /proc/asound/pcm),并提供 read() 方法,代码如下(详细描述略):

/*
 *  Info interface
 */

static void snd_pcm_proc_read(struct snd_info_entry *entry,
			      struct snd_info_buffer *buffer)
{
    
    
	struct snd_pcm *pcm;

	mutex_lock(&register_mutex);
	list_for_each_entry(pcm, &snd_pcm_devices, list) {
    
    
		snd_iprintf(buffer, "%02i-%02i: %s : %s",
			    pcm->card->number, pcm->device, pcm->id, pcm->name);
		if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
			snd_iprintf(buffer, " : playback %i",
				    pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count);
		if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
			snd_iprintf(buffer, " : capture %i",
				    pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count);
		snd_iprintf(buffer, "\n");
	}
	mutex_unlock(&register_mutex);
}

static struct snd_info_entry *snd_pcm_proc_entry;

static void snd_pcm_proc_init(void)
{
    
    
	struct snd_info_entry *entry;

	entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL);
	if (entry) {
    
    
		snd_info_set_text_ops(entry, NULL, snd_pcm_proc_read);
		if (snd_info_register(entry) < 0) {
    
    
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	snd_pcm_proc_entry = entry;
}

执行 cat /proc/asound/pcm 后如下:
在这里插入图片描述
**Note:**其中对于 dai 来说,pcm->id 为如下:
在这里插入图片描述
pcm->name 则需要在 alsa driver 中手动去赋值。
该 read() 函数会列出所有 snd_pcm 下的 info 信息(在 snd_pcm_dev_register() 中会将所有的 snd_pcm 添加到 snd_pcm_devices list 中)

特别地,对于 alsa asoc 中涉及的 dai 对应的节点会类似如下(详细解析见后面章节):
在这里插入图片描述
在这里插入图片描述
特殊,在使用 audio driver 时还可以通过 CONFIG_SND_DEBUG=y && CONFIG_SND_PROC_FS=y 宏隔开在 alsa driver 中自己实现 proc 操作,如 dummy.c => dummy_proc_init/write/read() 用来 print/change dummy->pcm_hw_params(详细略)。

猜你喜欢

转载自blog.csdn.net/weixin_45437140/article/details/128512543