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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1、基本概念及逻辑关系

        如上图通过上一节声卡的学习我们已经知道PCM是声卡的一个子设备或者表示一个PCM实例。

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

        一个pcm实例由一个playback stream和一个capture stream组成这两个stream又分别有一个或多个substreams组成。可以用如下图来表示他们直接的逻辑关系

        当一个子流已经存在并且已经被打开当再次被打开的时候会被阻塞。        

        在实际的应用中通常不会如上图这么复杂大多数情况下是一个声卡有一个PCM实例PCM下面有一个playback和capture而playback和capture各自有一个substream。

        PCM层有几个很重要的结构体我们通过如下的UML图来梳理他们直接的关系。

         图片地址http://hi.csdn.net/attachment/201104/2/0_1301728746sAUd.gif

        1、snd_pcm挂在snd_card下面的一个snd_device。

        2、snd_pcm中的字段streams[2]该数组中的两个元素指向两个snd_pcm_str结构分别代表playback stream和capture stream。

        3、snd_pcm_str中的substream字段指向snd_pcm_substream结构。

        4、snd_pcm_substream是pcm中间层的核心绝大部分任务都是在substream中处理尤其是他的opssnd_pcm_ops字段许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。它的runtime字段则指向snd_pcm_runtime结构snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数。

2、PCM创建流程

        PCM的整个创建流程请参考如下时序图进行理解

         alsa-driver的中间层已经提供新建PCM的API

2.1、创建PCM实例

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

        card表示所属的声卡。

        IDPCM实例的ID名字。

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

        playback_count表示该PCM播放流中将会有几个substream。

        capture_count 表示该PCM录音流中将会有几个substream。

        rpcm返回的PCM实例。

        该函数的主要作用是创建PCM逻辑设备创建回放子流和录制子流实例并初始化回放子流和录制子流的PCM操作函数数据搬运时需要调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备工作。

2.2、设置PCM设备的操作函数

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

        pcm上述snd_pcm_new 创建的PCM实例。

        direction是指SNDRV_PCM_STREAM_PLAYBACK或SNDRV_PCM_STREAM_CAPTURE即设置为播放或者录音功能。

        snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数。

2.3、定义PCM的操作函数

        以AC97驱动linux/sound/arm/pxa2xx-ac97.c为例在驱动中对于PCM进行了如下设置

static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = {
	.open		= pxa2xx_ac97_pcm_open,
	.close		= pxa2xx_ac97_pcm_close,
	.hw_params	= pxa2xx_pcm_hw_params,
	.prepare	= pxa2xx_ac97_pcm_prepare,
	.trigger	= pxa2xx_pcm_trigger,
	.pointer	= pxa2xx_pcm_pointer,
};

snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pxa2xx_ac97_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pxa2xx_ac97_pcm_ops);

2.4、定义硬件参数

static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
	.info			= SNDRV_PCM_INFO_MMAP |
				  SNDRV_PCM_INFO_MMAP_VALID |
				  SNDRV_PCM_INFO_INTERLEAVED |
				  SNDRV_PCM_INFO_PAUSE |
				  SNDRV_PCM_INFO_RESUME,
	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
				  SNDRV_PCM_FMTBIT_S24_LE |
				  SNDRV_PCM_FMTBIT_S32_LE,
	.period_bytes_min	= 32,
	.period_bytes_max	= 8192 - 32,
	.periods_min		= 1,
	.periods_max		= 256,
	.buffer_bytes_max	= 128 * 1024,
	.fifo_size		= 32,
};

int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_dmaengine_dai_dma_data *dma_params;
	int ret;

	runtime->hw = pxa2xx_pcm_hardware;

	dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
	if (!dma_params)
		return 0;

	/*
	 * For mysterious reasons (and despite what the manual says)
	 * playback samples are lost if the DMA count is not a multiple
	 * of the DMA burst size.  Let's add a rule to enforce that.
	 */
	ret = snd_pcm_hw_constraint_step(runtime, 0,
		SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
	if (ret)
		return ret;

	ret = snd_pcm_hw_constraint_step(runtime, 0,
		SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
	if (ret)
		return ret;

	ret = snd_pcm_hw_constraint_integer(runtime,
					    SNDRV_PCM_HW_PARAM_PERIODS);
	if (ret < 0)
		return ret;

	return snd_dmaengine_pcm_open(
		substream, dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev,
						     dma_params->chan_name));
}

3、PCM相关源码分析

3.1、snd_pcm_new

/**
 * snd_pcm_new - create a new PCM instance
 * @card: the card instance
 * @id: the id string
 * @device: the device index (zero based)
 * @playback_count: the number of substreams for playback
 * @capture_count: the number of substreams for capture
 * @rpcm: the pointer to store the new pcm instance
 *
 * Creates a new PCM instance.
 *
 * The pcm operators have to be set afterwards to the new instance
 * via snd_pcm_set_ops().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
{
    /* 直接调用函数_snd_pcm_new参数internal传入false */
	return _snd_pcm_new(card, id, device, playback_count, capture_count,
			false, rpcm);
}

static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, bool internal,
		struct snd_pcm **rpcm)
{
	struct snd_pcm *pcm;
	int err;
    /* 1. 逻辑设备的操作函数结构体, 主要用于注册子设备 */
	static const struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
	static const struct snd_device_ops internal_ops = {
		.dev_free = snd_pcm_dev_free,
	};

	if (snd_BUG_ON(!card))
		return -ENXIO;
	if (rpcm)
		*rpcm = NULL;
    /* 2. 为snd_pcm结构体分配空间根据传入参数赋值 */
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	if (!pcm)
		return -ENOMEM;
	pcm->card = card;
	pcm->device = device;
	pcm->internal = internal;
	mutex_init(&pcm->open_mutex);
	init_waitqueue_head(&pcm->open_wait);
	INIT_LIST_HEAD(&pcm->list);
	if (id)
		strscpy(pcm->id, id, sizeof(pcm->id));

    /* 3. 根据传入的playback和capture的个数创建PCM流 snd_pcm_str */
	err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
				 playback_count);
	if (err < 0)
		goto free_pcm;

	err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);
	if (err < 0)
		goto free_pcm;

    /* 4. 创建一个PCM逻辑设备创建逻辑设备并添加到逻辑设备链表 */
	err = snd_device_new(card, SNDRV_DEV_PCM, pcm,
			     internal ? &internal_ops : &ops);
	if (err < 0)
		goto free_pcm;

	if (rpcm)
		*rpcm = pcm;
	return 0;

free_pcm:
	snd_pcm_free(pcm);
	return err;
}

3.2、snd_pcm

struct snd_pcm {
	struct snd_card *card;
	struct list_head list;
	int device; /* device number */
	unsigned int info_flags;
	unsigned short dev_class;
	unsigned short dev_subclass;
	char id[64];
	char name[80];
	struct snd_pcm_str streams[2];
	struct mutex open_mutex;
	wait_queue_head_t open_wait;
	void *private_data;
	void (*private_free) (struct snd_pcm *pcm);
	bool internal; /* pcm is for internal use only */
	bool nonatomic; /* whole PCM operations are in non-atomic context */
	bool no_device_suspend; /* don't invoke device PM suspend */
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	struct snd_pcm_oss oss;
#endif
};

         这里重要的变量有两个streams与private_data。streams有两个是因为一个指向播放设备一个指向录音设备。private_data在很多结构里都可以看到和面象对象里的继承有点类似如果将snd_pcm理解为基类的话private_data指向的就是它的继承类也就是真正的实现者。

        list在pcm.c中有一个全局变量snd_pcm_devices将所有的snd_pcm对象链接起来目的是外部提供一些可供枚举所有设备的接口看起来并不怎么被用到。

        另外还有info_flags、dev_class等变量看起来是为一些特殊设备预留的对待一些特殊操作。

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;
};

         snd_pcm_str的主要作用是指向snd_pcm_substream而snd_pcm_substream可以有多个这也是snd_pcm_str存在的原因否则snd_pcm直接指向snd_pcm_substream就可以了。

        这里的dev是将pcm加入到文件系统时要用到。包含的信息在下面介绍的snd_pcm_new_stream中会看到。

3.3、snd_pcm_new_stream

/**
 * snd_pcm_new_stream - create a new PCM stream
 * @pcm: the pcm instance
 * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
 * @substream_count: the number of substreams
 *
 * Creates a new stream for the pcm.
 * The corresponding stream on the pcm must have been empty before
 * calling this, i.e. zero must be given to the argument of
 * snd_pcm_new().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
	int idx, err;
    /* 3.1 根据传入的参数为PCM流snd_pcm_str赋值方向所属的PCMPCM子流的个数 */
	struct snd_pcm_str *pstr = &pcm->streams[stream];
	struct snd_pcm_substream *substream, *prev;

#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	mutex_init(&pstr->oss.setup_mutex);
#endif
	pstr->stream = stream;
	pstr->pcm = pcm;
	pstr->substream_count = substream_count;
	if (!substream_count)
		return 0;

	snd_device_initialize(&pstr->dev, pcm->card);
	pstr->dev.groups = pcm_dev_attr_groups;
	pstr->dev.type = &pcm_dev_type;
	dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
		     stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');

    /* proc */
	if (!pcm->internal) {
		err = snd_pcm_stream_proc_init(pstr);
		if (err < 0) {
			pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
			return err;
		}
	}
	prev = NULL;
	for (idx = 0, prev = NULL; idx < substream_count; idx++) {
        /* 为子流分配空间赋值pcmpcm流ID 方向..... */
		substream = kzalloc(sizeof(*substream), GFP_KERNEL);
		if (!substream)
			return -ENOMEM;
		substream->pcm = pcm;
		substream->pstr = pstr;
		substream->number = idx;
		substream->stream = stream;
		sprintf(substream->name, "subdevice #%i", idx);
		substream->buffer_bytes_max = UINT_MAX;
        /* 添加子流到子流的链表 */
		if (prev == NULL)    /* 第一个子流 */
			pstr->substream = substream;
		else
			prev->next = substream;    /* 非第一个子流添加到前一个子流后部 */
        /* proc */
		if (!pcm->internal) {
			err = snd_pcm_substream_proc_init(substream);
			if (err < 0) {
				pcm_err(pcm,
					"Error in snd_pcm_stream_proc_init\n");
				if (prev == NULL)
					pstr->substream = NULL;
				else
					prev->next = NULL;
				kfree(substream);
				return err;
			}
		}
        /* 结构体初始化 */
		substream->group = &substream->self_group;
		snd_pcm_group_init(&substream->self_group);
		list_add_tail(&substream->link_list, &substream->self_group.substreams);
		atomic_set(&substream->mmap_count, 0);
		prev = substream;
	}
	return 0;
}

         函数参数中的int stream是一个枚举类型 

enum {
	SNDRV_PCM_STREAM_PLAYBACK = 0,
	SNDRV_PCM_STREAM_CAPTURE,
	SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE,
};

        从snd_device_initialize(&pstr->dev, pcm->card); 开始。dev最终会被传入device_add函数中用来构建文件系统。

void snd_device_initialize(struct device *dev, struct snd_card *card)
{
	device_initialize(dev);
	if (card)
		dev->parent = &card->card_dev;
	dev->class = sound_class;
	dev->release = default_release;
}

        这段函数中可以看到dev->class被设置成sound_class这个是我们之前提到的文件放到snd目录的原因。

3.4、snd_pcm_substream

struct snd_pcm_substream {
	struct snd_pcm *pcm;
	struct snd_pcm_str *pstr;
	void *private_data;		/* copied from pcm->private_data */
	int number;
	char name[32];			/* substream name */
	int stream;			/* stream (direction) */
	struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
	size_t buffer_bytes_max;	/* limit ring buffer size */
	struct snd_dma_buffer dma_buffer;
	size_t dma_max;
	/* -- hardware operations -- */
	const struct snd_pcm_ops *ops;
	/* -- runtime information -- */
	struct snd_pcm_runtime *runtime;
        /* -- timer section -- */
	struct snd_timer *timer;		/* timer */
	unsigned timer_running: 1;	/* time is running */
	long wait_time;	/* time in ms for R/W to wait for avail */
	/* -- next substream -- */
	struct snd_pcm_substream *next;
	/* -- linked substreams -- */
	struct list_head link_list;	/* linked list member */
	struct snd_pcm_group self_group;	/* fake group for non linked substream (with substream lock inside) */
	struct snd_pcm_group *group;		/* pointer to current group */
	/* -- assigned files -- */
	int ref_count;
	atomic_t mmap_count;
	unsigned int f_flags;
	void (*pcm_release)(struct snd_pcm_substream *);
	struct pid *pid;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	/* -- OSS things -- */
	struct snd_pcm_oss_substream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
	struct snd_info_entry *proc_root;
#endif /* CONFIG_SND_VERBOSE_PROCFS */
	/* misc flags */
	unsigned int hw_opened: 1;
	unsigned int managed_buffer_alloc:1;
};

        snd_pcm_substream的内容有些多此处只需要重要的进行介绍。

        private_data从snd_pcm中的private_data拷贝过来的指向实现者的结构。

        const struct snd_pcm_ops *ops这部分是框架的内容具体的操作需要实现者的参与留给实现者的函数指针集。这个和文件操作的设计策略是一致的。

        struct snd_pcm_runtime *runtime读写数据的时候由它来控制。到分析读写代码的时候会重点关注它。

        struct snd_pcm_substream *next将多个snd_pcm_substream对象链接起来它就是snd_pcm_str指向的链接。

        group在用户空间可以通过SNDRV_PCM_IOCTL_LINK将多个substream链接起来。然后就可以对这些对象进行统一的操作。我没遇到过具体的应用场景。

3.5、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)
{
	struct snd_pcm_str *stream = &pcm->streams[direction];
	struct snd_pcm_substream *substream;
	
	for (substream = stream->substream; substream != NULL; substream = substream->next)
		substream->ops = ops;
}
EXPORT_SYMBOL(snd_pcm_set_ops);

        此函数是提供给调用侧使用的。设置的内容可以参考pcm文件结构简图。 

3.6、snd_pcm_dev_register

        在继续分析snd_pcm_dev_register函数之前需要先介绍一个结构体。struct snd_minor。

struct snd_minor {
	int type;			/* SNDRV_DEVICE_TYPE_XXX */
	int card;			/* card number */
	int device;			/* device number */
	const struct file_operations *f_ops;	/* file operations */
	void *private_data;		/* private data for f_ops->open */
	struct device *dev;		/* device for sysfs */
	struct snd_card *card_ptr;	/* assigned card instance */
};

        type: 设备类型比如是pcm, control, timer等设备。

        card_number: 所属的card。

        device: 当前设备类型下的设备编号。

        f_ops: 具体设备的文件操作集合。

        private_data: open函数的私有数据。

        card_ptr: 所属的card。

        此结构体是用来保存当前设备的上下文信息该card下所有逻辑设备都存在此结构。

static int snd_pcm_dev_register(struct snd_device *device)
{
    /* 1、添加pcm结构体到全局链表snd_pcm_devices */
	int cidx, err;
	struct snd_pcm_substream *substream;
	struct snd_pcm *pcm;

	if (snd_BUG_ON(!device || !device->device_data))
		return -ENXIO;
    /* snd_devcie保存的是snd_pcm对象 */
	pcm = device->device_data;

	mutex_lock(&register_mutex);
    /* snd_pcm对象将被保存到全局变量snd_pcm_devices中用于枚举设备等操作 */
	err = snd_pcm_add(pcm);
	if (err)
		goto unlock;
	for (cidx = 0; cidx < 2; cidx++) {
        /* 2、确定PCM设备节点名字 */
		int devtype = -1;
		if (pcm->streams[cidx].substream == NULL)
			continue;
		switch (cidx) {
		case SNDRV_PCM_STREAM_PLAYBACK:
			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
			break;
		case SNDRV_PCM_STREAM_CAPTURE:
			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
			break;
		}
		/* register pcm */
        /* 将设备添加到文件系统,将snd_pcm_f_ops传入将被设置给snd_minor对象 */
		err = snd_register_device(devtype, pcm->card, pcm->device,
					  &snd_pcm_f_ops[cidx], pcm,
					  &pcm->streams[cidx].dev);
		if (err < 0) {
			list_del_init(&pcm->list);
			goto unlock;
		}

		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
            /* 设定CONFIG_SND_PCM_TIMER宏的时候会去设置substream的时间 */
			snd_pcm_timer_init(substream);
	}

	pcm_call_notify(pcm, n_register);

 unlock:
	mutex_unlock(&register_mutex);
	return err;
}

/**
 * snd_register_device - Register the ALSA device file for the card
 * @type: the device type, SNDRV_DEVICE_TYPE_XXX
 * @card: the card instance
 * @dev: the device index
 * @f_ops: the file operations
 * @private_data: user pointer for f_ops->open()
 * @device: the device to register
 *
 * Registers an ALSA device file for the given card.
 * The operators have to be set in reg parameter.
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_register_device(int type, struct snd_card *card, int dev,
			const struct file_operations *f_ops,
			void *private_data, struct device *device)
{
	int minor;
	int err = 0;
	struct snd_minor *preg;

	if (snd_BUG_ON(!device))
		return -EINVAL;

	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	if (preg == NULL)
		return -ENOMEM;
    /* 创建一个snd_minor并添加到全局结构体 snd_minors */
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;
	preg->private_data = private_data;
	preg->card_ptr = card;
	mutex_lock(&sound_mutex);
    /* 4、注册一个设备节点 */
	minor = snd_find_free_minor(type, card, dev);
	if (minor < 0) {
		err = minor;
		goto error;
	}

	preg->dev = device;
	device->devt = MKDEV(major, minor);
	err = device_add(device);
	if (err < 0)
		goto error;

	snd_minors[minor] = preg;
 error:
	mutex_unlock(&sound_mutex);
	if (err < 0)
		kfree(preg);
	return err;
}

        当声卡被注册时会注册所有的逻辑设备。主要的工作是创建PCM设备节点
具体的流程

                1、添加pcm结构体到全局链表snd_pcm_devices。

                2、确定PCM设备节点名字。

                3、创建一个snd_minor并添加到全局结构体 snd_minors。

                4、注册一个设备节点

        可以看到添加到文件系统的是播放设备和录音设备根据snd_pcm_str指向的内容来设定的。代码中看到snd_pcm也被定义为SNDRV_DEV_PCM设备但是文件系统中并不会保存这个类型的设备。

        snd_pcm_timer_init是在CONFIG_SND_PCM_TIMER宏被定义的时候会起作用。

        通过下图可以帮助你更好的理解各结构直接的乱讲关系。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)” 的相关文章