summaryrefslogtreecommitdiff
path: root/sound/core
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core')
-rw-r--r--sound/core/control.c139
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: