PCM Interface

PCM的创建和初始设置部分
一个声卡设备可以有4个pcm instances, 一个pcm实例和一个pcm设备相关联,一个pcm instance 包含 pcm playback & capture streams,且每一个stream包含一个或多个 substreams,主要函数如下:

在hda_controller.c中有关于hardware的定义

static struct snd_pcm_hardware azx_pcm_hw = {
	.info =			(SNDRV_PCM_INFO_MMAP |
				 SNDRV_PCM_INFO_INTERLEAVED |
				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
				 SNDRV_PCM_INFO_MMAP_VALID |
				 /* No full-resume yet implemented */
				 /* SNDRV_PCM_INFO_RESUME |*/
				 SNDRV_PCM_INFO_PAUSE |
				 SNDRV_PCM_INFO_SYNC_START |
				 SNDRV_PCM_INFO_HAS_WALL_CLOCK |
				 SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
	.rates =		SNDRV_PCM_RATE_48000,
	.rate_min =		48000,
	.rate_max =		48000,
	.channels_min =		2,
	.channels_max =		2,
	.buffer_bytes_max =	AZX_MAX_BUF_SIZE,
	.period_bytes_min =	128,
	.period_bytes_max =	AZX_MAX_BUF_SIZE / 2,
	.periods_min =		2,
	.periods_max =		AZX_MAX_FRAG,
	.fifo_size =		0,
};

open callback

static int azx_pcm_open(struct snd_pcm_substream *substream)
{

	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;

	runtime->hw = azx_pcm_hw;
	.......//more initalization for hardware

hw_params callback

static int azx_pcm_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *hw_params)
{
	....
	....
	int ret;
	ret = chip->ops->substream_alloc_pages(chip, substream,
					  params_buffer_bytes(hw_params));
	return ret;
}

然后substream_alloc_pages()在操作集hda_controller_ops{}中,具体函数内容如下:

static int substream_alloc_pages(struct azx *chip,
				 struct snd_pcm_substream *substream,
				 size_t size)
{
	struct azx_dev *azx_dev = get_azx_dev(substream);
	int ret;

	mark_runtime_wc(chip, azx_dev, substream, false);
	azx_dev->bufsize = 0;
	azx_dev->period_bytes = 0;
	azx_dev->format_val = 0;
	ret = snd_pcm_lib_malloc_pages(substream, size);
	if (ret < 0)
		return ret;
	mark_runtime_wc(chip, azx_dev, substream, true);
	return 0;
}


最终调用snd_pcm_lib_malloc_pages()函数分配DMA buffer,这样就和文档中的内容对应起来了
prepare callback

static int azx_pcm_prepare(struct snd_pcm_substream *substream)
{
	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
	struct azx *chip = apcm->chip;
	struct azx_dev *azx_dev = get_azx_dev(substream);
	struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
	struct snd_pcm_runtime *runtime = substream->runtime;
	....
	err = azx_setup_periods(chip, substream, azx_dev);
	azx_setup_controller(chip, azx_dev);
	.....
}

trigger callback

static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		rstart = 1;
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_RESUME:
		start = 1;
		break;
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_STOP:
	...

pointer callback(get the current hardware pointer)

static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream)
{
	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
	struct azx *chip = apcm->chip;
	struct azx_dev *azx_dev = get_azx_dev(substream);
	return bytes_to_frames(substream->runtime,
			       azx_get_position(chip, azx_dev, false));
}

operators

struct snd_pcm_ops {
	int (*open)(struct snd_pcm_substream *substream);
	int (*close)(struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_pcm_substream * substream,
		     unsigned int cmd, void *arg);
	int (*hw_params)(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params);
	int (*hw_free)(struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_pcm_substream *substream);
	int (*trigger)(struct snd_pcm_substream *substream, int cmd);
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
	int (*wall_clock)(struct snd_pcm_substream *substream,
			  struct timespec *audio_ts);
	int (*copy)(struct snd_pcm_substream *substream, int channel,
		    snd_pcm_uframes_t pos,
		    void __user *buf, snd_pcm_uframes_t count);
	int (*silence)(struct snd_pcm_substream *substream, int channel, 
		       snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
	struct page *(*page)(struct snd_pcm_substream *substream,
			     unsigned long offset);
	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
	int (*ack)(struct snd_pcm_substream *substream);
};

创建一个pcm device

static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,struct hda_pcm *cpcm)
{
	struct azx *chip = bus->private_data;
	struct snd_pcm *pcm;
	struct azx_pcm *apcm;
	int pcm_dev = cpcm->device;
	err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
			  cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams,
			  cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams,
			  &pcm);
	...
	snd_pcm_set_ops(pcm, s, &azx_pcm_ops);	apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);

    /* buffer pre-allocation */
	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
					      chip->card->dev,
					      size, MAX_PREALLOC_SIZE);

不像《write a alsa driver》上所说的要有一个pcm constructor,上述函数就是pcm的一个constructor,因为主要函数在这上面都有了,下面简要分析一下这个constructor。

snd_pcm_new()函数有4个参数,第一个是指向这个pcm归属的那个card,第二个是ID string,第三个是这个stream的index,第四第五个参数是playback/capture substream 的number
snd_pcm_set_ops(), pcm在被create以后,需要为每一个pcm stream 设置ops,所有的callback都在这里有相应的描述
snd_pcm_lib_preallocate_pages_for_all(),在设置好ops以后,要pre-allocate the buffer,根据参数可知上限是32M,

Runtime Pointer , PCM info 的主要部分

在pcm substream 被打开以后,一个PCM runtime instnce 就会被分配给这个substream.。你可以通过substream->runtime 来调用这个pointer,这个runtime pointer包含了可以控制这个PCM的大部分信息:

the copy of hw_params and sw_params configuration
the buffer pointers
mmap records
spinlocks etc
所有的信息都包含在结构体 struct snd_pcm_runtime{}中;

hardware description

在struct snd_pcm_hardware中包含了关于hardware配置的基本定义。即文章最开始部分的结构体

首先是一些info标志位,如SNDRV_PCM_INFO_MMAP,有这个则表示该hardware支持mmap.
然后是一些rate和channel, 注意这里有一个buffer_byte_max, 定义了the maximum buffer size in bytes
结构体里的FIFO SIZE 在driver和alsa-lib中都用不到,所以不用考虑

PCM configuration

讲的主要是 the PCM runtime records

The PCM configurations are stored in the runtime instamce after the application sends hw_params data via alsa-lib.

DMA buffer information

DMA buffer 由下面四个fields来定义:

dma_area, 表示 buffer pointer(logical address),(当buffer is mmaped的时候才需要)
dma_addr, 表示buffer的 physical address,只有当buffer是 linear buffer的时候才会被specified(明确规定)
dma_bytes, buffer的大小(这个是必须有的)
dma_private,由ALSA DMA allocator来分配的
在ALSA中,有一个标准的分配buffer的func: snd_pcm_lib_malloc_pages(),

running status

主要看snd_pcm_status
这个 runnung status 主要参考 runtime->status, 这是一个指向struct snd_pcm_mmap_status 的指针。
例如,我们可以通过 runtime>status->hw_ptr来获得当前的 DMA hardware 指针。
DMA application 指针通过 runtime->control来指定,这里的control 指向struct snd_pcm_mmap_control,在这个结构体中主要是有重要指针 appl_ptr。

private data

注意这里说的是runtime->private_data, 而不是pcm->private_data,
pcm->private_data,指向PCM 刚创建时候的静态分配的chip instance;
runtime->private_data,指向由PCM open callback创建的动态数据结构。

static int azx_pcm_open(struct snd_pcm_substream *substream)
{
	struct azx_dev *azx_dev;
	azx_dev = azx_assign_device(chip, substream);
	...
	runtime->private_data = azx_dev;
	...
}

operators
details about each pcm callback(ops)

static struct snd_pcm_ops azx_pcm_ops = {
	.open = azx_pcm_open,	
	.close = azx_pcm_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = azx_pcm_hw_params,
	.hw_free = azx_pcm_hw_free,
	.prepare = azx_pcm_prepare,
	.trigger = azx_pcm_trigger,
	.pointer = azx_pcm_pointer,
	.wall_clock =  azx_get_wallclock_tstamp,
	.mmap = azx_pcm_mmap,
	.page = snd_pcm_sgbuf_ops_page,
};

open/close: 当一个pcm substream 被打开/关闭的时候被调用
ioctl: 当任何一个对pcm ioctls的调用时候都走这里,通常情况下走常规的snd_pcm_lib_ioctl();

hw_params: 当这个hardware parameter(hw_params)被application 建立起来以后,这表示一当pcm substream的buffer size, period size, format都已经被定义了。一些hardware的建立工作就需要在这个回调函数里面完成,包括这个buffers的alloction。关于buffer分配,调用这个函数snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
这里还需要注意的两点,一个是,hw_params函数和prepare函数在初始化的时候会被调用很多次,所以要注意不要重复分配一块buffer;另一个是hw_params callback是non-atomic(schedulable)的,而trigger callback是atomic(non-schedulable),所以mutexes或schedule related 函数就不能在trigger callback
里面使用。

prepare:也会被调用很多次
trigger:当pcm is started、stopper、paused的时候被调用。

pointer:当PCM中间层要查询当前buffer上的hardware position,这个position 必须以 frames的形式返回,范围是从0 - buffer_size-1;一般情况下该callback的调用,是在buffer update routine的时候,当interrupt routine的snd_pcm_period_elapsed()函数被调用后,PCM中间层更新position并计算剩下可用的space,并唤醒sleeping poll threads等,该回调操作也是原子的。

PCM interrupt handler
在PCM中的interrupt handler是用来更新buffer position,以及告诉中间层当buffer position跨过指定的period 大小的时候。一般通过调用snd_pcm_period_elapsed()函数来inform。
下面由集中声卡发生中断的几种类型:

interuptes at the period(fragment)boundary
这是使用最多的形式,hardward 在每一个period boundary产生中断,在这种情况下,可以在每个interrupt中调用snd_pcm_period_elapsed(struct snd_pcm_substream *substream)函数。该函数以substream pointer作为参数,因此需要保持这个pointer是可访问的。如果在handler中获得了一个spinlock,且这个lock在其他的pcm callbacks中也被使用,这时你就需要先release这个lock在你调用snd_pcm_period_elapsed函数之前,因为这个函数内部调用其他怕pcm callbacks。

irqreturn_t azx_interrupt(int irq, void *dev_id)
{
	struct azx *chip = dev_id;
	struct azx_dev *azx_dev;  
	...
		if (!chip->ops->position_check ||
			chip->ops->position_check(chip, azx_dev)) {
			spin_unlock(&chip->reg_lock);
			snd_pcm_period_elapsed(azx_dev->substream);
			spin_lock(&chip->reg_lock);
		}
	...
	spin_unlock(&chip->lock);
    return IRQ_HANDLED;
 }

。。。。。interrupt还有很多,这里先不介绍了,后续有用到再来补上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值