diff options
Diffstat (limited to 'sound/core')
-rw-r--r-- | sound/core/control.c | 330 | ||||
-rw-r--r-- | sound/core/init.c | 1 |
2 files changed, 193 insertions, 138 deletions
diff --git a/sound/core/control.c b/sound/core/control.c index 4525e127afd9..de976a8c0177 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -881,24 +881,18 @@ static int snd_ctl_elem_read(struct snd_card *card, struct snd_kcontrol *kctl; struct snd_kcontrol_volatile *vd; unsigned int index_offset; - int result; - down_read(&card->controls_rwsem); 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) { - snd_ctl_build_ioff(&control->id, kctl, index_offset); - result = kctl->get(kctl, control); - } else - result = -EPERM; - } - up_read(&card->controls_rwsem); - return result; + if (kctl == NULL) + return -ENOENT; + + 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) + return -EPERM; + + snd_ctl_build_ioff(&control->id, kctl, index_offset); + return kctl->get(kctl, control); } static int snd_ctl_elem_read_user(struct snd_card *card, @@ -913,8 +907,11 @@ static int snd_ctl_elem_read_user(struct snd_card *card, snd_power_lock(card); result = snd_power_wait(card, SNDRV_CTL_POWER_D0); - if (result >= 0) + if (result >= 0) { + down_read(&card->controls_rwsem); result = snd_ctl_elem_read(card, control); + up_read(&card->controls_rwsem); + } snd_power_unlock(card); if (result >= 0) if (copy_to_user(_control, control, sizeof(*control))) @@ -931,30 +928,28 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, unsigned int index_offset; int result; - down_read(&card->controls_rwsem); 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_WRITE) || - kctl->put == NULL || - (file && vd->owner && vd->owner != file)) { - result = -EPERM; - } else { - snd_ctl_build_ioff(&control->id, kctl, index_offset); - result = kctl->put(kctl, control); - } - if (result > 0) { - struct snd_ctl_elem_id id = control->id; - up_read(&card->controls_rwsem); - snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id); - return 0; - } + if (kctl == NULL) + return -ENOENT; + + index_offset = snd_ctl_get_ioff(kctl, &control->id); + vd = &kctl->vd[index_offset]; + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || kctl->put == NULL || + (file && vd->owner && vd->owner != file)) { + return -EPERM; } - up_read(&card->controls_rwsem); - return result; + + snd_ctl_build_ioff(&control->id, kctl, index_offset); + result = kctl->put(kctl, control); + if (result < 0) + return result; + + if (result > 0) { + struct snd_ctl_elem_id id = control->id; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id); + } + + return 0; } static int snd_ctl_elem_write_user(struct snd_ctl_file *file, @@ -971,8 +966,11 @@ static int snd_ctl_elem_write_user(struct snd_ctl_file *file, card = file->card; snd_power_lock(card); result = snd_power_wait(card, SNDRV_CTL_POWER_D0); - if (result >= 0) + if (result >= 0) { + down_write(&card->controls_rwsem); result = snd_ctl_elem_write(card, file, control); + up_write(&card->controls_rwsem); + } snd_power_unlock(card); if (result >= 0) if (copy_to_user(_control, control, sizeof(*control))) @@ -1095,9 +1093,7 @@ static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, char *src = ue->elem_data + snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; - mutex_lock(&ue->card->user_ctl_lock); memcpy(&ucontrol->value, src, size); - mutex_unlock(&ue->card->user_ctl_lock); return 0; } @@ -1110,60 +1106,67 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, char *dst = ue->elem_data + snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; - mutex_lock(&ue->card->user_ctl_lock); change = memcmp(&ucontrol->value, dst, size) != 0; if (change) memcpy(dst, &ucontrol->value, size); - mutex_unlock(&ue->card->user_ctl_lock); return change; } -static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol, - int op_flag, - unsigned int size, - unsigned int __user *tlv) +static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, + unsigned int size) { - struct user_element *ue = kcontrol->private_data; - int change = 0; - void *new_data; + struct user_element *ue = kctl->private_data; + unsigned int *container; + int change; - if (op_flag == SNDRV_CTL_TLV_OP_WRITE) { - if (size > 1024 * 128) /* sane value */ - return -EINVAL; + if (size > 1024 * 128) /* sane value */ + return -EINVAL; - new_data = memdup_user(tlv, size); - if (IS_ERR(new_data)) - return PTR_ERR(new_data); - mutex_lock(&ue->card->user_ctl_lock); - change = ue->tlv_data_size != size; - if (!change) - change = memcmp(ue->tlv_data, new_data, size) != 0; - kfree(ue->tlv_data); - ue->tlv_data = new_data; - ue->tlv_data_size = size; - mutex_unlock(&ue->card->user_ctl_lock); - } else { - int ret = 0; + container = memdup_user(buf, size); + if (IS_ERR(container)) + return PTR_ERR(container); - mutex_lock(&ue->card->user_ctl_lock); - if (!ue->tlv_data_size || !ue->tlv_data) { - ret = -ENXIO; - goto err_unlock; - } - if (size < ue->tlv_data_size) { - ret = -ENOSPC; - goto err_unlock; - } - if (copy_to_user(tlv, ue->tlv_data, ue->tlv_data_size)) - ret = -EFAULT; -err_unlock: - mutex_unlock(&ue->card->user_ctl_lock); - if (ret) - return ret; + change = ue->tlv_data_size != size; + if (!change) + change = memcmp(ue->tlv_data, container, size) != 0; + if (!change) { + kfree(container); + return 0; } + + kfree(ue->tlv_data); + ue->tlv_data = container; + ue->tlv_data_size = size; + return change; } +static int read_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, + unsigned int size) +{ + struct user_element *ue = kctl->private_data; + + if (ue->tlv_data_size == 0 || ue->tlv_data == NULL) + return -ENXIO; + + if (size < ue->tlv_data_size) + return -ENOSPC; + + if (copy_to_user(buf, ue->tlv_data, ue->tlv_data_size)) + return -EFAULT; + + return 0; +} + +static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kctl, int op_flag, + unsigned int size, unsigned int __user *buf) +{ + if (op_flag == SNDRV_CTL_TLV_OP_WRITE) + return replace_user_tlv(kctl, buf, size); + else + return read_user_tlv(kctl, buf, size); +} + static int snd_ctl_elem_init_enum_names(struct user_element *ue) { char *names, *p; @@ -1405,71 +1408,115 @@ static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr) return 0; } +static int call_tlv_handler(struct snd_ctl_file *file, int op_flag, + struct snd_kcontrol *kctl, + struct snd_ctl_elem_id *id, + unsigned int __user *buf, unsigned int size) +{ + static const struct { + int op; + int perm; + } pairs[] = { + {SNDRV_CTL_TLV_OP_READ, SNDRV_CTL_ELEM_ACCESS_TLV_READ}, + {SNDRV_CTL_TLV_OP_WRITE, SNDRV_CTL_ELEM_ACCESS_TLV_WRITE}, + {SNDRV_CTL_TLV_OP_CMD, SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND}, + }; + struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; + int i; + int err; + + /* Check support of the request for this element. */ + for (i = 0; i < ARRAY_SIZE(pairs); ++i) { + if (op_flag == pairs[i].op && (vd->access & pairs[i].perm)) + break; + } + if (i == ARRAY_SIZE(pairs)) + return -ENXIO; + + if (kctl->tlv.c == NULL) + return -ENXIO; + + /* When locked, this is unavailable. */ + if (vd->owner != NULL && vd->owner != file) + return -EPERM; + + err = kctl->tlv.c(kctl, op_flag, size, buf); + if (err < 0) + return err; + + if (err > 0) + snd_ctl_notify(file->card, SNDRV_CTL_EVENT_MASK_TLV, id); + + return 0; +} + +static int read_tlv_buf(struct snd_kcontrol *kctl, struct snd_ctl_elem_id *id, + unsigned int __user *buf, unsigned int size) +{ + struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; + unsigned int len; + + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)) + return -ENXIO; + + if (kctl->tlv.p == NULL) + return -ENXIO; + + len = sizeof(unsigned int) * 2 + kctl->tlv.p[1]; + if (size < len) + return -ENOMEM; + + if (copy_to_user(buf, kctl->tlv.p, len)) + return -EFAULT; + + return 0; +} + static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, - struct snd_ctl_tlv __user *_tlv, + struct snd_ctl_tlv __user *buf, int op_flag) { - struct snd_card *card = file->card; - struct snd_ctl_tlv tlv; + struct snd_ctl_tlv header; + unsigned int *container; + unsigned int container_size; struct snd_kcontrol *kctl; + struct snd_ctl_elem_id id; struct snd_kcontrol_volatile *vd; - unsigned int len; - int err = 0; - if (copy_from_user(&tlv, _tlv, sizeof(tlv))) + if (copy_from_user(&header, buf, sizeof(header))) return -EFAULT; - if (tlv.length < sizeof(unsigned int) * 2) + + /* In design of control core, numerical ID starts at 1. */ + if (header.numid == 0) return -EINVAL; - if (!tlv.numid) + + /* At least, container should include type and length fields. */ + if (header.length < sizeof(unsigned int) * 2) 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 == SNDRV_CTL_TLV_OP_READ && - (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) || - (op_flag == SNDRV_CTL_TLV_OP_WRITE && - (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) || - (op_flag == SNDRV_CTL_TLV_OP_CMD && - (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) { - err = -ENXIO; - goto __kctl_end; - } + container_size = header.length; + container = buf->tlv; + + kctl = snd_ctl_find_numid(file->card, header.numid); + if (kctl == NULL) + return -ENOENT; + + /* Calculate index of the element in this set. */ + id = kctl->id; + snd_ctl_build_ioff(&id, kctl, header.numid - id.numid); + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { - if (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) { - struct snd_ctl_elem_id id = kctl->id; - up_read(&card->controls_rwsem); - snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &id); - return 0; - } + return call_tlv_handler(file, op_flag, kctl, &id, container, + container_size); } else { - if (op_flag != SNDRV_CTL_TLV_OP_READ) { - err = -ENXIO; - goto __kctl_end; - } - len = kctl->tlv.p[1] + 2 * sizeof(unsigned int); - if (tlv.length < len) { - err = -ENOMEM; - goto __kctl_end; + if (op_flag == SNDRV_CTL_TLV_OP_READ) { + return read_tlv_buf(kctl, &id, container, + container_size); } - if (copy_to_user(_tlv->tlv, kctl->tlv.p, len)) - err = -EFAULT; } - __kctl_end: - up_read(&card->controls_rwsem); - return err; + + /* Not supported. */ + return -ENXIO; } static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) @@ -1511,11 +1558,20 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg 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, SNDRV_CTL_TLV_OP_READ); + down_read(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ); + up_read(&ctl->card->controls_rwsem); + return err; case SNDRV_CTL_IOCTL_TLV_WRITE: - return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE); + down_write(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE); + up_write(&ctl->card->controls_rwsem); + return err; case SNDRV_CTL_IOCTL_TLV_COMMAND: - return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD); + down_write(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD); + up_write(&ctl->card->controls_rwsem); + return err; case SNDRV_CTL_IOCTL_POWER: return -ENOPROTOOPT; case SNDRV_CTL_IOCTL_POWER_STATE: diff --git a/sound/core/init.c b/sound/core/init.c index b4365bcf28a7..6e219dc23f96 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -248,7 +248,6 @@ int snd_card_new(struct device *parent, int idx, const char *xid, INIT_LIST_HEAD(&card->devices); init_rwsem(&card->controls_rwsem); rwlock_init(&card->ctl_files_rwlock); - mutex_init(&card->user_ctl_lock); INIT_LIST_HEAD(&card->controls); INIT_LIST_HEAD(&card->ctl_files); spin_lock_init(&card->files_lock); |