Linux音频子系统(三)ALSA的核心层声卡注册

前一章节对整个ALSA的框架进行了分层,本章主要来梳理下ALSA的框架中核心层的声卡注册,对于这部分的代码我们修改的比较少,已理解流程为主,本章只完成对于声卡注册相关流程的梳理。

1. 声卡驱动初始化

static int __init alsa_sound_init(void)
{
	snd_major = major;
	snd_ecards_limit = cards_limit;
	if (register_chrdev(major, "alsa", &snd_fops)) {
		snd_printk(KERN_ERR "unable to register native major device number %d\n", major);
		return -EIO;
	}
	if (snd_info_init() < 0) {
		unregister_chrdev(major, "alsa");
		return -ENOMEM;
	}
	snd_info_minor_register();
#ifndef MODULE
	printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n");
#endif
	return 0;
}

该函数实现了几个主要的功能

  • 注册了主设备号为116的字符设备文件,文件操作是snd_fops,snd_open接口比较重要,snd_open接口通过文件节点inode得到了次设备号,通过snd_minors数组得到对应的声音逻辑设备的文件操作,调用对应的open接口,并调用fops_put替换成了对应的逻辑设备的文件操作(snd_minors里维护),后面会详细的介绍这个过程
  • snd_info_init创建proc目录下的asound,并在该目录下分别建立version EVM card0 cards device pcm timers,用来查看相关的信息。
static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
}

该snd_fops中只有open和llseek,那么应用怎么能完成对于alsa的操作呢?其实它只是完成一个中转的作用,下面看看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 *old_fops;
	int err = 0;

	if (minor >= ARRAY_SIZE(snd_minors))
		return -ENODEV;
	mutex_lock(&sound_mutex);
	mptr = snd_minors[minor];
	if (mptr == NULL) {
		mptr = autoload_device(minor);
		if (!mptr) {
			mutex_unlock(&sound_mutex);
			return -ENODEV;
		}
	}
	old_fops = file->f_op;
	file->f_op = fops_get(mptr->f_ops);
	if (file->f_op == NULL) {
		file->f_op = old_fops;
		err = -ENODEV;
	}
	mutex_unlock(&sound_mutex);
	if (err < 0)
		return err;

	if (file->f_op->open) {
		err = file->f_op->open(inode, file);
		if (err) {
			fops_put(file->f_op);
			file->f_op = fops_get(old_fops);
		}
	}
	fops_put(old_fops);
	return err;
}

该函数主要完成

1.【minor = iminor(inode)】以minor变量保存传入节点inode结构体的次设备号
2.【mptr = snd_minors[minor]】以minor为下标在结构体数组中取出一项,让mptr指针指向此项
3.【old_fops = file->f_op;file->f_op = fops_get(mptr->f_ops);】表示取出mptr结构体指针里的file_operations 结构体
4.【err = file->f_op->open(inode, file);】调用file_operations 结构体里面的open函数

那么问题的重点是,谁来配置snd_minors,那么谁就会提供新的fps。

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
				const struct file_operations *f_ops,
				void *private_data,
				const char *name, struct device *device)
{
	int minor;
	struct snd_minor *preg;

	if (snd_BUG_ON(!name))
		return -EINVAL;
	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	if (preg == NULL)
		return -ENOMEM;
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;
	preg->private_data = private_data;
	mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
	minor = snd_find_free_minor(type);
#else
	minor = snd_kernel_minor(type, card, dev);
	if (minor >= 0 && snd_minors[minor])
		minor = -EBUSY;
#endif
	if (minor < 0) {
		mutex_unlock(&sound_mutex);
		kfree(preg);
		return minor;
	}
	snd_minors[minor] = preg;
	preg->dev = device_create(sound_class, device, MKDEV(major, minor),
				  private_data, "%s", name);
	if (IS_ERR(preg->dev)) {
		snd_minors[minor] = NULL;
		mutex_unlock(&sound_mutex);
		minor = PTR_ERR(preg->dev);
		kfree(preg);
		return minor;
	}

	mutex_unlock(&sound_mutex);
	return 0;
}

该函数主要申请了一个struct snd_minor的结构体,将该结构体内type、card、device、f_ops、private_data赋值给snd_minors数组内,并在/sys/class创建设备节点。该函数要完成card、device的初始化。对于这个接口两个功能会使用,一方面在声卡注册会使用,同时对于PCM的注册也会使用。那么对于声卡驱动其流程会是什么样的?

static inline int snd_register_device(int type, struct snd_card *card, int dev,
				      const struct file_operations *f_ops,
				      void *private_data,
				      const char *name)
{
	return snd_register_device_for_dev(type, card, dev, f_ops,
					   private_data, name,
					   snd_card_get_device_link(card));
}

snd_register_device()被谁调用到control.c中

static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;
	int err, cardnum;
	char name[16];

	if (snd_BUG_ON(!card))
		return -ENXIO;
	cardnum = card->number;
	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
		return -ENXIO;
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
				       &snd_ctl_f_ops, card, name)) < 0)
		return err;
	return 0;
}

这个函数就是注册了设备的节点,其中涉及到了设备节点的创建和管理,也就是设备的/dev/snd/controlC0的节点,下面我们来认识下这个节点的操作函数snd_ctl_f_ops,也就会替换文章刚开的ops。

int snd_ctl_create(struct snd_card *card)
{
	static struct snd_device_ops ops = {
		.dev_free = snd_ctl_dev_free,
		.dev_register =	snd_ctl_dev_register,
		.dev_disconnect = snd_ctl_dev_disconnect,
	};

	if (snd_BUG_ON(!card))
		return -ENXIO;
	return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}

snd_ctl_create最终会创建card,并且将这个card添加到dev的list链表中,这个函数会被谁使用呢?就进入声卡创建的核心函数snd_card_new,这个函数只是创建了一个声卡struct snd_card,为后面注册做好准备,暂时不表代码,其主要完成的工作如下图
Linux音频子系统(三)ALSA的核心层声卡注册_第1张图片

2 声卡注册

注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数。

int snd_card_register(struct snd_card *card)
{
  	int err;
  	
  	if (snd_BUG_ON(!card))
  		return -EINVAL;
  	
  	if (!card->registered) {
  		err = device_add(&card->card_dev);
  		if (err < 0)
  			return err;
  		card->registered = true;
  	}
  	
  	if ((err = snd_device_register_all(card)) < 0)
  		return err;
  	mutex_lock(&snd_card_mutex);
  	if (snd_cards[card->number]) {
  		/* already registered */
  		mutex_unlock(&snd_card_mutex);
  		return 0;
  	}
  	if (*card->id) {
  		/* make a unique id name from the given string */
  		char tmpid[sizeof(card->id)];
  		memcpy(tmpid, card->id, sizeof(card->id));
  		snd_card_set_id_no_lock(card, tmpid, tmpid);
  	} else {
  		/* create an id from either shortname or longname */
  		const char *src;
  		src = *card->shortname ? card->shortname : card->longname;
  		snd_card_set_id_no_lock(card, src,
  					retrieve_id_from_card_name(src));
  	}
  	snd_cards[card->number] = card;
  	mutex_unlock(&snd_card_mutex);
  	init_info_for_card(card);
  	#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
  	if (snd_mixer_oss_notify_callback)
  		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
  	#endif
  	return 0;
}

其主要完成以下几件事情:

  1. 如果该card是没有注册的,就调用device_add(&card->card_dev)加入到device链表,并将标志位修改为已注册
  2. 实际上是通过snd_card的devices链表,遍历所有的snd_device,并且调用snd_device的ops->dev_register()来实现各自设备的注册的。
  3. init_info_for_card建立一些相应的proc和sysfs下的文件或属性节点

对于如何要完成一个声卡驱动,其应该满足3步曲,申请,初始化,注册

  1. 创建snd_card的一个实例snd_card_new
  2. 初始化结构体
  3. 通过snd_card_register注册到内核中

你可能感兴趣的:(音频子系统)