Linux ALSA 之三:简单的 ALSA Driver 实现

Posted 年少生而为人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux ALSA 之三:简单的 ALSA Driver 实现相关的知识,希望对你有一定的参考价值。

简单的 ALSA Driver 实现

一、概述

本节主要根据例子来描述撰写简单的 Alsa Driver 实现步骤,其中涉及的 Alsa Api 具体功能基本已经在Linux ALSA 之二:ALSA 声卡与设备中已经描述。

二、Linux ALSA 音频设备驱动实例

1、注册 Platform Device & Platform Driver

在 dummy.c 中直接 module_init() 入口函数中注册 Platform Device & Platform Driver,匹配后进入 platform_driver 的 probe() 函数。

#define SND_DUMMY_DRIVER	"snd_dummy"
static struct platform_driver snd_dummy_driver = 
	.probe		= snd_dummy_probe,
	.remove		= snd_dummy_remove,
	.driver		= 
		.name	= SND_DUMMY_DRIVER,
		.pm	= SND_DUMMY_PM_OPS,
	,
;

static int __init alsa_card_dummy_init(void)

	...
	//1.1 注册 Platform Driver 
	err = platform_driver_register(&snd_dummy_driver);
	if (err < 0)
		return err;
	
	// 用来模拟 DMA Memory 是否已经有被分配(后面会详解)
	err = alloc_fake_buffer();
	if (err < 0) 
		platform_driver_unregister(&snd_dummy_driver);
		return err;
	

	cards = 0;
	for (i = 0; i < SNDRV_CARDS; i++) 
		struct platform_device *device;
		if (! enable[i])
			continue;
		//1.2 注册 Platform Device
		device = platform_device_register_simple(SND_DUMMY_DRIVER,
							 i, NULL, 0);
		if (IS_ERR(device))
			continue;
		if (!platform_get_drvdata(device)) 
			platform_device_unregister(device);
			continue;
		
		devices[i] = device;
		cards++;
	
	...
	return 0;

2、创建 card

在前面 driver & device 匹配后则会调用 probe() 函数。对于每个声卡而言,必须创建一个 card 实例,用于管理这个声卡上的所有设备(组件),如 pcm、control、MIDI、synthesizer 等。

static int snd_dummy_probe(struct platform_device *devptr)

	struct snd_card *card;
	struct snd_dummy *dummy;
	struct dummy_model *m = NULL, **mdl;
	int idx, err;
	int dev = devptr->id;
	
	//2.1 创建 card,其中 index 为 card 索引号,xid 为标识字符串,extra_size 为要分配的 private_data size
	err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE,
			   sizeof(struct snd_dummy), &card);
	if (err < 0)
		return err;
	dummy = card->private_data;
	dummy->card = card;
	...
	return err;

3、PCM 设备相关设定

每个声卡下都可以有多个 PCM 实例,一个 PCM 实例对应一个设备文件。PCM 实例由 PCM Playback & Capture Streams 组成,而每个 PCM Streams 又由一个或多个 PCM Substreams 组成(一般只有一个 Substream)。

3.1 创建 PCM Device

#define MAX_PCM_DEVICES		4
#define MAX_PCM_SUBSTREAMS	128
static int pcm_devs[SNDRV_CARDS] = [0 ... (SNDRV_CARDS - 1)] = 1;
static int pcm_substreams[SNDRV_CARDS] = [0 ... (SNDRV_CARDS - 1)] = 8;

static int snd_card_dummy_pcm(struct snd_dummy *dummy, int device,
			      int substreams)

	struct snd_pcm *pcm;
	struct snd_pcm_ops *ops;
	int err;
	//3.1 创建 PCM Device, 
	//Param1: card 指针; Param2: 标识字符串; Param3: PCM 设备索引(0 标识第一个 PCM 设备); Param4&5: Playback&Capture Substreams 数
	err = snd_pcm_new(dummy->card, "Dummy PCM", device,
			       substreams, substreams, &pcm);
	if (err < 0)
		return err;
	dummy->pcm = pcm;
	...
	return 0;


static int snd_dummy_probe(struct platform_device *devptr)

	...
	//创建所有 PCM Devices
	for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) 
		if (pcm_substreams[dev] < 1)
			pcm_substreams[dev] = 1;
		if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
			pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
		err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]);
		if (err < 0)
			goto __nodev;
	
	...
	return err;

3.2 设置 PCM 操作

static struct snd_pcm_ops dummy_pcm_ops = 
	.open =		dummy_pcm_open,
	.close =	dummy_pcm_close,
	.ioctl =	snd_pcm_lib_ioctl,
	.hw_params =	dummy_pcm_hw_params,
	.hw_free =	dummy_pcm_hw_free,
	.prepare =	dummy_pcm_prepare,
	.trigger =	dummy_pcm_trigger,
	.pointer =	dummy_pcm_pointer,
;

static struct snd_pcm_ops dummy_pcm_ops_no_buf = 
	.open =		dummy_pcm_open,
	.close =	dummy_pcm_close,
	.ioctl =	snd_pcm_lib_ioctl,
	.hw_params =	dummy_pcm_hw_params,
	.hw_free =	dummy_pcm_hw_free,
	.prepare =	dummy_pcm_prepare,
	.trigger =	dummy_pcm_trigger,
	.pointer =	dummy_pcm_pointer,
	.copy_user =	dummy_pcm_copy,
	.copy_kernel =	dummy_pcm_copy_kernel,
	.fill_silence =	dummy_pcm_silence,
	.page =		dummy_pcm_page,
;

static int snd_card_dummy_pcm(struct snd_dummy *dummy, int device,
			      int substreams)

	struct snd_pcm *pcm;
	struct snd_pcm_ops *ops;
	int err;
	...

	// 3.2.1 根据 DMA Memory 是否已经存在选择不同的 ops
	if (fake_buffer)
		ops = &dummy_pcm_ops_no_buf;
	else
		ops = &dummy_pcm_ops;
	
	// 3.2.2 设置 pcm ops 操作
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops);
	pcm->private_data = dummy;
	pcm->info_flags = 0;
	strcpy(pcm->name, "Dummy PCM");
	if (!fake_buffer) 
		snd_pcm_lib_preallocate_pages_for_all(pcm,
			SNDRV_DMA_TYPE_CONTINUOUS,
			snd_dma_continuous_data(GFP_KERNEL),
			0, 64*1024);
	
	return 0;

如上代码,设置 PCM 操作使用 snd_pcm_set_ops(),定义如下

/**
 * snd_pcm_set_ops - set the PCM operators
 * @pcm: the pcm instance
 * @direction: stream direction, SNDRV_PCM_STREAM_XXX
 * @ops: the operator table
 *
 * Sets the given PCM operators to the pcm instance.
 */
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
		     const struct snd_pcm_ops *ops)

其中 PCM 操作结构体 snd_pcm_ops 定义如下:(详细描述已在注释中)

struct snd_pcm_ops 
	/* This is called when a pcm substream is opened */
	int (*open)(struct snd_pcm_substream *substream);
	/* This is called when a pcm substream is closed */
	int (*close)(struct snd_pcm_substream *substream);
	/* This is used for any special call to pcm ioctls. But usually could pass a generic ioctl callback snd_pcm_lib_ioctl. */
	int (*ioctl)(struct snd_pcm_substream * substream,
		     unsigned int cmd, void *arg);
	/* This is called when the hardware parameter (hw_params) is set up by the application, 
	that is, once when the buffer size, the period size, the format, etc. 
	Many hardware setups should be done in this callback, including the allocation of buffer. */
	int (*hw_params)(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params);
	/* This is called to release the resources allocated via hw_params */
	int (*hw_free)(struct snd_pcm_substream *substream);
	/* This is callback is called when the pcm is 'prepared'. You can set the format type,
	sample rate, etc. The difference from hw_params is that the prepare callback will be 
	called each time snd_pcm_prepare() is called, i.e. when recovering after underruns, etc.
	Be careful that this callback will be called many times at each setup, too. */
	int (*prepare)(struct snd_pcm_substream *substream);
	/* This is called when the pcm is started, stopped or paused. Which action is specified 
	in the second argument, SNDRV_PCM_TRIGGER_XXX in <sound/pcm.h>.  At Least, 
	the START and STOP commands must be defined in this callback. */
	int (*trigger)(struct snd_pcm_substream *substream, int cmd);
	/* This callback is called when the PCM middle layer inquires the current hardware position 
	on the buffer. The position must be returned in frames, ranging from 0 to buffer_size-1.
	This is called usually from the buffer-update routine in the pcm middle layer, which
	is invoked when snd_pcm_period_elapsed() is called in the interrupt routine. Then 
	the pcm middle layer updates the position and calculates the available space, and wakes 
	up the sleeping poll threads, etc. This callback is alse atmoic. */
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
	int (*get_time_info)(struct snd_pcm_substream *substream,
			struct timespec *system_ts, struct timespec *audio_ts,
			struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
			struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
	/* For copy and silence callbacks: These callbacks are not mandatory, and can be omitted in most case.
	These callbacks are used when the hardware buffer cannot be in the normal memory space. 
	Some chips have their own buffer on the hardware which is not mappable. In such a case, 
	you have to transfer the data manually from the memory buffer to the hardware buffer. 
	Or, if the buffer is non-contiguous on both physical and virtual memory spaces, these callbacks must be defined, too.*/
	int (*fill_silence)(struct snd_pcm_substream *substream, int channel,
			    unsigned long pos, unsigned long bytes);
	int (*copy_user)(struct snd_pcm_substream *substream, int channel,
			 unsigned long pos, void __user *buf,
			 unsigned long bytes);
	int (*copy_kernel)(struct snd_pcm_substream *substream, int channel,
			   unsigned long pos, void *buf, unsigned long bytes);
	struct page *(*page)(struct snd_pcm_substream *substream,
			     unsigned long offset);
	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
	/* This callbacks is also not mandatory. This callback is called when the appl_ptr 
	is updated in read or write operations. Some drivers like emu10k1-fx and cs46xx need 
	to track the current appl_ptr for the internal buffer, and this callback is useful 
	only for such a purpose. */
	int (*ack)(struct snd_pcm_substream *substream);
;

在上述代码中可以看到针对 fake_buffer 的不同对应的 ops 也不同,其中 fake_buffer 用于描述 DMA 是否已经有自己的缓冲区 Buffer,具体描述如下:

1) fake_buffer = 1
   => 在 module_init 中有申请 fake buffer(当成是 DMA 已经有相应的缓冲区)
      => 由于该 DMA 已经有相应可用的 Buffer, 故无需 allocate_pages for DMA
         => 对应的 snd_pcm_ops = dummy_pcm_ops_no_buf
         	=> 在 ops->hw_params 中不会再 allocate DMA 缓冲区
         	   => 该 snd_pcm_ops 则需要填充 copy_user 等 callbacks, 在 pcm_write() 时最终会将数据给到 copy_user(), 需要 driver 在 copy_user() 中将 Audio Data Copy to DMA Buffer.
         	   
2) fake_buffer = 0
   => 在 module_init 中没有申请 fake buffer(当成是 DMA 没有相应的缓冲区)
      => 由于该 DMA 没有相应可用的 Buffer, 故需要 allocate_pages for DMA
        【在 snd_pcm_new 之后先 call snd_pcm_lib_preallocate_pages_for_all() 预分配 DMA Buffer(预分配=> 未分配), 即设置相应参数,如 size & max_size】
         => 对应的 snd_pcm_ops = dummy_pcm_ops
         	=> 在 ops->hw_params 中会 call snd_pcm_lib_malloc_pages() 分配 DMA 缓冲区, 并将 dma buffer info 保存在 substream->dma_buffer && substream-> runtime->dma_buffer_p & dma_area & dma_addr & dma_bytes
         	   => 该 snd_pcm_ops 则不会填充 copy_user 等 callbacks, 在 pcm_write() 时会将 Audio Data 直接送到前面分配的 DMA Buffer 中, 即 runtime->dma_area + hwOffset.      

举例:
1)mtk-dtv 对于 audio 都会有被分配好一块 DDR,此时使用到的 DMA 都有对应物理地址连续的一片 DDR 作为 DMA 缓冲区,此时则属于上述 fake_buffer=1 Case;
2)mtk-mt2701 等移动设备对于 PCM DMA 的话则没有分配好 DMA 缓冲区,即需要在 driver 中去分配,此时则属于上述 fake_buffer=0 Case.
Note:上述例子由于涉及 ASOC,故后面的章节中会讲解到。

  • 当一个 PCM 子流被打开时,会为该 substeam 分配一个 runtime 实例(即 substream->runtime),runtime 包含各种各样的信息:hw_params & sw_params 配置的拷贝、缓冲区指针、mmap 记录、自旋锁等,几乎 PCM 的所有控制信息均能从中取得,定义如下:
struct snd_pcm_runtime 
	/* -- Status -- */
	struct snd_pcm_substream *trigger_master;
	struct timespec trigger_tstamp;	/* trigger timestamp */
	bool trigger_tstamp_latched;     /* trigger timestamp latched in low-level driver/hardware */
	int overrange;
	snd_pcm_uframes_t avail_max;
	snd_pcm_uframes_t hw_ptr_base;	/* Position at buffer restart */
	snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
	unsigned long hw_ptr_jiffies;	/* Time when hw_ptr is updated */
	unsigned long hw_ptr_buffer_jiffies; /* buffer time in jiffies */
	snd_pcm_sframes_t delay;	/* extra delay; typically FIFO size */
	u64 hw_ptr_wrap;                /* offset for hw_ptr due to boundary wrap-around */

	/* -- HW params -- */
	snd_pcm_access_t access;	/* access mode */
	snd_pcm_format_t format;	/* SNDRV_PCM_FORMAT_* */
	snd_pcm_subformat_t subformat;	/* subformat */
	unsigned int rate;		/* rate in Hz */
	unsigned int channels;		/* channels */
	snd_pcm_uframes_t period_size;	/* period size */
	unsigned int periods;		/* periods */
	snd_pcm_uframes_t buffer_size;	/* buffer size */
	snd_pcm_uframes_t min_align;	/* Min alignment for the format */
	size_t byte_align;
	unsigned int frame_bits;
	unsigned int sample_bits;
	unsigned int info;
	unsigned int rate_num;
	unsigned int rate_den;
	unsigned int no_period_wakeup: 1;

	/* -- SW params -- */
	int tstamp_mode;		/* mmap timestamp is updated */
  	unsigned int period_step;
	snd_pcm_uframes_t start_threshold;
	snd_pcm_uframes_t stop_threshold;
	snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
						noise is nearest than this */
	snd_pcm_uframes_t silence_size;	/* Silence filling size */
	snd_pcm_uframes_t boundary;	/* pointers wrap point */

	snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
	snd_pcm_uframes_t silence_filled; /* size filled with silence */

	union snd_pcm_sync_id sync;	/* hardware synchronization ID */

	/* -- mmap -- */
	struct snd_pcm_mmap_status *status;
	struct snd_pcm_mmap_control *control;

	/* -- locking / scheduling -- */
	snd_pcm_uframes_t twake; 	/* do transfer (!poll) wakeup if non-zero */
	wait_queue_head_t sleep;	/* poll sleep */
	wait_queue_head_t tsleep;	/* transfer sleep */
	struct fasync_struct *fasync;

	/* -- private section -- */
	void *private_data;
	void (*private_free)(struct snd_pcm_runtime *runtime);

	/* -- hardware description -- */
	struct snd_pcm_hardware hw;
	struct snd_pcm_hw_constraints hw_constraints;

	/* -- timer -- */
	unsigned int timer_resolution;	/* timer resolution */
	int tstamp_type;		/* timestamp type */

	/* -- DMA -- */           
	unsigned char *dma_area;	/* DMA area */
	dma_addr_t dma_addr;		/* physical bus address (not accessible from main CPU) */
	size_t dma_bytes;		/* size of DMA area */

	struct snd_dma_buffer *dma_buffer_p;	/* allocated buffer */

	/* -- audio timestamp config -- */
	struct snd_pcm_audio_tstamp_config audio_tstamp_config;
	struct snd_pcm_audio_tstamp_report audio_tstamp_report;
	struct timespec driver_tstamp;

#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	/* -- OSS things -- */
	struct snd_pcm_oss_runtime oss;
#endif
;

snd_pcm_runtime 中的大多数记录对被声卡驱动操作集中的函数是只读的,仅仅 PCM 中间层可更新或修改这些信息,但是硬件描述、中断回调函数、DMA 缓冲区信息和私有数据是例外的。

下面解释 snd_pcm_runtime 结构体中的几个重要成员:

(1)Hw Params
包含了基本硬件配置的定义,需要在 ops->open() 函数中赋值。runtime 实例保存的是硬件描述的拷贝而非指针,这意味着在 open()函数中可以修改被拷贝的硬件描述(runtime->hw)。

struct snd_pcm_hardware 
	unsigned int info;		/* SNDRV_PCM_INFO_* */
	u64 formats;			/* SNDRV_PCM_FMTBIT_* */
	unsigned int rates;		/* SNDRV_PCM_RATE_* */
	unsigned int rate_min;		/* min rate */
	unsigned int rate_max;		/* max rate */
	unsigned int channels_min;	/* min channels */
	unsigned int channels_max;	/* max channels */
	size_t buffer_bytes_max;	/* max buffer size */
	size_t period_bytes_min;	/* min period size */
	size_t period_bytes_max;	/* max period size */
	unsigned int periods_min;	/* min # of periods */
	unsigned int periods_max;	/* max # of periods */
	size_t fifo_size;		/* fifo size in bytes */
;

buffer_bytes_max 定义最大的缓冲区大小,注意:没有 buffer_bytes_min 字段,这是因为它可以通过最小的周期大小和最小的周期数量计算出来;
period 信息定义了 PCM 中断产生的周期。更小的周期大小意味着
更多的中断,在录音时,周期大小定义了输入延迟,在播放时,整个缓冲区大小对应着输出延迟。
Note:对缓冲区和周期大小的配置以帧(frame)形式存储,而 frames_to_bytes()bytes_to_frames()可完成帧和字节的转换。

For pcm:
1 frame => format2bytes*channels;
1 sample => format2bytes.

dummy.c 定义如下:

static const struct snd_pcm_hardware dummy_pcm_hardware = 
	.info =			(SNDRV_PCM_INFO_MMAP |
				 SNDRV_PCM_INFO_INTERLEAVED |
				 SNDRV_PCM_INFO_RESUME |
				 SNDRV_PCM_INFO_MMAP_VALID),
	.formats =		USE_FORMATS,
	.rates =		USE_RATE,
	.rate_min =		USE_RATE_MIN,
	.rate_max =		USE_RATE_MAX,
	.channels_min =		USE_CHANNELS_MIN,
	.channels_max =		USE_CHANNELS_MAX,
	.buffer_bytes_max =	MAX_BUFFER_SIZE,
	.period_bytes_min =	MIN_PERIOD_SIZE,
	.period_bytes_max =	MAX_PERIOD_SIZE,
	.periods_min =		USE_PERIODS_MIN,
	.periods_max =		USE_PERIODS_MAX,
	.fifo_size =		0,
;

snd_pcm_ops 中的 open() 函数将被调用,在这个函数中,至少需要初始化 runtime->hw 字段,dummy 代码范例如下:

static int dummy_pcm_open(struct snd_pcm_substream *substream)

	

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!

1. PCM是什么


PCM是英文Pulse-code modulation的缩写,中文译名是脉冲编码调制。我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲,把这些脉冲的幅值按一定的精度进行量化,这些量化后的数值被连续地输出、传输、处理或记录到存储介质中,所有这些组成了数字音频的产生过程。

技术分享

       图1.1  模拟音频的采样、量化

 

PCM信号的两个重要指标是采样频率和量化精度,目前,CD音频的采样频率通常为44100Hz,量化精度是16bit。通常,播放音乐时,应用程序从存储介质中读取音频数据(MP3、WMA、AAC......),经过解码后,最终送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得的PCM数据送回给应用程序,由应用程序完成压缩、存储等任务。所以,音频驱动的两大核心任务就是:

  • playback    如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频
  • capture     把mic拾取到得模拟信号,经过采样、量化,转换为PCM信号送回给用户空间的应用程序

2. alsa-driver中的PCM中间层


ALSA已经为我们实现了功能强劲的PCM中间层,自己的驱动中只要实现一些底层的需要访问硬件的函数即可。

 

要访问PCM的中间层代码,你首先要包含头文件<sound/pcm.h>,另外,如果需要访问一些与 hw_param相关的函数,可能也要包含<sound/pcm_params.h>。

 

每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件。pcm实例数量的这种限制源于Linux设备号所占用的位大小,如果以后使用64位的设备号,我们将可以创建更多的pcm实例。不过大多数情况下,在嵌入式设备中,一个pcm实例已经足够了。

 

一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。

技术分享

                                    图2.1  声卡中的pcm结构

 

在嵌入式系统中,通常不会像图2.1中这么复杂,大多数情况下是一个声卡,一个pcm实例,pcm下面有一个playback和capture stream,playback和capture下面各自有一个substream。

 

 下面一张图列出了pcm中间层几个重要的结构,他可以让我们从uml的角度看一看这列结构的关系,理清他们之间的关系,对我们理解pcm中间层的实现方式。

技术分享

                                                 图2.2  pcm中间层的几个重要的结构体的关系图

 

  • snd_pcm是挂在snd_card下面的一个snd_device
  • snd_pcm中的字段:streams[2],该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream
  • snd_pcm_str中的substream字段,指向snd_pcm_substream结构
  • snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是他的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数。

 3. 新建一个pcm


 

alsa-driver的中间层已经为我们提供了新建pcm的api:

 

        int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count,
                                     struct snd_pcm ** rpcm);

 

参数device 表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始。

参数playback_count 表示该pcm将会有几个playback substream。

参数capture_count 表示该pcm将会有几个capture substream。

 

另一个用于设置pcm操作函数接口的api:

 

        void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);

 

 

新建一个pcm可以用下面一张新建pcm的调用的序列图进行描述:

 

 技术分享

 

                                                                         图3.1 新建pcm的序列图

  • snd_card_create    pcm是声卡下的一个设备(部件),所以第一步是要创建一个声卡
  • snd_pcm_new    调用该api创建一个pcm,才该api中会做以下事情
    • 如果有,建立playback stream,相应的substream也同时建立
    • 如果有,建立capture stream,相应的substream也同时建立
    • 调用snd_device_new()把该pcm挂到声卡中,参数ops中的dev_register字段指向了函数snd_pcm_dev_register,这个回调函数会在声卡的注册阶段被调用。
  • snd_pcm_set_ops    设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数
  • snd_card_register    注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序(alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc

4. 设备文件节点的建立(dev/snd/pcmCxxDxxp、pcmCxxDxxc)


4.1 struct snd_minor

每个snd_minor结构体保存了声卡下某个逻辑设备的上下文信息,他在逻辑设备建立阶段被填充,在逻辑设备被使用时就可以从该结构体中得到相应的信息。pcm设备也不例外,也需要使用该结构体。该结构体在include/sound/core.h中定义。

 

[c-sharp] view plain copy
 
  1. struct snd_minor {  
  2.     int type;           /* SNDRV_DEVICE_TYPE_XXX */  
  3.     int card;           /* card number */  
  4.     int device;         /* device number */  
  5.     const struct file_operations *f_ops;    /* file operations */  
  6.     void *private_data;     /* private data for f_ops->open */  
  7.     struct device *dev;     /* device for sysfs */  
  8. };  

 

在sound/sound.c中定义了一个snd_minor指针的全局数组:

 

[c-sharp] view plain copy
 
  1. static struct snd_minor *snd_minors[256];  

 

前面说过,在声卡的注册阶段(snd_card_register),会调用pcm的回调函数snd_pcm_dev_register(),这个函数里会调用函数snd_register_device_for_dev():

 

[c-sharp] view plain copy
 
  1. static int snd_pcm_dev_register(struct snd_device *device)  
  2. {  
  3.     ......  
  4.   
  5.     /* register pcm */  
  6.     err = snd_register_device_for_dev(devtype, pcm->card,  
  7.                          pcm->device,  
  8.                     &snd_pcm_f_ops[cidx],  
  9.                     pcm, str, dev);  
  10.     ......  
  11. }  

 

我们再进入snd_register_device_for_dev():

 

[c-sharp] view plain copy
 
  1. int snd_register_device_for_dev(int type, struct snd_card *card, int dev,  
  2.                 const struct file_operations *f_ops,  
  3.                 void *private_data,  
  4.                 const char *name, struct device *device)  
  5. {  
  6.     int minor;  
  7.     struct snd_minor *preg;  
  8.   
  9.     if (snd_BUG_ON(!name))  
  10.         return -EINVAL;  
  11.     preg = kmalloc(sizeof *preg, GFP_KERNEL);  
  12.     if (preg == NULL)  
  13.         return -ENOMEM;  
  14.     preg->type = type;  
  15.     preg->card = card ? card->number : -1;  
  16.     preg->device = dev;  
  17.     preg->f_ops = f_ops;  
  18.     preg->private_data = private_data;  
  19.     mutex_lock(&sound_mutex);  
  20. #ifdef CONFIG_SND_DYNAMIC_MINORS  
  21.     minor = snd_find_free_minor();  
  22. #else  
  23.     minor = snd_kernel_minor(type, card, dev);  
  24.     if (minor >= 0 && snd_minors[minor])  
  25.         minor = -EBUSY;  
  26. #endif  
  27.     if (minor < 0) {  
  28.         mutex_unlock(&sound_mutex);  
  29.         kfree(preg);  
  30.         return minor;  
  31.     }  
  32.     snd_minors[minor] = preg;  
  33.     preg->dev = device_create(sound_class, device, MKDEV(major, minor),  
  34.                   private_data, "%s", name);  
  35.     if (IS_ERR(preg->dev)) {  
  36.         snd_minors[minor] = NULL;  
  37.         mutex_unlock(&sound_mutex);  
  38.         minor = PTR_ERR(preg->dev);  
  39.         kfree(preg);  
  40.         return minor;  
  41.     }  
  42.   
  43.     mutex_unlock(&sound_mutex);  
  44.     return 0;  
  45. }  

 

  • 首先,分配并初始化一个snd_minor结构中的各字段
    • type:SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE
    • card: card的编号
    • device:pcm实例的编号,大多数情况为0
    • f_ops:snd_pcm_f_ops
    • private_data:指向该pcm的实例
  • 根据type,card和pcm的编号,确定数组的索引值minor,minor也作为pcm设备的此设备号
  • 把该snd_minor结构的地址放入全局数组snd_minors[minor]中
  • 最后,调用device_create创建设备节点

4.2 设备文件的建立

 


 

在4.1节的最后,设备文件已经建立,不过4.1节的重点在于snd_minors数组的赋值过程,在本节中,我们把重点放在设备文件中。

 

回到pcm的回调函数snd_pcm_dev_register()中:

 

[c-sharp] view plain copy
 
  1. static int snd_pcm_dev_register(struct snd_device *device)  
  2. {  
  3.     int cidx, err;  
  4.     char str[16];  
  5.     struct snd_pcm *pcm;  
  6.     struct device *dev;  
  7.   
  8.     pcm = device->device_data;  
  9.          ......  
  10.     for (cidx = 0; cidx < 2; cidx++) {  
  11.                   ......  
  12.         switch (cidx) {  
  13.         case SNDRV_PCM_STREAM_PLAYBACK:  
  14.             sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);  
  15.             devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;  
  16.             break;  
  17.         case SNDRV_PCM_STREAM_CAPTURE:  
  18.             sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);  
  19.             devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;  
  20.             break;  
  21.         }  
  22.         /* device pointer to use, pcm->dev takes precedence if 
  23.          * it is assigned, otherwise fall back to card‘s device 
  24.          * if possible */  
  25.         dev = pcm->dev;  
  26.         if (!dev)  
  27.             dev = snd_card_get_device_link(pcm->card);  
  28.         /* register pcm */  
  29.         err = snd_register_device_for_dev(devtype, pcm->card,  
  30.                           pcm->device,  
  31.                           &snd_pcm_f_ops[cidx],  
  32.                           pcm, str, dev);  
  33.                   ......  
  34.     }  
  35.          ......  
  36. }  

 

 

以上代码我们可以看出,对于一个pcm设备,可以生成两个设备文件,一个用于playback,一个用于capture,代码中也确定了他们的命名规则:

  • playback  --  pcmCxDxp,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0p
  • capture  --  pcmCxDxc,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0c

snd_pcm_f_ops

 

snd_pcm_f_ops是一个标准的文件系统file_operations结构数组,它的定义在sound/core/pcm_native.c中:

 

[c-sharp] view plain copy
 
  1. const struct file_operations snd_pcm_f_ops[2] = {  
  2.     {  
  3.         .owner =        THIS_MODULE,  
  4.         .write =        snd_pcm_write,  
  5.         .aio_write =        snd_pcm_aio_write,  
  6.         .open =         snd_pcm_playback_open,  
  7.         .release =      snd_pcm_release,  
  8.         .llseek =       no_llseek,  
  9.         .poll =         snd_pcm_playback_poll,  
  10.         .unlocked_ioctl =   snd_pcm_playback_ioctl,  
  11.         .compat_ioctl =     snd_pcm_ioctl_compat,  
  12.         .mmap =         snd_pcm_mmap,  
  13.         .fasync =       snd_pcm_fasync,  
  14.         .get_unmapped_area =    snd_pcm_get_unmapped_area,  
  15.     },  
  16.     {  
  17.         .owner =        THIS_MODULE,  
  18.         .read =         snd_pcm_read,  
  19.         .aio_read =     snd_pcm_aio_read,  
  20.         .open =         snd_pcm_capture_open,  
  21.         .release =      snd_pcm_release,  
  22.         .llseek =       no_llseek,  
  23.         .poll =         snd_pcm_capture_poll,  
  24.         .unlocked_ioctl =   snd_pcm_capture_ioctl,  
  25.         .compat_ioctl =     snd_pcm_ioctl_compat,  
  26.         .mmap =         snd_pcm_mmap,  
  27.         .fasync =       snd_pcm_fasync,  
  28.         .get_unmapped_area =    snd_pcm_get_unmapped_area,  
  29.     }  
  30. };  

 

snd_pcm_f_ops作为snd_register_device_for_dev的参数被传入,并被记录在snd_minors[minor]中的字段f_ops中。最后,在snd_register_device_for_dev中创建设备节点:

 

[c-sharp] view plain copy
 
  1. snd_minors[minor] = preg;  
  2. preg->dev = device_create(sound_class, device, MKDEV(major, minor),  
  3.               private_data, "%s", name);  

 

 

4.3 层层深入,从应用程序到驱动层pcm


4.3.1 字符设备注册

在sound/core/sound.c中有alsa_sound_init()函数,定义如下:

 

[c-sharp] view plain copy
 
  1. static int __init alsa_sound_init(void)  
  2. {  
  3.     snd_major = major;  
  4.     snd_ecards_limit = cards_limit;  
  5.     if (register_chrdev(major, "alsa", &snd_fops)) {  
  6.         snd_printk(KERN_ERR "unable to register native major device number %d/n", major);  
  7.         return -EIO;  
  8.     }  
  9.     if (snd_info_init() < 0) {  
  10.         unregister_chrdev(major, "alsa");  
  11.         return -ENOMEM;  
  12.     }  
  13.     snd_info_minor_register();  
  14.     return 0;  
  15. }  

 

register_chrdev中的参数major与之前创建pcm设备是device_create时的major是同一个,这样的结果是,当应用程序open设备文件/dev/snd/pcmCxDxp时,会进入snd_fops的open回调函数,我们将在下一节中讲述open的过程。

4.3.2 打开pcm设备

从上一节中我们得知,open一个pcm设备时,将会调用snd_fops的open回调函数,我们先看看snd_fops的定义:

 

[c-sharp] view plain copy
 
  1. static const struct file_operations snd_fops =  
  2. {  
  3.     .owner =    THIS_MODULE,  
  4.     .open =     snd_open  
  5. };  

 

跟入snd_open函数,它首先从inode中取出此设备号,然后以次设备号为索引,从snd_minors全局数组中取出当初注册pcm设备时填充的snd_minor结构(参看4.1节的内容),然后从snd_minor结构中取出pcm设备的f_ops,并且把file->f_op替换为pcm设备的f_ops,紧接着直接调用pcm设备的f_ops->open(),然后返回。因为file->f_op已经被替换,以后,应用程序的所有read/write/ioctl调用都会进入pcm设备自己的回调函数中,也就是4.2节中提到的snd_pcm_f_ops结构中定义的回调。

 

[c-sharp] view plain copy
 
  1. static int snd_open(struct inode *inode, struct file *file)  
  2. {  
  3.     unsigned int minor = iminor(inode);  
  4.     struct snd_minor *mptr = NULL;  
  5.     const struct file_operations *old_fops;  
  6.     int err = 0;  
  7.   
  8.     if (minor >= ARRAY_SIZE(snd_minors))  
  9.         return -ENODEV;  
  10.     mutex_lock(&sound_mutex);  
  11.     mptr = snd_minors[minor];  
  12.     if (mptr == NULL) {  
  13.         mptr = autoload_device(minor);  
  14.         if (!mptr) {  
  15.             mutex_unlock(&sound_mutex);  
  16.             return -ENODEV;  
  17.         }  
  18.     }  
  19.     old_fops = file->f_op;  
  20.     file->f_op = fops_get(mptr->f_ops);  
  21.     if (file->f_op == NULL) {  
  22.         file->f_op = old_fops;  
  23.         err = -ENODEV;  
  24.     }  
  25.     mutex_unlock(&sound_mutex);  
  26.     if (err < 0)  
  27.         return err;  
  28.   
  29.     if (file->f_op->open) {  
  30.         err = file->f_op->open(inode, file);  
  31.         if (err) {  
  32.             fops_put(file->f_op);  
  33.             file->f_op = fops_get(old_fops);  
  34.         }  
  35.     }  
  36.     fops_put(old_fops);  
  37.     return err;  
  38. }  

 

 

下面的序列图展示了应用程序如何最终调用到snd_pcm_f_ops结构中的回调函数:

技术分享

                                                               图4.3.2.1    应用程序操作pcm设备

 

以上是关于Linux ALSA 之三:简单的 ALSA Driver 实现的主要内容,如果未能解决你的问题,请参考以下文章

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)

Linux audio驱动模型

linux下怎样修改/usr/share/alsa/alsa.conf

Linux ALSA介绍