Linux ALSA驱动框架分析之(二):pcm逻辑设备的创建

Linux ALSA驱动框架分析之(一):架构介绍
Linux ALSA驱动框架分析之(二):pcm逻辑设备的创建
Linux ALSA驱动框架分析之(三):Control逻辑设备的创建

pcm设备的主要功能就是:

  1. playback,把用户空间的应用程序发过来的PCM数据进行D/A转换,转化为人耳可以辨别的模拟音频。
  2. capture,把麦克风或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号。

声卡可以包含多个pcm设备。一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
在这里插入图片描述
snd_pcm结构体描述一个pcm设备,定义如下:

struct snd_pcm {
	struct snd_card *card;      //所属声卡
	struct list_head list;  
	int device;               /* pcm设备的编号 */
	......
	char id[64];
	char name[80];

	//streams[0]为playback(播放),streams[1]为capture(录音)
	struct snd_pcm_str streams[2];

	......

};


struct snd_pcm_str {
	int stream;				/* 0为playback,1为capture */
	struct snd_pcm *pcm;

	/* -- substreams -- */
	unsigned int substream_count;  //substreams的个数
	unsigned int substream_opened;
	struct snd_pcm_substream *substream; //子流

	......

	struct device dev;
};

snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是它的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理,它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这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) */
	
	size_t buffer_bytes_max;	/* limit ring buffer size */

	struct snd_dma_buffer dma_buffer;

	size_t dma_max;

	/* 由具体驱动设置ops */
	const struct snd_pcm_ops *ops;

	/* pcm设备运行时的信息 */
	struct snd_pcm_runtime *runtime;

	......
};

驱动程序可调用snd_pcm_set_ops函数来设置snd_pcm_substream里的ops字段。

调用snd_pcm_new函数创建声卡的控制逻辑设备:

/* card,声卡实例
 * id,pcm设备的标识符
 * device,表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始
 * playback_count,表示该pcm将会有几个playback substream
 * capture_count,表示该pcm将会有几个capture substream
 */
int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
{
	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 ,
		struct snd_pcm **rpcm)
{
	struct snd_pcm *pcm;
	int err;
	//这个snd_device_ops不同类型的逻辑设备,其回调函数是不一样的
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};

	......

	//创建一个snd_pcm 
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	if (!pcm)
		return -ENOMEM;

	//设置snd_pcm 
	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)
		strlcpy(pcm->id, id, sizeof(pcm->id));

	//创建playback的substreams
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}

	//创建capture的substreams
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	
	//创建snd_device,并挂入snd_card的devices链表
	if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if (rpcm)
		*rpcm = pcm;
	return 0;
}

snd_device_new函数:

int snd_device_new(struct snd_card *card, enum snd_device_type type,
		   void *device_data, struct snd_device_ops *ops)
{
	struct snd_device *dev;
	struct list_head *p;

	......

	//创建一个snd_device 
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	//设置snd_device 
	INIT_LIST_HEAD(&dev->list);
	dev->card = card;
	dev->type = type;
	 
	//snd_device的state设置为SNDRV_DEV_BUILD,表示还未注册
	dev->state = SNDRV_DEV_BUILD; 
	dev->device_data = device_data;
	dev->ops = ops;

	/* 把snd_device挂入snd_card的devices链表  */
	list_for_each_prev(p, &card->devices) {
		struct snd_device *pdev = list_entry(p, struct snd_device, list);
		if ((unsigned int)pdev->type <= (unsigned int)type)
			break;
	}

	list_add(&dev->list, p);
	return 0;
}

snd_pcm是挂在snd_card下面的一个snd_device,来个图:
在这里插入图片描述
在声卡的注册阶段( snd_card_register),会调用snd_device_register_all函数注册声卡的所有逻辑设备,最终会调用到snd_device->snd_device_pos的dev_register回调,对于pcm逻辑设备该回调设置为snd_pcm_dev_register。
来个时序图:
在这里插入图片描述
snd_pcm_dev_register函数定义如下:

static int snd_pcm_dev_register(struct snd_device *device)
{
	int cidx, err;
	struct snd_pcm_substream *substream;
	struct snd_pcm_notify *notify;
	struct snd_pcm *pcm;

	......

	mutex_lock(&register_mutex);
	err = snd_pcm_add(pcm);
	if (err)
		goto unlock;
	for (cidx = 0; cidx < 2; cidx++) {
		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;
		}
		/* 注册snd_device */
		err = snd_register_device(devtype, pcm->card, pcm->device,
					  &snd_pcm_f_ops[cidx], pcm,
					  &pcm->streams[cidx].dev);
		......
		}

		......
	}

	......
}

调用snd_register_device函数注册snd_device:

/* 参数说明:
 * type,逻辑设备的类型
 * card,逻辑设备所属的snd_card 
 * f_ops,文件操作集
 * private_data,私有数据
 * device,逻辑设备对应的struct device 
 */
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;

	......

	//创建一个snd_minor 
	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	if (preg == NULL)
		return -ENOMEM;

	//设置snd_minor 
	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);

	//找到一个次设备号
	minor = snd_find_free_minor(type, card, dev);
	......

	preg->dev = device;

	//主设备号为116
	device->devt = MKDEV(major, minor);

	//会在/dev/snd/目录下创建pcm逻辑设备的设备文件,如pcmC0D0c...
	err = device_add(device);
	......

	/* snd_minor为struct snd_minor *类型的全局数组,用于保存系统中的snd_minor
      之后可以次设备号为下标在该数组中找到对应的snd_minor
   */
	snd_minors[minor] = preg;
	......

}

在sound/sound.c中定义了一个snd_minor指针的全局数组:


#define SNDRV_OS_MINORS			256
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];  /*  sound/core/sound.c*/

snd_minor结构体定义如下:

struct snd_minor {
	int type;			/* 逻辑设备的类型*/
	int card;			/* 声卡的编号 */
	int device;			/* device number */
	const struct file_operations *f_ops;	/* 文件操作集 */
	void *private_data;		
	struct device *dev;		/* 逻辑设备的device  */
	struct snd_card *card_ptr;	/* 声卡实例 */
};

来个图:
在这里插入图片描述
声卡设备的主设备号都是116,声卡的不同逻辑设备,其次设备号是不同的。注册一个逻辑设备会创建一个对应的snd_minor,里面有file_operations,应用层open、read、write等最终会调用到snd_minor里file_operations对应的回调函数。

对于一个pcm设备,snd_pcm_dev_register函数执行后,会生成两个设备文件,一个用于playback,一个用于capture,对应的file_operations:

const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.write_iter =		snd_pcm_writev,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.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 =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.read_iter =		snd_pcm_readv,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.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 =	snd_pcm_get_unmapped_area,
	}
};

应用层open、read、write最终是怎么样调用到snd_minor里file_operations对应的回调函数的呢?

在sound/core/sound.c文件中会注册字符设备:

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};

static int __init alsa_sound_init(void)
{
	snd_major = major;
	snd_ecards_limit = cards_limit;

	//注册字符设备,对应的file_operations为snd_fops 
	if (register_chrdev(major, "alsa", &snd_fops)) {
		pr_err("ALSA core: unable to register native major device number %d\n", major);
		return -EIO;
	}
	......
	return 0;
}

来看看snd_open函数:

static int snd_open(struct inode *inode, struct file *file)
{
	//得到次设备
	unsigned int minor = iminor(inode);
	struct snd_minor *mptr = NULL;
	const struct file_operations *new_fops;
	int err = 0;

	if (minor >= ARRAY_SIZE(snd_minors))
		return -ENODEV;
	mutex_lock(&sound_mutex);
	
	//以次设备号为下标在snd_minors数组中找到对应的snd_minor 
	mptr = snd_minors[minor];
	......

	//从snd_minor得到file_operations 
	new_fops = fops_get(mptr->f_ops);
	mutex_unlock(&sound_mutex);
	......

	//替换原来的file_operations 
	replace_fops(file, new_fops);

	if (file->f_op->open)
		err = file->f_op->open(inode, file); 
	return err;
}

应用层打开一个pcm设备:
在这里插入图片描述
进行录音或播放,ioctl("/dev/snd/pcmC0D0",…),最终会调用到:
snd_pcm_substream->snd_pcm_ops->ioctl,进行硬件上的操作。snd_pcm_substream的snd_pcm_ops由驱动程序调用snd_pcm_set_ops函数进行设置:

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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值