Asoc dapm(二) - kcontrol注册与使用
2015-10-14 11:38
1011 查看
注册kcontrol
构造snd_kcontrol_new结构体数组,然后通过snd_soc_add_controls进行注册static const struct snd_kcontrol_new uda1341_snd_controls[] = { SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), SOC_SINGLE("Capture Volume", UDA134X_EA010, 2, 0x07, 0), SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1), ... } snd_soc_add_controls(codec, uda1341_snd_controls, ARRAY_SIZE(uda1341_snd_controls));
以下函数基本都在soc-core.c文件中
snd_soc_add_controls
int snd_soc_add_controls(struct snd_soc_codec *codec, const struct snd_kcontrol_new *controls, int num_controls) { struct snd_card *card = codec->card; int err, i; for (i = 0; i < num_controls; i++) { const struct snd_kcontrol_new *control = &controls[i]; err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL)); if (err < 0) { dev_err(codec->dev, "%s: Failed to add %s\n", codec->name, control->name); return err; } } return 0; } EXPORT_SYMBOL_GPL(snd_soc_add_controls);
snd_soc_add_controls的作用是根据codec_driver传过来的snd_kcontrol_new数组去初始化snd_kcontrol,函数snd_soc_cnew实现了这个功能,至于snd_ctl_add,检查在snd_card的snd_kcontrol链表中是否已存在,如果不存在,则添加到该链表中,并更新snd_card记录control的信息
snd_ctl_add
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) { struct snd_ctl_elem_id id; unsigned int idx; int err = -EINVAL; if (! kcontrol) return err; if (snd_BUG_ON(!card || !kcontrol->info)) goto error; id = kcontrol->id; down_write(&card->controls_rwsem); //在card的snd_kcontrol寻找是否已存在要添加的snd_kcontrol if (snd_ctl_find_id(card, &id)) { up_write(&card->controls_rwsem); snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n", id.iface, id.device, id.subdevice, id.name, id.index); err = -EBUSY; goto error; } if (snd_ctl_find_hole(card, kcontrol->count) < 0) { up_write(&card->controls_rwsem); err = -ENOMEM; goto error; } //将snd_kcontrol添加到card->controls链表中 list_add_tail(&kcontrol->list, &card->controls); //更新card和kcontrol中的参数 card->controls_count += kcontrol->count; kcontrol->id.numid = card->last_numid + 1; card->last_numid += kcontrol->count; up_write(&card->controls_rwsem); for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); return 0; error: snd_ctl_free_one(kcontrol); return err; }
需要注意的是
1、struct snd_ctl_elem_id中的numid和index
因为一个kcontrol是一个逻辑控件,可能对应多个物理控件,内核中的kcontrol->id.numid记录的是这个kcontrol中第一个物理控件在所有物理控件中的位置,用户空间也会传来struct snd_ctl_elem_id变量,里面的numid记录的是kcontrol中的具体某个物理控件的位置
2、struct snd_kcontrol中的count记录的是kcontrol中包括的物理控件的数目
3、strcut snd_card中的last_numid记录的是上一次所有物理控件的数目,当添加新的kcontrol之后,kcontrol->id.numid=card->last_numid+1, card->last_numid+=kcontrol->count
ALSA注册字符设备
具体来看看alsa的字符设备是如何注册的?上层应用程序又是如何访问到file_operation snd_ctl_f_ops?alsa_sound_init
static const struct file_operations snd_fops = { .owner = THIS_MODULE, .open = snd_open }; alsa_sound_init register_chrdev(major, "alsa", &snd_fops)
register_chrdev函数是早期注册字符设备的接口,他以major为主设备号注册0-255作为次设备号,并为每个设备建立一个对应的默认cdev。
snd_ctl_dev_register
static const struct file_operations snd_ctl_f_ops = { .owner = THIS_MODULE, .read = snd_ctl_read, .open = snd_ctl_open, .release = snd_ctl_release, .poll = snd_ctl_poll, .unlocked_ioctl = snd_ctl_ioctl, .compat_ioctl = snd_ctl_ioctl_compat, .fasync = snd_ctl_fasync, }; snd_ctl_dev_register snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, &snd_ctl_f_ops, card, name) ... snd_register_device_for_dev 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; preg = kmalloc(sizeof *preg, GFP_KERNEL); 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); minor = snd_kernel_minor(type, card, dev); snd_minors[minor] = preg; preg->dev = device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name); //name为"ControlC%i" mutex_unlock(&sound_mutex); return 0; }
在snd_register_device_for_dev中会分配一个snd_minors[minor](ctl_dev和pcm_dev分别对应不同的minor,他们的major都是116),并把file_operation snd_ctl_f_ops赋值给snd_minors[minor]的f_ops变量,然后在sys目录下添加对应minor的设备。当mdev检测到sys目录树的变动会在/dev目录下创建对应的设备节点或者你自己手动根据major和minor在对应目录下创建设备节点。设备节点为/dev/controlC0等。
此时如果打开设备节点/dev/control0,file->f_op只有open一个函数,因为在alsa_sound_init注册该字符设备时对应的file_operations是snd_fops,但是请来看snd_fops中的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; mptr = snd_minors[minor]; old_fops = file->f_op; file->f_op = fops_get(mptr->f_ops); 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; }
他用snd_minors[minors]->f_ops即之前保存的snd_ctl_f_ops替换掉了当前打开的control0设备节点的file->f_op,此时通过打开/dev/control0对应的file就能访问snd_ctl_f_ops中的read, open, ioctl之类的函数了。
总结一点就是:在打开字符设备之后,用事先保存的snd_ctl_f_ops替换掉当前打开字符设备对应的file中的f_ops,然后就能使用事先保存的snd_ctl_f_ops中的操作函数了。
用户空间调用kcontrol
snd_ctl_ioctlstatic long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct snd_ctl_file *ctl; struct snd_card *card; struct snd_kctl_ioctl *p; void __user *argp = (void __user *)arg; int __user *ip = argp; int err; ctl = file->private_data; card = ctl->card; switch (cmd) { case SNDRV_CTL_IOCTL_PVERSION: return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; case SNDRV_CTL_IOCTL_CARD_INFO: return snd_ctl_card_info(card, ctl, cmd, argp); case SNDRV_CTL_IOCTL_ELEM_LIST: return snd_ctl_elem_list(card, argp); case SNDRV_CTL_IOCTL_ELEM_INFO: return snd_ctl_elem_info_user(ctl, argp); case SNDRV_CTL_IOCTL_ELEM_READ: return snd_ctl_elem_read_user(card, argp); case SNDRV_CTL_IOCTL_ELEM_WRITE: return snd_ctl_elem_write_user(ctl, argp); case SNDRV_CTL_IOCTL_ELEM_LOCK: return snd_ctl_elem_lock(ctl, argp); case SNDRV_CTL_IOCTL_ELEM_UNLOCK: return snd_ctl_elem_unlock(ctl, argp); case SNDRV_CTL_IOCTL_ELEM_ADD: return snd_ctl_elem_add_user(ctl, argp, 0); case SNDRV_CTL_IOCTL_ELEM_REPLACE: return snd_ctl_elem_add_user(ctl, argp, 1); case SNDRV_CTL_IOCTL_ELEM_REMOVE: return snd_ctl_elem_remove(ctl, argp); case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: return snd_ctl_subscribe_events(ctl, ip); case SNDRV_CTL_IOCTL_TLV_READ: return snd_ctl_tlv_ioctl(ctl, argp, 0); case SNDRV_CTL_IOCTL_TLV_WRITE: return snd_ctl_tlv_ioctl(ctl, argp, 1); case SNDRV_CTL_IOCTL_TLV_COMMAND: return snd_ctl_tlv_ioctl(ctl, argp, -1); case SNDRV_CTL_IOCTL_POWER: return -ENOPROTOOPT; } ... }
分析下其中的snd_ctl_elem_info_user和snd_ctl_elem_read_user
snd_ctl_elem_info_user
static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl, struct snd_ctl_elem_info __user *_info) { struct snd_ctl_elem_info info; ... copy_from_user(&info, _info, sizeof(info)) snd_ctl_elem_info(ctl, &info); copy_to_user(_info, &info, sizeof(info)) } static int snd_ctl_elem_info(struct snd_ctl_file *ctl, struct snd_ctl_elem_info *info) { struct snd_card *card = ctl->card; struct snd_kcontrol *kctl; struct snd_kcontrol_volatile *vd; unsigned int index_offset; int result; down_read(&card->controls_rwsem); //在snd_card的kcontrol链表中根据info->id找到对应的kcontrol kctl = snd_ctl_find_id(card, &info->id); if (kctl == NULL) { up_read(&card->controls_rwsem); return -ENOENT; } //调用kcontrol中的info回调函数获取struct snd_ctl_elem_info信息 result = kctl->info(kctl, info); if (result >= 0) { snd_BUG_ON(info->access); index_offset = snd_ctl_get_ioff(kctl, &info->id); vd = &kctl->vd[index_offset]; snd_ctl_build_ioff(&info->id, kctl, index_offset); info->access = vd->access; if (vd->owner) { info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK; if (vd->owner == ctl) info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER; info->owner = vd->owner_pid; } else { info->owner = -1; } } up_read(&card->controls_rwsem); return result; }
从上面的代码可以看出,应用层通过ioctl来访问elem_info其实最终通过之前注册kcontrol时kcontrol中的info回调函数,一般如snd_soc_info_volsw,在soc-core.c文件中。来看看snd_soc_info_volsw
int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; int max = mc->max; unsigned int shift = mc->shift; unsigned int rshift = mc->rshift; if (max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = shift == rshift ? 1 : 2; uinfo->value.integer.min = 0; uinfo->value.integer.max = max; return 0; }
kcontrol->private_value字段是一个unsigned long类型的地址,从该地址起保存reg, shift, rshift, max等参数,将从该地址起的一段内存区域强制转化为truct soc_mixer_control *类型,就可以通过struct soc_mixer_control *mc来访问这些参数了。
在snd_soc_info_volsw函数中根据以上这些参数来赋值结构体snd_ctl_elem_info中的变量
snd_ctl_elem_read_user
static int snd_ctl_elem_read_user(struct snd_card *card, struct snd_ctl_elem_value __user *_control) { struct snd_ctl_elem_value *control; ... control = memdup_user(_control, sizeof(*control)); snd_ctl_elem_read(card, control); copy_to_user(_control, control, sizeof(*control))) kfree(control); } static int snd_ctl_elem_read(struct snd_card *card, struct snd_ctl_elem_value *control) { struct snd_kcontrol *kctl; struct snd_kcontrol_volatile *vd; unsigned int index_offset; int result; down_read(&card->controls_rwsem); //在snd_card的kcontrol链表中根据info->id找到对应的kcontrol kctl = snd_ctl_find_id(card, &control->id); if (kctl == NULL) { result = -ENOENT; } else { index_offset = snd_ctl_get_ioff(kctl, &control->id); vd = &kctl->vd[index_offset]; if ((vd->access & SNDRV_CTL_ELEM_ACCESS_READ) && kctl->get != NULL) { //struct snd_ctl_elem_value *control的id就是kctl的id //但是control->index和control->numid可能会因为一个逻辑kcontrol内有多个物理控件而存在偏移 snd_ctl_build_ioff(&control->id, kctl, index_offset); //调用kcontrol中的get回调函数获取struct snd_ctl_elem_value result = kctl->get(kctl, control); } else result = -EPERM; } up_read(&card->controls_rwsem); return result; }
可以看出和上面的snd_ctl_elem_info一样,最终还是会调用kcontrol中的get回调函数。只不过info函数获取这个逻辑kcontrol的info,但是get函数获取这个逻辑kcontrol中某个物理kcontrol的value。
来看看get回调函数snd_soc_get_volsw
int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); unsigned int reg = mc->reg; unsigned int shift = mc->shift; unsigned int rshift = mc->rshift; int max = mc->max; unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask; if (shift != rshift) ucontrol->value.integer.value[1] = (snd_soc_read(codec, reg) >> rshift) & mask; if (invert) { ucontrol->value.integer.value[0] = max - ucontrol->value.integer.value[0]; if (shift != rshift) ucontrol->value.integer.value[1] = max - ucontrol->value.integer.value[1]; } return 0; }
如果是左右声道的kcontrol,也即一个逻辑kcontrol中有两个物理kcontrol,分别对应控制左右声道,则value数组下标的个数就是物理kcontrol的个数,2个。从寄存器读取值后存入ucontrol->value.integer.value[0]和[1]中。
附上几篇文章的链接
1. Asoc dapm(一) - kcontrol
2. Asoc dapm(二) - kcontrol注册与使用
3. Asoc dapm(三) - dapm widgets & dapm kcontrol & dapm route
4. Asoc dapm(四) - dapm widgets & dapm route注册
5. Asoc dapm(五) - dapm widget链表更新
相关文章推荐
- dspmq dspmqver command not found(dspmq命令找不到,dspmqver主安装目录设置不正确)
- 程序员如何优雅地拒绝产品经理
- OneAPM 云监控部署与试用体验
- OneAPM 云监控部署与试用体验
- 使用MPMoviePlayerControll播放视频
- IOS7 直接加载MPMoviePlayerViewControllerview且横屏显示
- MPMediaItemPropertyAssetURL 属性获取的URL为空
- pmon 的实例健康检查处理逻辑分析
- Foundations of Qt Development 学习笔记 Part1 Tips1-50
- mipmap 和 drawable 的区别
- 工作流总结(三)JBPM 六个流程服务接口
- HP-EPM2-8组文化建设
- chipmunk小细节
- Quartus II LPM使用指南-FIFO篇
- 什么是SDK? Software Development Kit
- iClap:产品经理再忙也要看《琅琊榜》
- 做产品过程的一些总结
- HadoopMapReduce源码解析
- cgi fast-cgi fpm概念
- cordys BPM 流程设计 常用规范