Linux音频驱动之三:pcm接口的调用流程

本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记

一.pcm设备的open

由“Linux音频驱动之一:音频驱动注册流程” 这篇文章可知,pcm device调用的是snd_pcm_dev_register函数注册的。

snd_pcm_dev_register
err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,
						  &snd_pcm_f_ops[cidx],pcm, str, dev);

当注册播放pcm设备时,fops是snd_pcm_f_ops[0];
当注册录制pcm设备时,fops是snd_pcm_f_ops[1];
现在以播放pcm设备为例,看一下snd_pcm_f_ops[0]。

const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	dummy_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	dummy_get_unmapped_area,
	}
};

当打开pcm device设备时,就会调用上面的snd_pcm_playback_open函数。
进入snd_pcm_playback_open函数分析。

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
	struct snd_pcm *pcm;

	pcm = snd_lookup_minor_data(iminor(inode),
				    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
	return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
}
  • 获得snd_pcm数据。
    iminor(inode) = MINOR(inode->i_rdev)
    inode->i_rdev是文件对应设备的设备号,MINOR(inode->i_rdev)是次设备号。
    我们在pcm device这个设备注册的时候,在snd_minors数组里面寻找一个没有使用的元素,把fops,snd_pcm,type等信息保存。把该元素的下标minor作为次设备号。
    主设备号是116
#define CONFIG_SND_MAJOR	116

dev_t = 116 << 20 | minor
最后把dev_t注册进了内核。
现在open时根据次设备号找到snd_minors[minor],得到snd_pcm数据。

  • 调用snd_pcm_open函数
    1.调用snd_card_file_add函数。
    这个函数将打开的文件保存,放入了snd_card的files_list链表。作用是用于跟踪连接状态,避免热插拔释放繁忙资源。
err = snd_card_file_add(pcm->card, file);
  1. 调用snd_pcm_open_file函数。
err = snd_pcm_open_file(file, pcm, stream, &pcm_file);
  • snd_pcm_open_file函数
    1.调用snd_pcm_open_substream函数获取snd_pcm_substream数据。
err = snd_pcm_open_substream(pcm, stream, file, &substream);

2.申请一个snd_pcm_file结构体,把获取到的snd_pcm_substream数据保存到snd_pcm_file.substream。
然后把snd_pcm_file赋值给file->private_data。
Linux音频驱动之三:pcm接口的调用流程_第1张图片

二. snd_pcm_open_substream函数分析
  • 调用函数snd_pcm_attach_substream获取snd_pcm_substream。
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
    pstr = &pcm->streams[stream];
    for (substream = pstr->substream; substream; substream = substream->next)
		if (!SUBSTREAM_BUSY(substream))
			break;

根据snd_pcm.streams[0]得到播放的snd_pcm_str。
循环查找snd_pcm_str的substream,找到substream->ref_count == 0 的那个substream。这个substream就是我们我们上面想要的snd_pcm_substream数据。

runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = snd_malloc_pages(size, GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
runtime->control = snd_malloc_pages(size, GFP_KERNEL);

申请一个snd_pcm_runtime类型的数据runtime,然后给runtime->status和runtime->control分配空间。
填充substream数据。

substream->runtime = runtime;
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->f_flags = file->f_flags;
pstr->substream_opened++;

最终得到的数据如下:
Linux音频驱动之三:pcm接口的调用流程_第2张图片

  • 调用snd_pcm_hw_constraints_init函数
    snd_pcm_hw_constraints_init函数里面涉及到几个重要的结构体。
struct snd_pcm_hw_constraints {
	struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - 
			 SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
	struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
			     SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};

SNDRV_PCM_HW_PARAM_FIRST_MASK = 0,SNDRV_PCM_HW_PARAM_LAST_MASK = 2;
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = 8, SNDRV_PCM_HW_PARAM_LAST_INTERVAL = 19
因此:

struct snd_pcm_hw_constraints {
	struct snd_mask masks[3];
	struct snd_interval intervals[12];
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};
#define SNDRV_MASK_MAX	256
struct snd_mask 
{
	__u32 bits[(SNDRV_MASK_MAX+31)/32];
};
struct snd_interval {
    unsigned int min, 
    unsigned int max,
    unsigned int openmin:1,
    openmax:1,
    integer:1,
    empty:1;
};

因此:

struct snd_pcm_hw_constraints 
{
    struct snd_mask masks[3] =
    {
        int bits[8];     //设置Access type
        int bits[8];     //设置Format
        int bits[8];    //设置Subformat
    };
	struct snd_interval intervals[12]=
    {
        //设置采样数据位数
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置每帧数据位数
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置通道
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置采样率
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置中断之间的时间间隔
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置中断之间的数据帧(不知道是个啥意思)
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置终端之间的bytes(不知道是个啥意思)
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
        //设置中断个数
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置缓冲时间
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置缓冲大小
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置缓冲大小
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },
         //设置Approx tick duration
        {
            unsigned int min, 
            unsigned int max,
            unsigned int openmin:1,
            openmax:1,
            integer:1,
            empty:1;   
        },       
    }
	unsigned int rules_num;
	unsigned int rules_all;
	struct snd_pcm_hw_rule *rules;
};

masks元素的前两个设置成0xff

for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
    snd_mask_any(constrs_mask(constrs, k));
}

设置后:

struct snd_mask masks[3] =
{
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;   //设置Access type
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;     //设置Format
    int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ;    //设置Subformat
};

初始化intervals[12]的所有元素,元素里面的变量全部设置为0。

for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
    snd_interval_any(constrs_interval(constrs, k));
}

static inline void snd_interval_any(struct snd_interval *i)
{
	i->min = 0;
	i->openmin = 0;
	i->max = UINT_MAX;
	i->openmax = 0;
	i->integer = 0;
	i->empty = 0;
}

初始化intervals[12]的整数标记,就是intervals[12]中的元素哪几个要设置成整数的。

snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));

通道,缓冲大小,采样位数,帧位数等要设置成整数。

添加rule规则,就是给snd_pcm_hw_constraints.rules赋值。

err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,snd_pcm_hw_rule_format, NULL,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);

int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,
			int var,
			snd_pcm_hw_rule_func_t func, void *private,
			int dep, ...)
{
	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
	struct snd_pcm_hw_rule *c;
	unsigned int k;
	va_list args;
	va_start(args, dep);
	if (constrs->rules_num >= constrs->rules_all) {
		struct snd_pcm_hw_rule *new;
		unsigned int new_rules = constrs->rules_all + 16;
		new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
		if (!new)
			return -ENOMEM;
		if (constrs->rules) {
			memcpy(new, constrs->rules,
			       constrs->rules_num * sizeof(*c));
			kfree(constrs->rules);
		}
		constrs->rules = new;
		constrs->rules_all = new_rules;
	}
	c = &constrs->rules[constrs->rules_num];
	c->cond = cond;
	c->func = func;
	c->var = var;
	c->private = private;
	k = 0;
	while (1) {
		if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps)))
			return -EINVAL;
		c->deps[k++] = dep;
		if (dep < 0)
			break;
		dep = va_arg(args, int);
	}
	constrs->rules_num++;
	va_end(args);
	return 0;
}

其中va_start,va_arg,va_end是传入可变参数用的。
va_start(args, dep):也就是让args指针指向dep变量所在的地址;
va_arg(args, int):让args指针偏移到下一个地址,同时返回里面的值,int类型。偏移结束的条件是取出来的值小于0。
va_end(args):args = 0
上面的代码把args指针里面的取出来的值保存在c->deps数组中。把上面的snd_pcm_hw_rule_add函数翻译一下得到:

constrs.rules[0] = 
{
    cond = 0;
    func = snd_pcm_hw_rule_format;
    var = SNDRV_PCM_HW_PARAM_FORMAT
    deps[0] = SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8;
    private = NULL;
}

下面添加了很多的规则,就不一一介绍了。

  • 调用substream->ops->open函数
    根据Linux 音频驱动这一章节中的内容:
    给snd_pcm.streams[i].substream[i].ops赋值,mmap,pointer,ioctl等几个函数使用s3c24xx_pcm_ops的,其他的使用soc_pcm_ops自己的。soc_pcm_ops如下:
soc_pcm_ops.open		= soc_pcm_open,
soc_pcm_ops.close		= soc_codec_close,
soc_pcm_ops.hw_params	= soc_pcm_hw_params,
soc_pcm_ops.hw_free	= soc_pcm_hw_free,
soc_pcm_ops.prepare	= soc_pcm_prepare,
soc_pcm_ops.trigger	= soc_pcm_trigger,
soc_pcm_ops.mmap = platform->pcm_ops->mmap;
soc_pcm_ops.pointer = platform->pcm_ops->pointer;
soc_pcm_ops.ioctl = platform->pcm_ops->ioctl;
soc_pcm_ops.copy = platform->pcm_ops->copy;
soc_pcm_ops.silence = platform->pcm_ops->silence;
soc_pcm_ops.ack = platform->pcm_ops->ack;
soc_pcm_ops.page = platform->pcm_ops->page;
if (playback)
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);

if (capture)
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);

可以知道substream->ops->open = soc_pcm_open函数。
这一步就是调用soc_pcm_open函数。

  1. 调用cpu_dai->ops->startup函数。
if (cpu_dai->ops->startup) 
{
    ret = cpu_dai->ops->startup(substream, cpu_dai);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open interface %s\n",
            cpu_dai->name);
        goto out;
    }
}

cpu_dai = s3c24xx_i2s_dai,s3c24xx_i2s_dai->ops->open = NULL,这里不执行。

2.调用platform->pcm_ops->open

if (platform->pcm_ops->open) 
{
    ret = platform->pcm_ops->open(substream);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
        goto platform_err;
    }
}

platform = s3c24xx_soc_platform,s3c24xx_soc_platform->pcm_ops->open = s3c24xx_pcm_open
进入s3c24xx_pcm_open函数。

static int s3c24xx_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct s3c24xx_runtime_data *prtd;
	pr_debug("Entered %s\n", __func__);
	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
	snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware);
	prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
	if (prtd == NULL)
		return -ENOMEM;

	spin_lock_init(&prtd->lock);
	runtime->private_data = prtd;
	return 0;
}

设置SNDRV_PCM_HW_PARAM_PERIODS项时,要用整形数据。

snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);

对snd_pcm_runtime.hw赋值。
runtime->hw.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME;
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |SNDRV_PCM_FMTBIT_S8;
runtime->hw.period_bytes_min = 4Kb;
runtime->hw.period_bytes_max = 8Kb;
runtime->hw.periods_min = 2;
runtime->hw.periods_max =128 ;
runtime->hw.buffer_bytes_max =128Kb ;
runtime->hw.fifo_size = 32;

3.调用codec_dai->ops->startup函数。

if (codec_dai->ops->startup) 
{
    ret = codec_dai->ops->startup(substream, codec_dai);
    if (ret < 0) {
        printk(KERN_ERR "asoc: can't open codec %s\n",
            codec_dai->name);
        goto codec_dai_err;
    }
}

codec_dai = uda134x_dai,codec_dai->ops->startup = uda134x_startup,分析一下uda134x_startup函数。

static int uda134x_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_device *socdev = rtd->socdev;
	struct snd_soc_codec *codec = socdev->card->codec;
	struct uda134x_priv *uda134x = codec->private_data;
	struct snd_pcm_runtime *master_runtime;

	if (uda134x->master_substream) {
		master_runtime = uda134x->master_substream->runtime;

		pr_debug("%s constraining to %d bits at %d\n", __func__,
			 master_runtime->sample_bits,
			 master_runtime->rate);

		snd_pcm_hw_constraint_minmax(substream->runtime,
					     SNDRV_PCM_HW_PARAM_RATE,
					     master_runtime->rate,
					     master_runtime->rate);

		snd_pcm_hw_constraint_minmax(substream->runtime,
					     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
					     master_runtime->sample_bits,
					     master_runtime->sample_bits);

		uda134x->slave_substream = substream;
	} else
		uda134x->master_substream = substream;

	//
	uda134x_write(codec, 2, 2|(5U<<2));
	return 0;
}

设置采样率

snd_pcm_hw_constraint_minmax(substream->runtime,SNDRV_PCM_HW_PARAM_RATE,
 master_runtime->rate,master_runtime->rate);

设置采样位数,8位或者16位或者其他

snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,master_runtime->sample_bits,
master_runtime->sample_bits);

设置mic的灵敏度,以及录制通道

uda134x_write(codec, 2, 2|(5U<<2));

Linux音频驱动之三:pcm接口的调用流程_第3张图片
灵敏度设置的是21,录制通道是2

4.调用machine->ops->startup函数
machine = s3c24xx_uda134x_dai_link,machine->ops->startup = s3c24xx_uda134x_startup
这个函数的作用是通过pclk和xtal得到采样率。

for (i = 0; i < 2; i++) 
{
    int fs = i ? 256 : 384;

    rates[i*33] = clk_get_rate(xtal) / fs;
    for (j = 1; j < 33; j++)
        rates[i*33 + j] = clk_get_rate(pclk) / (j * fs);
}

5.根据codec_dai和cpu_dai重新计算runtime->hw的一些值,需要同时满足codec_dai和cpu_dai

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 
{
    runtime->hw.rate_min =max(codec_dai->playback.rate_min,cpu_dai->playback.rate_min);
    runtime->hw.rate_max =min(codec_dai->playback.rate_max,cpu_dai->playback.rate_max);
    runtime->hw.channels_min =max(codec_dai->playback.channels_min,cpu_dai->playback.channels_min);
    runtime->hw.channels_max =min(codec_dai->playback.channels_max,cpu_dai->playback.channels_max);
    runtime->hw.formats =codec_dai->playback.formats & cpu_dai->playback.formats;
    runtime->hw.rates =codec_dai->playback.rates & cpu_dai->playback.rates;
}
  • 调用snd_pcm_hw_constraints_complete函数
    重新设置SNDRV_PCM_HW_PARAM_ACCESS,SNDRV_PCM_HW_PARAM_FORMAT,SNDRV_PCM_HW_PARAM_CHANNELS等参数,设置到runtime.hw_constraints.rules里面去。

你可能感兴趣的:(linux内核驱动)