Asoc dapm(二) - kcontrol注册与使用

来源:互联网 发布:锐速windows破解版 编辑:程序博客网 时间:2024/06/01 08:07

注册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_devint 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_ioctl

static 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链表更新

0 0
原创粉丝点击