您的位置:首页 > 产品设计 > 产品经理

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_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链表更新
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: