5.打开设备(第一部分)

在文件相关的章节中,已经介绍过打开文件的过程。但还没介绍完,这一章将继续这个话题,具体看看还有哪些操作。

 5.打开设备(第一部分)_第1张图片

打开一个设备,主线上的函数大概有这么多。这些函数基本都在pcm_native.c中,azx_pcm_open是例外,其实它被设置在了snd_pcm_substream对象中。这种实现方法其实就是设计模式中所说的模板方法,基本框架已经建立好了,具体实现留给子类去处理,不过c 语言中是通过函数指针去实现的。

这一部分,我们要追踪的内容将以这些函数为主线,依次来看它们的具体功能。

5.1 snd_pcm_playback_open

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
	struct snd_pcm *pcm;
	int err = nonseekable_open(inode, file);
	if (err < 0)
		return err;
	pcm = snd_lookup_minor_data(iminor(inode),
				    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
	err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
	if (pcm)
		snd_card_unref(pcm->card);
	return err;
}

这个函数之前已经介绍过,它将通过snd_lookup_minor_data从snd_minors中获取之前保存的snd_pcm对象。下一步就是调用snd_pcm_open了。另外如果是打开录制设备,过程是类似的:

static int snd_pcm_capture_open(struct inode *inode, struct file *file)
{
	struct snd_pcm *pcm;
	int err = nonseekable_open(inode, file);
	if (err < 0)
		return err;
	pcm = snd_lookup_minor_data(iminor(inode),
				    SNDRV_DEVICE_TYPE_PCM_CAPTURE);
	err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
	if (pcm)
		snd_card_unref(pcm->card);
	return err;
}

差别只是传入的参数是SNDRV_PCM_STREAM_CAPTURE。

5.2 snd_pcm_open

static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
{
	int err;
	wait_queue_entry_t wait;

	if (pcm == NULL) {
		err = -ENODEV;
		goto __error1;
	}
    // file 将被保存到snd_card对象
	err = snd_card_file_add(pcm->card, file);
	if (err < 0)
		goto __error1;
	if (!try_module_get(pcm->card->module)) {
		err = -EFAULT;
		goto __error2;
	}
	init_waitqueue_entry(&wait, current);
	add_wait_queue(&pcm->open_wait, &wait);
	mutex_lock(&pcm->open_mutex);
	while (1) {
		err = snd_pcm_open_file(file, pcm, stream);
		if (err >= 0)
			break;
		if (err == -EAGAIN) {
			if (file->f_flags & O_NONBLOCK) {
				err = -EBUSY;
				break;
			}
		} else
			break;
		set_current_state(TASK_INTERRUPTIBLE);
		mutex_unlock(&pcm->open_mutex);
		schedule();
		mutex_lock(&pcm->open_mutex);
		if (pcm->card->shutdown) {
			err = -ENODEV;
			break;
		}
		if (signal_pending(current)) {
			err = -ERESTARTSYS;
			break;
		}
	}
	remove_wait_queue(&pcm->open_wait, &wait);
	mutex_unlock(&pcm->open_mutex);
	if (err < 0)
		goto __error;
	return err;

      __error:
	module_put(pcm->card->module);
      __error2:
      	snd_card_file_remove(pcm->card, file);
      __error1:
      	return err;
}

这段代码的主要作用是应对错误的处理。当err == -EAGAIN,非block打开的模式下,将多次尝试打开文件。

5.3 snd_pcm_open_file

static int snd_pcm_open_file(struct file *file,
			     struct snd_pcm *pcm,
			     int stream)
{
	struct snd_pcm_file *pcm_file;
	struct snd_pcm_substream *substream;
	int err;

	err = snd_pcm_open_substream(pcm, stream, file, &substream);
	if (err < 0)
		return err;

	pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);
	if (pcm_file == NULL) {
		snd_pcm_release_substream(substream);
		return -ENOMEM;
	}
	pcm_file->substream = substream;
	if (substream->ref_count == 1)
		substream->pcm_release = pcm_release_private;
	file->private_data = pcm_file;

	return 0;
}

这段函数里,我们关注一下snd_pcm_flie。

struct snd_pcm_file {
	struct snd_pcm_substream *substream;
	int no_compat_mmap;
	unsigned int user_pversion;	/* supported protocol version */
};

从snd_pcm_open_substream获取的substream,将被保存到这个结构里。然后它将被保存到file的private_data中:file->private_data = pcm_file。这样后续的读写操作就不需要再次查找substream对象了。

5.4 snd_pcm_open_substream

int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
			   struct file *file,
			   struct snd_pcm_substream **rsubstream)
{
	struct snd_pcm_substream *substream;
	int err;

	err = snd_pcm_attach_substream(pcm, stream, file, &substream);
	if (err < 0)
		return err;
	if (substream->ref_count > 1) {
		*rsubstream = substream;
		return 0;
	}

	err = snd_pcm_hw_constraints_init(substream);
	if (err < 0) {
		pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");
		goto error;
	}

	err = substream->ops->open(substream);
	if (err < 0)
		goto error;

	substream->hw_opened = 1;

	err = snd_pcm_hw_constraints_complete(substream);
	if (err < 0) {
		pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");
		goto error;
	}

	/* automatically set EXPLICIT_SYNC flag in the managed mode whenever
	 * the DMA buffer requires it
	 */
	if (substream->managed_buffer_alloc &&
	    substream->dma_buffer.dev.need_sync)
		substream->runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC;

	*rsubstream = substream;
	return 0;

 error:
	snd_pcm_release_substream(substream);
	return err;
}

这里涉及到的几个函数,我们都会介绍到。就这个函数而言,目的是获取substream对象,并对它进行必要的赋值,它是后续读写等操作的对象。

5.5 snd_pcm_attach_substream

int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
			     struct file *file,
			     struct snd_pcm_substream **rsubstream)
{
	struct snd_pcm_str * pstr;
	struct snd_pcm_substream *substream;
	struct snd_pcm_runtime *runtime;
	struct snd_card *card;
	int prefer_subdevice;
	size_t size;

	if (snd_BUG_ON(!pcm || !rsubstream))
		return -ENXIO;
	if (snd_BUG_ON(stream != SNDRV_PCM_STREAM_PLAYBACK &&
		       stream != SNDRV_PCM_STREAM_CAPTURE))
		return -EINVAL;
	*rsubstream = NULL;
//  根据stream来获取snd_pcm_str,stream只能是SNDRV_PCM_STREAM_PLAYBACK或者 SNDRV_PCM_STREAM_CAPTURE
	pstr = &pcm->streams[stream];
	if (pstr->substream == NULL || pstr->substream_count == 0)
		return -ENODEV;

	card = pcm->card;
// 从打开的control文件中获取当前的substream是否已经打开
	prefer_subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_PCM);

// 半双工的设备中,不能同时录制和播放。只能选择其中一种。
	if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
		int opposite = !stream;

		for (substream = pcm->streams[opposite].substream; substream;
		     substream = substream->next) {
			if (SUBSTREAM_BUSY(substream))
				return -EAGAIN;
		}
	}
// 针对打开文件选项中追加模式的处理,这时候文件已经打开过了。其实直接返回就可以了,但是要处理多个substream的情况
	if (file->f_flags & O_APPEND) {
		if (prefer_subdevice < 0) {
			if (pstr->substream_count > 1)
				return -EINVAL; /* must be unique */
			substream = pstr->substream;
		} else {
			for (substream = pstr->substream; substream;
			     substream = substream->next)
				if (substream->number == prefer_subdevice)
					break;
		}
		if (! substream)
			return -ENODEV;
		if (! SUBSTREAM_BUSY(substream))
			return -EBADFD;
		substream->ref_count++;
		*rsubstream = substream;
		return 0;
	}

//选择对应的substream
	for (substream = pstr->substream; substream; substream = substream->next) {
		if (!SUBSTREAM_BUSY(substream) &&
		    (prefer_subdevice == -1 ||
		     substream->number == prefer_subdevice))
			break;
	}
	if (substream == NULL)
		return -EAGAIN;
// 生成snd_pcm_runtime对象
	runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
	if (runtime == NULL)
		return -ENOMEM;
// 分配status
	size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
	runtime->status = alloc_pages_exact(size, GFP_KERNEL);
	if (runtime->status == NULL) {
		kfree(runtime);
		return -ENOMEM;
	}
	memset(runtime->status, 0, size);

// 分配control
	size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
	runtime->control = alloc_pages_exact(size, GFP_KERNEL);
	if (runtime->control == NULL) {
		free_pages_exact(runtime->status,
			       PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
		kfree(runtime);
		return -ENOMEM;
	}
	memset(runtime->control, 0, size);

	init_waitqueue_head(&runtime->sleep);
	init_waitqueue_head(&runtime->tsleep);

	runtime->status->state = SNDRV_PCM_STATE_OPEN;
	mutex_init(&runtime->buffer_mutex);
// 将snd_pcm_runtime对象赋予substream
	substream->runtime = runtime;
// 指向具体驱动中生成的对象,hda设备中指向的是azx_pcm
	substream->private_data = pcm->private_data;
	substream->ref_count = 1;
	substream->f_flags = file->f_flags;
	substream->pid = get_pid(task_pid(current));
	pstr->substream_opened++;
	*rsubstream = substream;
	return 0;
}

熟悉这段代码,先再看看snd_pcm_str。

struct snd_pcm_str {
	int stream;				/* stream (direction) */
	struct snd_pcm *pcm;
	/* -- substreams -- */
	unsigned int substream_count;
	unsigned int substream_opened;
	struct snd_pcm_substream *substream;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	/* -- OSS things -- */
	struct snd_pcm_oss_stream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
	struct snd_info_entry *proc_root;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
	unsigned int xrun_debug;	/* 0 = disabled, 1 = verbose, 2 = stacktrace */
#endif
#endif
	struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
	struct device dev;
};

这里有一个substream_count,一般这个值都是1。表示一个设备中播放或者录制使用过的stream是一条,但在一些情景下,它可以是多个。比如HDA中支持多SDI模式,录音的时候可以有多路音频数据返回。用处我不是太明白,在回声消除的处理中,不同方位录制的声音,时间上有差值,可以用来消除回声。理解可能和这个有关系。

当substream_cout的数量超过1的时候,就不能单单通过对pcm文件的操作来完成了。需要control文件的配合,这就是要使用snd_ctl_get_preferred_subdevice的原因。

另外可以注意下status与control的分配:

size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));

runtime->status = alloc_pages_exact(size, GFP_KERNEL);

status 和 control可以通过mmap的方式分享给用户空间,mmap的最小单位是页,所以这里分配空间有点特殊。它们的用法,将在介绍播放音频的时候,详细介绍。

你可能感兴趣的:(alsa驱动解析,linux,linux内核,音频,驱动开发,音视频)