diff options
Diffstat (limited to 'sound/core/control.c')
-rw-r--r-- | sound/core/control.c | 1375 |
1 files changed, 1375 insertions, 0 deletions
diff --git a/sound/core/control.c b/sound/core/control.c new file mode 100644 index 00000000000..f4ea6bff1dd --- /dev/null +++ b/sound/core/control.c @@ -0,0 +1,1375 @@ +/* + * Routines for driver control interface + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/threads.h> +#include <linux/interrupt.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <sound/control.h> + +/* max number of user-defined controls */ +#define MAX_USER_CONTROLS 32 + +typedef struct _snd_kctl_ioctl { + struct list_head list; /* list of all ioctls */ + snd_kctl_ioctl_func_t fioctl; +} snd_kctl_ioctl_t; + +#define snd_kctl_ioctl(n) list_entry(n, snd_kctl_ioctl_t, list) + +static DECLARE_RWSEM(snd_ioctl_rwsem); +static LIST_HEAD(snd_control_ioctls); +#ifdef CONFIG_COMPAT +static LIST_HEAD(snd_control_compat_ioctls); +#endif + +static int snd_ctl_open(struct inode *inode, struct file *file) +{ + int cardnum = SNDRV_MINOR_CARD(iminor(inode)); + unsigned long flags; + snd_card_t *card; + snd_ctl_file_t *ctl; + int err; + + card = snd_cards[cardnum]; + if (!card) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(card, file); + if (err < 0) { + err = -ENODEV; + goto __error1; + } + if (!try_module_get(card->module)) { + err = -EFAULT; + goto __error2; + } + ctl = kcalloc(1, sizeof(*ctl), GFP_KERNEL); + if (ctl == NULL) { + err = -ENOMEM; + goto __error; + } + INIT_LIST_HEAD(&ctl->events); + init_waitqueue_head(&ctl->change_sleep); + spin_lock_init(&ctl->read_lock); + ctl->card = card; + ctl->pid = current->pid; + file->private_data = ctl; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_add_tail(&ctl->list, &card->ctl_files); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + return 0; + + __error: + module_put(card->module); + __error2: + snd_card_file_remove(card, file); + __error1: + return err; +} + +static void snd_ctl_empty_read_queue(snd_ctl_file_t * ctl) +{ + snd_kctl_event_t *cread; + + spin_lock(&ctl->read_lock); + while (!list_empty(&ctl->events)) { + cread = snd_kctl_event(ctl->events.next); + list_del(&cread->list); + kfree(cread); + } + spin_unlock(&ctl->read_lock); +} + +static int snd_ctl_release(struct inode *inode, struct file *file) +{ + unsigned long flags; + struct list_head *list; + snd_card_t *card; + snd_ctl_file_t *ctl; + snd_kcontrol_t *control; + unsigned int idx; + + ctl = file->private_data; + fasync_helper(-1, file, 0, &ctl->fasync); + file->private_data = NULL; + card = ctl->card; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_del(&ctl->list); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + down_write(&card->controls_rwsem); + list_for_each(list, &card->controls) { + control = snd_kcontrol(list); + for (idx = 0; idx < control->count; idx++) + if (control->vd[idx].owner == ctl) + control->vd[idx].owner = NULL; + } + up_write(&card->controls_rwsem); + snd_ctl_empty_read_queue(ctl); + kfree(ctl); + module_put(card->module); + snd_card_file_remove(card, file); + return 0; +} + +void snd_ctl_notify(snd_card_t *card, unsigned int mask, snd_ctl_elem_id_t *id) +{ + unsigned long flags; + struct list_head *flist; + snd_ctl_file_t *ctl; + snd_kctl_event_t *ev; + + snd_runtime_check(card != NULL && id != NULL, return); + read_lock(&card->ctl_files_rwlock); +#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) + card->mixer_oss_change_count++; +#endif + list_for_each(flist, &card->ctl_files) { + struct list_head *elist; + ctl = snd_ctl_file(flist); + if (!ctl->subscribed) + continue; + spin_lock_irqsave(&ctl->read_lock, flags); + list_for_each(elist, &ctl->events) { + ev = snd_kctl_event(elist); + if (ev->id.numid == id->numid) { + ev->mask |= mask; + goto _found; + } + } + ev = kcalloc(1, sizeof(*ev), GFP_ATOMIC); + if (ev) { + ev->id = *id; + ev->mask = mask; + list_add_tail(&ev->list, &ctl->events); + } else { + snd_printk(KERN_ERR "No memory available to allocate event\n"); + } + _found: + wake_up(&ctl->change_sleep); + spin_unlock_irqrestore(&ctl->read_lock, flags); + kill_fasync(&ctl->fasync, SIGIO, POLL_IN); + } + read_unlock(&card->ctl_files_rwlock); +} + +/** + * snd_ctl_new - create a control instance from the template + * @control: the control template + * @access: the default control access + * + * Allocates a new snd_kcontrol_t instance and copies the given template + * to the new instance. It does not copy volatile data (access). + * + * Returns the pointer of the new instance, or NULL on failure. + */ +snd_kcontrol_t *snd_ctl_new(snd_kcontrol_t * control, unsigned int access) +{ + snd_kcontrol_t *kctl; + unsigned int idx; + + snd_runtime_check(control != NULL, return NULL); + snd_runtime_check(control->count > 0, return NULL); + kctl = kcalloc(1, sizeof(*kctl) + sizeof(snd_kcontrol_volatile_t) * control->count, GFP_KERNEL); + if (kctl == NULL) + return NULL; + *kctl = *control; + for (idx = 0; idx < kctl->count; idx++) + kctl->vd[idx].access = access; + return kctl; +} + +/** + * snd_ctl_new1 - create a control instance from the template + * @ncontrol: the initialization record + * @private_data: the private data to set + * + * Allocates a new snd_kcontrol_t instance and initialize from the given + * template. When the access field of ncontrol is 0, it's assumed as + * READWRITE access. When the count field is 0, it's assumes as one. + * + * Returns the pointer of the newly generated instance, or NULL on failure. + */ +snd_kcontrol_t *snd_ctl_new1(snd_kcontrol_new_t * ncontrol, void *private_data) +{ + snd_kcontrol_t kctl; + unsigned int access; + + snd_runtime_check(ncontrol != NULL, return NULL); + snd_assert(ncontrol->info != NULL, return NULL); + memset(&kctl, 0, sizeof(kctl)); + kctl.id.iface = ncontrol->iface; + kctl.id.device = ncontrol->device; + kctl.id.subdevice = ncontrol->subdevice; + if (ncontrol->name) + strlcpy(kctl.id.name, ncontrol->name, sizeof(kctl.id.name)); + 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)); + kctl.info = ncontrol->info; + kctl.get = ncontrol->get; + kctl.put = ncontrol->put; + kctl.private_value = ncontrol->private_value; + kctl.private_data = private_data; + return snd_ctl_new(&kctl, access); +} + +/** + * snd_ctl_free_one - release the control instance + * @kcontrol: the control instance + * + * Releases the control instance created via snd_ctl_new() + * or snd_ctl_new1(). + * Don't call this after the control was added to the card. + */ +void snd_ctl_free_one(snd_kcontrol_t * kcontrol) +{ + if (kcontrol) { + if (kcontrol->private_free) + kcontrol->private_free(kcontrol); + kfree(kcontrol); + } +} + +static unsigned int snd_ctl_hole_check(snd_card_t * card, + unsigned int count) +{ + struct list_head *list; + snd_kcontrol_t *kctl; + + list_for_each(list, &card->controls) { + kctl = snd_kcontrol(list); + if ((kctl->id.numid <= card->last_numid && + kctl->id.numid + kctl->count > card->last_numid) || + (kctl->id.numid <= card->last_numid + count - 1 && + kctl->id.numid + kctl->count > card->last_numid + count - 1)) + return card->last_numid = kctl->id.numid + kctl->count - 1; + } + return card->last_numid; +} + +static int snd_ctl_find_hole(snd_card_t * card, unsigned int count) +{ + unsigned int last_numid, iter = 100000; + + last_numid = card->last_numid; + while (last_numid != snd_ctl_hole_check(card, count)) { + if (--iter == 0) { + /* this situation is very unlikely */ + snd_printk(KERN_ERR "unable to allocate new control numid\n"); + return -ENOMEM; + } + last_numid = card->last_numid; + } + return 0; +} + +/** + * snd_ctl_add - add the control instance to the card + * @card: the card instance + * @kcontrol: the control instance to add + * + * Adds the control instance created via snd_ctl_new() or + * snd_ctl_new1() to the given card. Assigns also an unique + * numid used for fast search. + * + * Returns zero if successful, or a negative error code on failure. + * + * It frees automatically the control which cannot be added. + */ +int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol) +{ + snd_ctl_elem_id_t id; + unsigned int idx; + + snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL); + snd_assert(kcontrol->info != NULL, return -EINVAL); + id = kcontrol->id; + down_write(&card->controls_rwsem); + if (snd_ctl_find_id(card, &id)) { + up_write(&card->controls_rwsem); + snd_ctl_free_one(kcontrol); + snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n", + id.iface, + id.device, + id.subdevice, + id.name, + id.index); + return -EBUSY; + } + if (snd_ctl_find_hole(card, kcontrol->count) < 0) { + up_write(&card->controls_rwsem); + snd_ctl_free_one(kcontrol); + return -ENOMEM; + } + list_add_tail(&kcontrol->list, &card->controls); + 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; +} + +/** + * snd_ctl_remove - remove the control from the card and release it + * @card: the card instance + * @kcontrol: the control instance to remove + * + * Removes the control from the card and then releases the instance. + * You don't need to call snd_ctl_free_one(). You must be in + * the write lock - down_write(&card->controls_rwsem). + * + * Returns 0 if successful, or a negative error code on failure. + */ +int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol) +{ + snd_ctl_elem_id_t id; + unsigned int idx; + + snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL); + list_del(&kcontrol->list); + card->controls_count -= kcontrol->count; + id = kcontrol->id; + for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id); + snd_ctl_free_one(kcontrol); + return 0; +} + +/** + * snd_ctl_remove_id - remove the control of the given id and release it + * @card: the card instance + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Returns 0 if successful, or a negative error code on failure. + */ +int snd_ctl_remove_id(snd_card_t * card, snd_ctl_elem_id_t *id) +{ + snd_kcontrol_t *kctl; + int ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + ret = snd_ctl_remove(card, kctl); + up_write(&card->controls_rwsem); + return ret; +} + +/** + * snd_ctl_remove_unlocked_id - remove the unlocked control of the given id and release it + * @file: active control handle + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Returns 0 if successful, or a negative error code on failure. + */ +static int snd_ctl_remove_unlocked_id(snd_ctl_file_t * file, snd_ctl_elem_id_t *id) +{ + snd_card_t *card = file->card; + snd_kcontrol_t *kctl; + int idx, ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + for (idx = 0; idx < kctl->count; idx++) + if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) { + up_write(&card->controls_rwsem); + return -EBUSY; + } + ret = snd_ctl_remove(card, kctl); + up_write(&card->controls_rwsem); + return ret; +} + +/** + * snd_ctl_rename_id - replace the id of a control on the card + * @card: the card instance + * @src_id: the old id + * @dst_id: the new id + * + * Finds the control with the old id from the card, and replaces the + * id with the new one. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_ctl_rename_id(snd_card_t * card, snd_ctl_elem_id_t *src_id, snd_ctl_elem_id_t *dst_id) +{ + snd_kcontrol_t *kctl; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, src_id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + kctl->id = *dst_id; + kctl->id.numid = card->last_numid + 1; + card->last_numid += kctl->count; + up_write(&card->controls_rwsem); + return 0; +} + +/** + * snd_ctl_find_numid - find the control instance with the given number-id + * @card: the card instance + * @numid: the number-id to search + * + * Finds the control instance with the given number-id from the card. + * + * Returns the pointer of the instance if found, or NULL if not. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + */ +snd_kcontrol_t *snd_ctl_find_numid(snd_card_t * card, unsigned int numid) +{ + struct list_head *list; + snd_kcontrol_t *kctl; + + snd_runtime_check(card != NULL && numid != 0, return NULL); + list_for_each(list, &card->controls) { + kctl = snd_kcontrol(list); + if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid) + return kctl; + } + return NULL; +} + +/** + * snd_ctl_find_id - find the control instance with the given id + * @card: the card instance + * @id: the id to search + * + * Finds the control instance with the given id from the card. + * + * Returns the pointer of the instance if found, or NULL if not. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + */ +snd_kcontrol_t *snd_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id) +{ + struct list_head *list; + snd_kcontrol_t *kctl; + + snd_runtime_check(card != NULL && id != NULL, return NULL); + if (id->numid != 0) + return snd_ctl_find_numid(card, id->numid); + list_for_each(list, &card->controls) { + kctl = snd_kcontrol(list); + if (kctl->id.iface != id->iface) + continue; + if (kctl->id.device != id->device) + continue; + if (kctl->id.subdevice != id->subdevice) + continue; + if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name))) + continue; + if (kctl->id.index > id->index) + continue; + if (kctl->id.index + kctl->count <= id->index) + continue; + return kctl; + } + return NULL; +} + +static int snd_ctl_card_info(snd_card_t * card, snd_ctl_file_t * ctl, + unsigned int cmd, void __user *arg) +{ + snd_ctl_card_info_t *info; + + info = kcalloc(1, sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + down_read(&snd_ioctl_rwsem); + info->card = card->number; + strlcpy(info->id, card->id, sizeof(info->id)); + strlcpy(info->driver, card->driver, sizeof(info->driver)); + strlcpy(info->name, card->shortname, sizeof(info->name)); + strlcpy(info->longname, card->longname, sizeof(info->longname)); + strlcpy(info->mixername, card->mixername, sizeof(info->mixername)); + strlcpy(info->components, card->components, sizeof(info->components)); + up_read(&snd_ioctl_rwsem); + if (copy_to_user(arg, info, sizeof(snd_ctl_card_info_t))) { + kfree(info); + return -EFAULT; + } + kfree(info); + return 0; +} + +static int snd_ctl_elem_list(snd_card_t *card, snd_ctl_elem_list_t __user *_list) +{ + struct list_head *plist; + snd_ctl_elem_list_t list; + snd_kcontrol_t *kctl; + snd_ctl_elem_id_t *dst, *id; + unsigned int offset, space, first, jidx; + + if (copy_from_user(&list, _list, sizeof(list))) + return -EFAULT; + offset = list.offset; + space = list.space; + first = 0; + /* try limit maximum space */ + if (space > 16384) + return -ENOMEM; + if (space > 0) { + /* allocate temporary buffer for atomic operation */ + dst = vmalloc(space * sizeof(snd_ctl_elem_id_t)); + if (dst == NULL) + return -ENOMEM; + down_read(&card->controls_rwsem); + list.count = card->controls_count; + plist = card->controls.next; + while (plist != &card->controls) { + if (offset == 0) + break; + kctl = snd_kcontrol(plist); + if (offset < kctl->count) + break; + offset -= kctl->count; + plist = plist->next; + } + list.used = 0; + id = dst; + while (space > 0 && plist != &card->controls) { + kctl = snd_kcontrol(plist); + for (jidx = offset; space > 0 && jidx < kctl->count; jidx++) { + snd_ctl_build_ioff(id, kctl, jidx); + id++; + space--; + list.used++; + } + plist = plist->next; + offset = 0; + } + up_read(&card->controls_rwsem); + if (list.used > 0 && copy_to_user(list.pids, dst, list.used * sizeof(snd_ctl_elem_id_t))) { + vfree(dst); + return -EFAULT; + } + vfree(dst); + } else { + down_read(&card->controls_rwsem); + list.count = card->controls_count; + up_read(&card->controls_rwsem); + } + if (copy_to_user(_list, &list, sizeof(list))) + return -EFAULT; + return 0; +} + +static int snd_ctl_elem_info(snd_ctl_file_t *ctl, snd_ctl_elem_info_t *info) +{ + snd_card_t *card = ctl->card; + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + unsigned int index_offset; + int result; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &info->id); + if (kctl == NULL) { + up_read(&card->controls_rwsem); + return -ENOENT; + } +#ifdef CONFIG_SND_DEBUG + info->access = 0; +#endif + result = kctl->info(kctl, info); + if (result >= 0) { + snd_assert(info->access == 0, ); + 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; +} + +static int snd_ctl_elem_info_user(snd_ctl_file_t *ctl, snd_ctl_elem_info_t __user *_info) +{ + snd_ctl_elem_info_t info; + int result; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + result = snd_ctl_elem_info(ctl, &info); + if (result >= 0) + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return result; +} + +int snd_ctl_elem_read(snd_card_t *card, snd_ctl_elem_value_t *control) +{ + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + unsigned int index_offset; + int result, indirect; + + 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]; + indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0; + if (control->indirect != indirect) { + result = -EACCES; + } else { + 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; +} + +static int snd_ctl_elem_read_user(snd_card_t *card, snd_ctl_elem_value_t __user *_control) +{ + snd_ctl_elem_value_t *control; + int result; + + control = kmalloc(sizeof(*control), GFP_KERNEL); + if (control == NULL) + return -ENOMEM; + if (copy_from_user(control, _control, sizeof(*control))) { + kfree(control); + return -EFAULT; + } + result = snd_ctl_elem_read(card, control); + if (result >= 0) + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + kfree(control); + return result; +} + +int snd_ctl_elem_write(snd_card_t *card, snd_ctl_file_t *file, snd_ctl_elem_value_t *control) +{ + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + unsigned int index_offset; + int result, indirect; + + 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]; + indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0; + if (control->indirect != indirect) { + result = -EACCES; + } else { + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || + kctl->put == NULL || + (file && vd->owner != NULL && vd->owner != file)) { + result = -EPERM; + } else { + snd_ctl_build_ioff(&control->id, kctl, index_offset); + result = kctl->put(kctl, control); + } + if (result > 0) { + up_read(&card->controls_rwsem); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &control->id); + return 0; + } + } + } + up_read(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_write_user(snd_ctl_file_t *file, snd_ctl_elem_value_t __user *_control) +{ + snd_ctl_elem_value_t *control; + int result; + + control = kmalloc(sizeof(*control), GFP_KERNEL); + if (control == NULL) + return -ENOMEM; + if (copy_from_user(control, _control, sizeof(*control))) { + kfree(control); + return -EFAULT; + } + result = snd_ctl_elem_write(file->card, file, control); + if (result >= 0) + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + kfree(control); + return result; +} + +static int snd_ctl_elem_lock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id) +{ + snd_card_t *card = file->card; + snd_ctl_elem_id_t id; + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner != NULL) + result = -EBUSY; + else { + vd->owner = file; + vd->owner_pid = current->pid; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_unlock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id) +{ + snd_card_t *card = file->card; + snd_ctl_elem_id_t id; + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner == NULL) + result = -EINVAL; + else if (vd->owner != file) + result = -EPERM; + else { + vd->owner = NULL; + vd->owner_pid = 0; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +struct user_element { + snd_ctl_elem_info_t info; + void *elem_data; /* element data */ + unsigned long elem_data_size; /* size of element data in bytes */ + void *priv_data; /* private data (like strings for enumerated type) */ + unsigned long priv_data_size; /* size of private data in bytes */ +}; + +static int snd_ctl_elem_user_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + struct user_element *ue = kcontrol->private_data; + + *uinfo = ue->info; + return 0; +} + +static int snd_ctl_elem_user_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + struct user_element *ue = kcontrol->private_data; + + memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size); + return 0; +} + +static int snd_ctl_elem_user_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + int change; + struct user_element *ue = kcontrol->private_data; + + change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0; + if (change) + memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size); + return change; +} + +static void snd_ctl_elem_user_free(snd_kcontrol_t * kcontrol) +{ + kfree(kcontrol->private_data); +} + +static int snd_ctl_elem_add(snd_ctl_file_t *file, snd_ctl_elem_info_t *info, int replace) +{ + snd_card_t *card = file->card; + snd_kcontrol_t kctl, *_kctl; + unsigned int access; + long private_size; + struct user_element *ue; + int idx, err; + + if (card->user_ctl_count >= MAX_USER_CONTROLS) + return -ENOMEM; + if (info->count > 1024) + return -EINVAL; + access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : + (info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE)); + info->id.numid = 0; + memset(&kctl, 0, sizeof(kctl)); + down_write(&card->controls_rwsem); + _kctl = snd_ctl_find_id(card, &info->id); + err = 0; + if (_kctl) { + if (replace) + err = snd_ctl_remove(card, _kctl); + else + err = -EBUSY; + } else { + if (replace) + err = -ENOENT; + } + up_write(&card->controls_rwsem); + if (err < 0) + return err; + memcpy(&kctl.id, &info->id, sizeof(info->id)); + kctl.count = info->owner ? info->owner : 1; + access |= SNDRV_CTL_ELEM_ACCESS_USER; + kctl.info = snd_ctl_elem_user_info; + if (access & SNDRV_CTL_ELEM_ACCESS_READ) + kctl.get = snd_ctl_elem_user_get; + if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) + kctl.put = snd_ctl_elem_user_put; + switch (info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + private_size = sizeof(char); + if (info->count > 128) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + private_size = sizeof(long); + if (info->count > 128) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + private_size = sizeof(long long); + if (info->count > 64) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_BYTES: + private_size = sizeof(unsigned char); + if (info->count > 512) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_IEC958: + private_size = sizeof(struct sndrv_aes_iec958); + if (info->count != 1) + return -EINVAL; + break; + default: + return -EINVAL; + } + private_size *= info->count; + ue = kcalloc(1, sizeof(struct user_element) + private_size, GFP_KERNEL); + if (ue == NULL) + return -ENOMEM; + ue->info = *info; + ue->elem_data = (char *)ue + sizeof(*ue); + ue->elem_data_size = private_size; + kctl.private_free = snd_ctl_elem_user_free; + _kctl = snd_ctl_new(&kctl, access); + if (_kctl == NULL) { + kfree(_kctl->private_data); + return -ENOMEM; + } + _kctl->private_data = ue; + for (idx = 0; idx < _kctl->count; idx++) + _kctl->vd[idx].owner = file; + err = snd_ctl_add(card, _kctl); + if (err < 0) { + snd_ctl_free_one(_kctl); + return err; + } + + down_write(&card->controls_rwsem); + card->user_ctl_count++; + up_write(&card->controls_rwsem); + + return 0; +} + +static int snd_ctl_elem_add_user(snd_ctl_file_t *file, snd_ctl_elem_info_t __user *_info, int replace) +{ + snd_ctl_elem_info_t info; + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + return snd_ctl_elem_add(file, &info, replace); +} + +static int snd_ctl_elem_remove(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id) +{ + snd_ctl_elem_id_t id; + int err; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + err = snd_ctl_remove_unlocked_id(file, &id); + if (! err) { + snd_card_t *card = file->card; + down_write(&card->controls_rwsem); + card->user_ctl_count--; + up_write(&card->controls_rwsem); + } + return err; +} + +static int snd_ctl_subscribe_events(snd_ctl_file_t *file, int __user *ptr) +{ + int subscribe; + if (get_user(subscribe, ptr)) + return -EFAULT; + if (subscribe < 0) { + subscribe = file->subscribed; + if (put_user(subscribe, ptr)) + return -EFAULT; + return 0; + } + if (subscribe) { + file->subscribed = 1; + return 0; + } else if (file->subscribed) { + snd_ctl_empty_read_queue(file); + file->subscribed = 0; + } + return 0; +} + +#ifdef CONFIG_PM +/* + * change the power state + */ +static int snd_ctl_set_power_state(snd_card_t *card, unsigned int power_state) +{ + switch (power_state) { + case SNDRV_CTL_POWER_D0: + if (card->power_state != power_state) { + card->pm_resume(card); + snd_power_change_state(card, power_state); + } + break; + case SNDRV_CTL_POWER_D3hot: + if (card->power_state != power_state) { + card->pm_suspend(card, PMSG_SUSPEND); + snd_power_change_state(card, power_state); + } + break; + case SNDRV_CTL_POWER_D1: + case SNDRV_CTL_POWER_D2: + case SNDRV_CTL_POWER_D3cold: + /* not supported yet */ + default: + return -EINVAL; + } + return 0; +} +#endif + +static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_ctl_file_t *ctl; + snd_card_t *card; + struct list_head *list; + snd_kctl_ioctl_t *p; + void __user *argp = (void __user *)arg; + int __user *ip = argp; + int err; + + ctl = file->private_data; + card = ctl->card; + snd_assert(card != NULL, return -ENXIO); + 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(ctl->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(ctl->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_POWER: + if (get_user(err, ip)) + return -EFAULT; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; +#ifdef CONFIG_PM + if (card->pm_suspend && card->pm_resume) { + snd_power_lock(card); + err = snd_ctl_set_power_state(card, err); + snd_power_unlock(card); + } else +#endif + err = -ENOPROTOOPT; + return err; + case SNDRV_CTL_IOCTL_POWER_STATE: +#ifdef CONFIG_PM + return put_user(card->power_state, ip) ? -EFAULT : 0; +#else + return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0; +#endif + } + down_read(&snd_ioctl_rwsem); + list_for_each(list, &snd_control_ioctls) { + p = list_entry(list, snd_kctl_ioctl_t, list); + err = p->fioctl(card, ctl, cmd, arg); + if (err != -ENOIOCTLCMD) { + up_read(&snd_ioctl_rwsem); + return err; + } + } + up_read(&snd_ioctl_rwsem); + snd_printd("unknown ioctl = 0x%x\n", cmd); + return -ENOTTY; +} + +static ssize_t snd_ctl_read(struct file *file, char __user *buffer, size_t count, loff_t * offset) +{ + snd_ctl_file_t *ctl; + int err = 0; + ssize_t result = 0; + + ctl = file->private_data; + snd_assert(ctl != NULL && ctl->card != NULL, return -ENXIO); + if (!ctl->subscribed) + return -EBADFD; + if (count < sizeof(snd_ctl_event_t)) + return -EINVAL; + spin_lock_irq(&ctl->read_lock); + while (count >= sizeof(snd_ctl_event_t)) { + snd_ctl_event_t ev; + snd_kctl_event_t *kev; + while (list_empty(&ctl->events)) { + wait_queue_t wait; + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + err = -EAGAIN; + goto __end_lock; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&ctl->change_sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&ctl->read_lock); + schedule(); + remove_wait_queue(&ctl->change_sleep, &wait); + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + spin_lock_irq(&ctl->read_lock); + } + kev = snd_kctl_event(ctl->events.next); + ev.type = SNDRV_CTL_EVENT_ELEM; + ev.data.elem.mask = kev->mask; + ev.data.elem.id = kev->id; + list_del(&kev->list); + spin_unlock_irq(&ctl->read_lock); + kfree(kev); + if (copy_to_user(buffer, &ev, sizeof(snd_ctl_event_t))) { + err = -EFAULT; + goto __end; + } + spin_lock_irq(&ctl->read_lock); + buffer += sizeof(snd_ctl_event_t); + count -= sizeof(snd_ctl_event_t); + result += sizeof(snd_ctl_event_t); + } + __end_lock: + spin_unlock_irq(&ctl->read_lock); + __end: + return result > 0 ? result : err; +} + +static unsigned int snd_ctl_poll(struct file *file, poll_table * wait) +{ + unsigned int mask; + snd_ctl_file_t *ctl; + + ctl = file->private_data; + if (!ctl->subscribed) + return 0; + poll_wait(file, &ctl->change_sleep, wait); + + mask = 0; + if (!list_empty(&ctl->events)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +/* + * register the device-specific control-ioctls. + * called from each device manager like pcm.c, hwdep.c, etc. + */ +static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists) +{ + snd_kctl_ioctl_t *pn; + + pn = kcalloc(1, sizeof(snd_kctl_ioctl_t), GFP_KERNEL); + if (pn == NULL) + return -ENOMEM; + pn->fioctl = fcn; + down_write(&snd_ioctl_rwsem); + list_add_tail(&pn->list, lists); + up_write(&snd_ioctl_rwsem); + return 0; +} + +int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls); +} + +#ifdef CONFIG_COMPAT +int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls); +} +#endif + +/* + * de-register the device-specific control-ioctls. + */ +static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists) +{ + struct list_head *list; + snd_kctl_ioctl_t *p; + + snd_runtime_check(fcn != NULL, return -EINVAL); + down_write(&snd_ioctl_rwsem); + list_for_each(list, lists) { + p = list_entry(list, snd_kctl_ioctl_t, list); + if (p->fioctl == fcn) { + list_del(&p->list); + up_write(&snd_ioctl_rwsem); + kfree(p); + return 0; + } + } + up_write(&snd_ioctl_rwsem); + snd_BUG(); + return -EINVAL; +} + +int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls); +} + +#ifdef CONFIG_COMPAT +int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls); +} + +#endif + +static int snd_ctl_fasync(int fd, struct file * file, int on) +{ + snd_ctl_file_t *ctl; + int err; + ctl = file->private_data; + err = fasync_helper(fd, file, on, &ctl->fasync); + if (err < 0) + return err; + return 0; +} + +/* + * ioctl32 compat + */ +#ifdef CONFIG_COMPAT +#include "control_compat.c" +#else +#define snd_ctl_ioctl_compat NULL +#endif + +/* + * INIT PART + */ + +static 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, +}; + +static snd_minor_t snd_ctl_reg = +{ + .comment = "ctl", + .f_ops = &snd_ctl_f_ops, +}; + +/* + * registration of the control device + */ +static int snd_ctl_dev_register(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + int err, cardnum; + char name[16]; + + snd_assert(card != NULL, return -ENXIO); + cardnum = card->number; + snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO); + sprintf(name, "controlC%i", cardnum); + if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, + card, 0, &snd_ctl_reg, name)) < 0) + return err; + return 0; +} + +/* + * disconnection of the control device + */ +static int snd_ctl_dev_disconnect(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + struct list_head *flist; + snd_ctl_file_t *ctl; + + down_read(&card->controls_rwsem); + list_for_each(flist, &card->ctl_files) { + ctl = snd_ctl_file(flist); + wake_up(&ctl->change_sleep); + kill_fasync(&ctl->fasync, SIGIO, POLL_ERR); + } + up_read(&card->controls_rwsem); + return 0; +} + +/* + * free all controls + */ +static int snd_ctl_dev_free(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + snd_kcontrol_t *control; + + down_write(&card->controls_rwsem); + while (!list_empty(&card->controls)) { + control = snd_kcontrol(card->controls.next); + snd_ctl_remove(card, control); + } + up_write(&card->controls_rwsem); + return 0; +} + +/* + * de-registration of the control device + */ +static int snd_ctl_dev_unregister(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + int err, cardnum; + + snd_assert(card != NULL, return -ENXIO); + cardnum = card->number; + snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO); + if ((err = snd_unregister_device(SNDRV_DEVICE_TYPE_CONTROL, card, 0)) < 0) + return err; + return snd_ctl_dev_free(device); +} + +/* + * create control core: + * called from init.c + */ +int snd_ctl_create(snd_card_t *card) +{ + static snd_device_ops_t ops = { + .dev_free = snd_ctl_dev_free, + .dev_register = snd_ctl_dev_register, + .dev_disconnect = snd_ctl_dev_disconnect, + .dev_unregister = snd_ctl_dev_unregister + }; + + snd_assert(card != NULL, return -ENXIO); + return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); +} |