diff options
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/control.c | 139 |
1 files changed, 109 insertions, 30 deletions
diff --git a/sound/core/control.c b/sound/core/control.c index f0c7272a2d48..31ad58154c06 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -236,12 +236,16 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, kctl.id.index = ncontrol->index; kctl.count = ncontrol->count ? ncontrol->count : 1; access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : - (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE| - SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT)); + (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE| + SNDRV_CTL_ELEM_ACCESS_INACTIVE| + SNDRV_CTL_ELEM_ACCESS_DINDIRECT| + SNDRV_CTL_ELEM_ACCESS_INDIRECT| + SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE| + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)); kctl.info = ncontrol->info; kctl.get = ncontrol->get; kctl.put = ncontrol->put; - kctl.tlv = ncontrol->tlv; + kctl.tlv.p = ncontrol->tlv.p; kctl.private_value = ncontrol->private_value; kctl.private_data = private_data; return snd_ctl_new(&kctl, access); @@ -883,6 +887,8 @@ struct user_element { struct snd_ctl_elem_info info; void *elem_data; /* element data */ unsigned long elem_data_size; /* size of element data in bytes */ + void *tlv_data; /* TLV data */ + unsigned long tlv_data_size; /* TLV data size */ void *priv_data; /* private data (like strings for enumerated type) */ unsigned long priv_data_size; /* size of private data in bytes */ }; @@ -917,9 +923,46 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, return change; } +static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol, + int op_flag, + unsigned int size, + unsigned int __user *tlv) +{ + struct user_element *ue = kcontrol->private_data; + int change = 0; + void *new_data; + + if (op_flag > 0) { + if (size > 1024 * 128) /* sane value */ + return -EINVAL; + new_data = kmalloc(size, GFP_KERNEL); + if (new_data == NULL) + return -ENOMEM; + if (copy_from_user(new_data, tlv, size)) { + kfree(new_data); + return -EFAULT; + } + change = ue->tlv_data_size != size; + if (!change) + change = memcmp(ue->tlv_data, new_data, size); + kfree(ue->tlv_data); + ue->tlv_data = new_data; + ue->tlv_data_size = size; + } else { + if (size < ue->tlv_data_size) + return -ENOSPC; + if (copy_to_user(tlv, ue->tlv_data, ue->tlv_data_size)) + return -EFAULT; + } + return change; +} + static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) { - kfree(kcontrol->private_data); + struct user_element *ue = kcontrol->private_data; + if (ue->tlv_data) + kfree(ue->tlv_data); + kfree(ue); } static int snd_ctl_elem_add(struct snd_ctl_file *file, @@ -938,7 +981,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, return -EINVAL; access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : (info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE| - SNDRV_CTL_ELEM_ACCESS_INACTIVE)); + SNDRV_CTL_ELEM_ACCESS_INACTIVE| + SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)); info->id.numid = 0; memset(&kctl, 0, sizeof(kctl)); down_write(&card->controls_rwsem); @@ -964,6 +1008,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, kctl.get = snd_ctl_elem_user_get; if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) kctl.put = snd_ctl_elem_user_put; + if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) { + kctl.tlv.c = snd_ctl_elem_user_tlv; + access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } switch (info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: private_size = sizeof(char); @@ -1068,38 +1116,65 @@ static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr) return 0; } -static int snd_ctl_tlv_read(struct snd_card *card, - struct snd_ctl_tlv __user *_tlv) +static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, + struct snd_ctl_tlv __user *_tlv, + int op_flag) { + struct snd_card *card = file->card; struct snd_ctl_tlv tlv; struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; unsigned int len; int err = 0; if (copy_from_user(&tlv, _tlv, sizeof(tlv))) return -EFAULT; - if (tlv.length < sizeof(unsigned int) * 3) - return -EINVAL; - down_read(&card->controls_rwsem); - kctl = snd_ctl_find_numid(card, tlv.numid); - if (kctl == NULL) { - err = -ENOENT; - goto __kctl_end; - } - if (kctl->tlv == NULL) { - err = -ENXIO; - goto __kctl_end; - } - len = kctl->tlv[1] + 2 * sizeof(unsigned int); - if (tlv.length < len) { - err = -ENOMEM; - goto __kctl_end; - } - if (copy_to_user(_tlv->tlv, kctl->tlv, len)) - err = -EFAULT; + if (tlv.length < sizeof(unsigned int) * 3) + return -EINVAL; + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_numid(card, tlv.numid); + if (kctl == NULL) { + err = -ENOENT; + goto __kctl_end; + } + if (kctl->tlv.p == NULL) { + err = -ENXIO; + goto __kctl_end; + } + vd = &kctl->vd[tlv.numid - kctl->id.numid]; + if ((op_flag == 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) || + (op_flag > 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) || + (op_flag < 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) { + err = -ENXIO; + goto __kctl_end; + } + if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + if (file && vd->owner != NULL && vd->owner != file) { + err = -EPERM; + goto __kctl_end; + } + err = kctl->tlv.c(kctl, op_flag, tlv.length, _tlv->tlv); + if (err > 0) { + up_read(&card->controls_rwsem); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id); + return 0; + } + } else { + if (op_flag) { + err = -ENXIO; + goto __kctl_end; + } + len = kctl->tlv.p[1] + 2 * sizeof(unsigned int); + if (tlv.length < len) { + err = -ENOMEM; + goto __kctl_end; + } + if (copy_to_user(_tlv->tlv, kctl->tlv.p, len)) + err = -EFAULT; + } __kctl_end: - up_read(&card->controls_rwsem); - return err; + up_read(&card->controls_rwsem); + return err; } static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) @@ -1140,8 +1215,12 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg 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_read(card, argp); + 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; case SNDRV_CTL_IOCTL_POWER_STATE: |