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还有很多,这里先不介绍了,后续有用到再来补上