162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Routines for driver control interface 462306a36Sopenharmony_ci * Copyright (c) by Jaroslav Kysela <perex@perex.cz> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/threads.h> 862306a36Sopenharmony_ci#include <linux/interrupt.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/moduleparam.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <linux/vmalloc.h> 1362306a36Sopenharmony_ci#include <linux/time.h> 1462306a36Sopenharmony_ci#include <linux/mm.h> 1562306a36Sopenharmony_ci#include <linux/math64.h> 1662306a36Sopenharmony_ci#include <linux/sched/signal.h> 1762306a36Sopenharmony_ci#include <sound/core.h> 1862306a36Sopenharmony_ci#include <sound/minors.h> 1962306a36Sopenharmony_ci#include <sound/info.h> 2062306a36Sopenharmony_ci#include <sound/control.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci// Max allocation size for user controls. 2362306a36Sopenharmony_cistatic int max_user_ctl_alloc_size = 8 * 1024 * 1024; 2462306a36Sopenharmony_cimodule_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444); 2562306a36Sopenharmony_ciMODULE_PARM_DESC(max_user_ctl_alloc_size, "Max allocation size for user controls"); 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define MAX_CONTROL_COUNT 1028 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistruct snd_kctl_ioctl { 3062306a36Sopenharmony_ci struct list_head list; /* list of all ioctls */ 3162306a36Sopenharmony_ci snd_kctl_ioctl_func_t fioctl; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic DECLARE_RWSEM(snd_ioctl_rwsem); 3562306a36Sopenharmony_cistatic DECLARE_RWSEM(snd_ctl_layer_rwsem); 3662306a36Sopenharmony_cistatic LIST_HEAD(snd_control_ioctls); 3762306a36Sopenharmony_ci#ifdef CONFIG_COMPAT 3862306a36Sopenharmony_cistatic LIST_HEAD(snd_control_compat_ioctls); 3962306a36Sopenharmony_ci#endif 4062306a36Sopenharmony_cistatic struct snd_ctl_layer_ops *snd_ctl_layer; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic int snd_ctl_remove_locked(struct snd_card *card, 4362306a36Sopenharmony_ci struct snd_kcontrol *kcontrol); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic int snd_ctl_open(struct inode *inode, struct file *file) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci unsigned long flags; 4862306a36Sopenharmony_ci struct snd_card *card; 4962306a36Sopenharmony_ci struct snd_ctl_file *ctl; 5062306a36Sopenharmony_ci int i, err; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci err = stream_open(inode, file); 5362306a36Sopenharmony_ci if (err < 0) 5462306a36Sopenharmony_ci return err; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); 5762306a36Sopenharmony_ci if (!card) { 5862306a36Sopenharmony_ci err = -ENODEV; 5962306a36Sopenharmony_ci goto __error1; 6062306a36Sopenharmony_ci } 6162306a36Sopenharmony_ci err = snd_card_file_add(card, file); 6262306a36Sopenharmony_ci if (err < 0) { 6362306a36Sopenharmony_ci err = -ENODEV; 6462306a36Sopenharmony_ci goto __error1; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci if (!try_module_get(card->module)) { 6762306a36Sopenharmony_ci err = -EFAULT; 6862306a36Sopenharmony_ci goto __error2; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); 7162306a36Sopenharmony_ci if (ctl == NULL) { 7262306a36Sopenharmony_ci err = -ENOMEM; 7362306a36Sopenharmony_ci goto __error; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci INIT_LIST_HEAD(&ctl->events); 7662306a36Sopenharmony_ci init_waitqueue_head(&ctl->change_sleep); 7762306a36Sopenharmony_ci spin_lock_init(&ctl->read_lock); 7862306a36Sopenharmony_ci ctl->card = card; 7962306a36Sopenharmony_ci for (i = 0; i < SND_CTL_SUBDEV_ITEMS; i++) 8062306a36Sopenharmony_ci ctl->preferred_subdevice[i] = -1; 8162306a36Sopenharmony_ci ctl->pid = get_pid(task_pid(current)); 8262306a36Sopenharmony_ci file->private_data = ctl; 8362306a36Sopenharmony_ci write_lock_irqsave(&card->ctl_files_rwlock, flags); 8462306a36Sopenharmony_ci list_add_tail(&ctl->list, &card->ctl_files); 8562306a36Sopenharmony_ci write_unlock_irqrestore(&card->ctl_files_rwlock, flags); 8662306a36Sopenharmony_ci snd_card_unref(card); 8762306a36Sopenharmony_ci return 0; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci __error: 9062306a36Sopenharmony_ci module_put(card->module); 9162306a36Sopenharmony_ci __error2: 9262306a36Sopenharmony_ci snd_card_file_remove(card, file); 9362306a36Sopenharmony_ci __error1: 9462306a36Sopenharmony_ci if (card) 9562306a36Sopenharmony_ci snd_card_unref(card); 9662306a36Sopenharmony_ci return err; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic void snd_ctl_empty_read_queue(struct snd_ctl_file * ctl) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci unsigned long flags; 10262306a36Sopenharmony_ci struct snd_kctl_event *cread; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci spin_lock_irqsave(&ctl->read_lock, flags); 10562306a36Sopenharmony_ci while (!list_empty(&ctl->events)) { 10662306a36Sopenharmony_ci cread = snd_kctl_event(ctl->events.next); 10762306a36Sopenharmony_ci list_del(&cread->list); 10862306a36Sopenharmony_ci kfree(cread); 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci spin_unlock_irqrestore(&ctl->read_lock, flags); 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int snd_ctl_release(struct inode *inode, struct file *file) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci unsigned long flags; 11662306a36Sopenharmony_ci struct snd_card *card; 11762306a36Sopenharmony_ci struct snd_ctl_file *ctl; 11862306a36Sopenharmony_ci struct snd_kcontrol *control; 11962306a36Sopenharmony_ci unsigned int idx; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci ctl = file->private_data; 12262306a36Sopenharmony_ci file->private_data = NULL; 12362306a36Sopenharmony_ci card = ctl->card; 12462306a36Sopenharmony_ci write_lock_irqsave(&card->ctl_files_rwlock, flags); 12562306a36Sopenharmony_ci list_del(&ctl->list); 12662306a36Sopenharmony_ci write_unlock_irqrestore(&card->ctl_files_rwlock, flags); 12762306a36Sopenharmony_ci down_write(&card->controls_rwsem); 12862306a36Sopenharmony_ci list_for_each_entry(control, &card->controls, list) 12962306a36Sopenharmony_ci for (idx = 0; idx < control->count; idx++) 13062306a36Sopenharmony_ci if (control->vd[idx].owner == ctl) 13162306a36Sopenharmony_ci control->vd[idx].owner = NULL; 13262306a36Sopenharmony_ci up_write(&card->controls_rwsem); 13362306a36Sopenharmony_ci snd_fasync_free(ctl->fasync); 13462306a36Sopenharmony_ci snd_ctl_empty_read_queue(ctl); 13562306a36Sopenharmony_ci put_pid(ctl->pid); 13662306a36Sopenharmony_ci kfree(ctl); 13762306a36Sopenharmony_ci module_put(card->module); 13862306a36Sopenharmony_ci snd_card_file_remove(card, file); 13962306a36Sopenharmony_ci return 0; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci/** 14362306a36Sopenharmony_ci * snd_ctl_notify - Send notification to user-space for a control change 14462306a36Sopenharmony_ci * @card: the card to send notification 14562306a36Sopenharmony_ci * @mask: the event mask, SNDRV_CTL_EVENT_* 14662306a36Sopenharmony_ci * @id: the ctl element id to send notification 14762306a36Sopenharmony_ci * 14862306a36Sopenharmony_ci * This function adds an event record with the given id and mask, appends 14962306a36Sopenharmony_ci * to the list and wakes up the user-space for notification. This can be 15062306a36Sopenharmony_ci * called in the atomic context. 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_civoid snd_ctl_notify(struct snd_card *card, unsigned int mask, 15362306a36Sopenharmony_ci struct snd_ctl_elem_id *id) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci unsigned long flags; 15662306a36Sopenharmony_ci struct snd_ctl_file *ctl; 15762306a36Sopenharmony_ci struct snd_kctl_event *ev; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (snd_BUG_ON(!card || !id)) 16062306a36Sopenharmony_ci return; 16162306a36Sopenharmony_ci if (card->shutdown) 16262306a36Sopenharmony_ci return; 16362306a36Sopenharmony_ci read_lock_irqsave(&card->ctl_files_rwlock, flags); 16462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_MIXER_OSS) 16562306a36Sopenharmony_ci card->mixer_oss_change_count++; 16662306a36Sopenharmony_ci#endif 16762306a36Sopenharmony_ci list_for_each_entry(ctl, &card->ctl_files, list) { 16862306a36Sopenharmony_ci if (!ctl->subscribed) 16962306a36Sopenharmony_ci continue; 17062306a36Sopenharmony_ci spin_lock(&ctl->read_lock); 17162306a36Sopenharmony_ci list_for_each_entry(ev, &ctl->events, list) { 17262306a36Sopenharmony_ci if (ev->id.numid == id->numid) { 17362306a36Sopenharmony_ci ev->mask |= mask; 17462306a36Sopenharmony_ci goto _found; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci ev = kzalloc(sizeof(*ev), GFP_ATOMIC); 17862306a36Sopenharmony_ci if (ev) { 17962306a36Sopenharmony_ci ev->id = *id; 18062306a36Sopenharmony_ci ev->mask = mask; 18162306a36Sopenharmony_ci list_add_tail(&ev->list, &ctl->events); 18262306a36Sopenharmony_ci } else { 18362306a36Sopenharmony_ci dev_err(card->dev, "No memory available to allocate event\n"); 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci _found: 18662306a36Sopenharmony_ci wake_up(&ctl->change_sleep); 18762306a36Sopenharmony_ci spin_unlock(&ctl->read_lock); 18862306a36Sopenharmony_ci snd_kill_fasync(ctl->fasync, SIGIO, POLL_IN); 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci read_unlock_irqrestore(&card->ctl_files_rwlock, flags); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_notify); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci/** 19562306a36Sopenharmony_ci * snd_ctl_notify_one - Send notification to user-space for a control change 19662306a36Sopenharmony_ci * @card: the card to send notification 19762306a36Sopenharmony_ci * @mask: the event mask, SNDRV_CTL_EVENT_* 19862306a36Sopenharmony_ci * @kctl: the pointer with the control instance 19962306a36Sopenharmony_ci * @ioff: the additional offset to the control index 20062306a36Sopenharmony_ci * 20162306a36Sopenharmony_ci * This function calls snd_ctl_notify() and does additional jobs 20262306a36Sopenharmony_ci * like LED state changes. 20362306a36Sopenharmony_ci */ 20462306a36Sopenharmony_civoid snd_ctl_notify_one(struct snd_card *card, unsigned int mask, 20562306a36Sopenharmony_ci struct snd_kcontrol *kctl, unsigned int ioff) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct snd_ctl_elem_id id = kctl->id; 20862306a36Sopenharmony_ci struct snd_ctl_layer_ops *lops; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci id.index += ioff; 21162306a36Sopenharmony_ci id.numid += ioff; 21262306a36Sopenharmony_ci snd_ctl_notify(card, mask, &id); 21362306a36Sopenharmony_ci down_read(&snd_ctl_layer_rwsem); 21462306a36Sopenharmony_ci for (lops = snd_ctl_layer; lops; lops = lops->next) 21562306a36Sopenharmony_ci lops->lnotify(card, mask, kctl, ioff); 21662306a36Sopenharmony_ci up_read(&snd_ctl_layer_rwsem); 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_notify_one); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci/** 22162306a36Sopenharmony_ci * snd_ctl_new - create a new control instance with some elements 22262306a36Sopenharmony_ci * @kctl: the pointer to store new control instance 22362306a36Sopenharmony_ci * @count: the number of elements in this control 22462306a36Sopenharmony_ci * @access: the default access flags for elements in this control 22562306a36Sopenharmony_ci * @file: given when locking these elements 22662306a36Sopenharmony_ci * 22762306a36Sopenharmony_ci * Allocates a memory object for a new control instance. The instance has 22862306a36Sopenharmony_ci * elements as many as the given number (@count). Each element has given 22962306a36Sopenharmony_ci * access permissions (@access). Each element is locked when @file is given. 23062306a36Sopenharmony_ci * 23162306a36Sopenharmony_ci * Return: 0 on success, error code on failure 23262306a36Sopenharmony_ci */ 23362306a36Sopenharmony_cistatic int snd_ctl_new(struct snd_kcontrol **kctl, unsigned int count, 23462306a36Sopenharmony_ci unsigned int access, struct snd_ctl_file *file) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci unsigned int idx; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci if (count == 0 || count > MAX_CONTROL_COUNT) 23962306a36Sopenharmony_ci return -EINVAL; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci *kctl = kzalloc(struct_size(*kctl, vd, count), GFP_KERNEL); 24262306a36Sopenharmony_ci if (!*kctl) 24362306a36Sopenharmony_ci return -ENOMEM; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci for (idx = 0; idx < count; idx++) { 24662306a36Sopenharmony_ci (*kctl)->vd[idx].access = access; 24762306a36Sopenharmony_ci (*kctl)->vd[idx].owner = file; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci (*kctl)->count = count; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci return 0; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/** 25562306a36Sopenharmony_ci * snd_ctl_new1 - create a control instance from the template 25662306a36Sopenharmony_ci * @ncontrol: the initialization record 25762306a36Sopenharmony_ci * @private_data: the private data to set 25862306a36Sopenharmony_ci * 25962306a36Sopenharmony_ci * Allocates a new struct snd_kcontrol instance and initialize from the given 26062306a36Sopenharmony_ci * template. When the access field of ncontrol is 0, it's assumed as 26162306a36Sopenharmony_ci * READWRITE access. When the count field is 0, it's assumes as one. 26262306a36Sopenharmony_ci * 26362306a36Sopenharmony_ci * Return: The pointer of the newly generated instance, or %NULL on failure. 26462306a36Sopenharmony_ci */ 26562306a36Sopenharmony_cistruct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, 26662306a36Sopenharmony_ci void *private_data) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci struct snd_kcontrol *kctl; 26962306a36Sopenharmony_ci unsigned int count; 27062306a36Sopenharmony_ci unsigned int access; 27162306a36Sopenharmony_ci int err; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci if (snd_BUG_ON(!ncontrol || !ncontrol->info)) 27462306a36Sopenharmony_ci return NULL; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci count = ncontrol->count; 27762306a36Sopenharmony_ci if (count == 0) 27862306a36Sopenharmony_ci count = 1; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci access = ncontrol->access; 28162306a36Sopenharmony_ci if (access == 0) 28262306a36Sopenharmony_ci access = SNDRV_CTL_ELEM_ACCESS_READWRITE; 28362306a36Sopenharmony_ci access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE | 28462306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_VOLATILE | 28562306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_INACTIVE | 28662306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE | 28762306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND | 28862306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK | 28962306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_LED_MASK | 29062306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci err = snd_ctl_new(&kctl, count, access, NULL); 29362306a36Sopenharmony_ci if (err < 0) 29462306a36Sopenharmony_ci return NULL; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci /* The 'numid' member is decided when calling snd_ctl_add(). */ 29762306a36Sopenharmony_ci kctl->id.iface = ncontrol->iface; 29862306a36Sopenharmony_ci kctl->id.device = ncontrol->device; 29962306a36Sopenharmony_ci kctl->id.subdevice = ncontrol->subdevice; 30062306a36Sopenharmony_ci if (ncontrol->name) { 30162306a36Sopenharmony_ci strscpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name)); 30262306a36Sopenharmony_ci if (strcmp(ncontrol->name, kctl->id.name) != 0) 30362306a36Sopenharmony_ci pr_warn("ALSA: Control name '%s' truncated to '%s'\n", 30462306a36Sopenharmony_ci ncontrol->name, kctl->id.name); 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci kctl->id.index = ncontrol->index; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci kctl->info = ncontrol->info; 30962306a36Sopenharmony_ci kctl->get = ncontrol->get; 31062306a36Sopenharmony_ci kctl->put = ncontrol->put; 31162306a36Sopenharmony_ci kctl->tlv.p = ncontrol->tlv.p; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci kctl->private_value = ncontrol->private_value; 31462306a36Sopenharmony_ci kctl->private_data = private_data; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci return kctl; 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_new1); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci/** 32162306a36Sopenharmony_ci * snd_ctl_free_one - release the control instance 32262306a36Sopenharmony_ci * @kcontrol: the control instance 32362306a36Sopenharmony_ci * 32462306a36Sopenharmony_ci * Releases the control instance created via snd_ctl_new() 32562306a36Sopenharmony_ci * or snd_ctl_new1(). 32662306a36Sopenharmony_ci * Don't call this after the control was added to the card. 32762306a36Sopenharmony_ci */ 32862306a36Sopenharmony_civoid snd_ctl_free_one(struct snd_kcontrol *kcontrol) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci if (kcontrol) { 33162306a36Sopenharmony_ci if (kcontrol->private_free) 33262306a36Sopenharmony_ci kcontrol->private_free(kcontrol); 33362306a36Sopenharmony_ci kfree(kcontrol); 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_free_one); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_cistatic bool snd_ctl_remove_numid_conflict(struct snd_card *card, 33962306a36Sopenharmony_ci unsigned int count) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci struct snd_kcontrol *kctl; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci /* Make sure that the ids assigned to the control do not wrap around */ 34462306a36Sopenharmony_ci if (card->last_numid >= UINT_MAX - count) 34562306a36Sopenharmony_ci card->last_numid = 0; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci list_for_each_entry(kctl, &card->controls, list) { 34862306a36Sopenharmony_ci if (kctl->id.numid < card->last_numid + 1 + count && 34962306a36Sopenharmony_ci kctl->id.numid + kctl->count > card->last_numid + 1) { 35062306a36Sopenharmony_ci card->last_numid = kctl->id.numid + kctl->count - 1; 35162306a36Sopenharmony_ci return true; 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci return false; 35562306a36Sopenharmony_ci} 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_cistatic int snd_ctl_find_hole(struct snd_card *card, unsigned int count) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci unsigned int iter = 100000; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci while (snd_ctl_remove_numid_conflict(card, count)) { 36262306a36Sopenharmony_ci if (--iter == 0) { 36362306a36Sopenharmony_ci /* this situation is very unlikely */ 36462306a36Sopenharmony_ci dev_err(card->dev, "unable to allocate new control numid\n"); 36562306a36Sopenharmony_ci return -ENOMEM; 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci return 0; 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci/* check whether the given id is contained in the given kctl */ 37262306a36Sopenharmony_cistatic bool elem_id_matches(const struct snd_kcontrol *kctl, 37362306a36Sopenharmony_ci const struct snd_ctl_elem_id *id) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci return kctl->id.iface == id->iface && 37662306a36Sopenharmony_ci kctl->id.device == id->device && 37762306a36Sopenharmony_ci kctl->id.subdevice == id->subdevice && 37862306a36Sopenharmony_ci !strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)) && 37962306a36Sopenharmony_ci kctl->id.index <= id->index && 38062306a36Sopenharmony_ci kctl->id.index + kctl->count > id->index; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci#ifdef CONFIG_SND_CTL_FAST_LOOKUP 38462306a36Sopenharmony_ci/* Compute a hash key for the corresponding ctl id 38562306a36Sopenharmony_ci * It's for the name lookup, hence the numid is excluded. 38662306a36Sopenharmony_ci * The hash key is bound in LONG_MAX to be used for Xarray key. 38762306a36Sopenharmony_ci */ 38862306a36Sopenharmony_ci#define MULTIPLIER 37 38962306a36Sopenharmony_cistatic unsigned long get_ctl_id_hash(const struct snd_ctl_elem_id *id) 39062306a36Sopenharmony_ci{ 39162306a36Sopenharmony_ci int i; 39262306a36Sopenharmony_ci unsigned long h; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci h = id->iface; 39562306a36Sopenharmony_ci h = MULTIPLIER * h + id->device; 39662306a36Sopenharmony_ci h = MULTIPLIER * h + id->subdevice; 39762306a36Sopenharmony_ci for (i = 0; i < SNDRV_CTL_ELEM_ID_NAME_MAXLEN && id->name[i]; i++) 39862306a36Sopenharmony_ci h = MULTIPLIER * h + id->name[i]; 39962306a36Sopenharmony_ci h = MULTIPLIER * h + id->index; 40062306a36Sopenharmony_ci h &= LONG_MAX; 40162306a36Sopenharmony_ci return h; 40262306a36Sopenharmony_ci} 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci/* add hash entries to numid and ctl xarray tables */ 40562306a36Sopenharmony_cistatic void add_hash_entries(struct snd_card *card, 40662306a36Sopenharmony_ci struct snd_kcontrol *kcontrol) 40762306a36Sopenharmony_ci{ 40862306a36Sopenharmony_ci struct snd_ctl_elem_id id = kcontrol->id; 40962306a36Sopenharmony_ci int i; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci xa_store_range(&card->ctl_numids, kcontrol->id.numid, 41262306a36Sopenharmony_ci kcontrol->id.numid + kcontrol->count - 1, 41362306a36Sopenharmony_ci kcontrol, GFP_KERNEL); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci for (i = 0; i < kcontrol->count; i++) { 41662306a36Sopenharmony_ci id.index = kcontrol->id.index + i; 41762306a36Sopenharmony_ci if (xa_insert(&card->ctl_hash, get_ctl_id_hash(&id), 41862306a36Sopenharmony_ci kcontrol, GFP_KERNEL)) { 41962306a36Sopenharmony_ci /* skip hash for this entry, noting we had collision */ 42062306a36Sopenharmony_ci card->ctl_hash_collision = true; 42162306a36Sopenharmony_ci dev_dbg(card->dev, "ctl_hash collision %d:%s:%d\n", 42262306a36Sopenharmony_ci id.iface, id.name, id.index); 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci } 42562306a36Sopenharmony_ci} 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci/* remove hash entries that have been added */ 42862306a36Sopenharmony_cistatic void remove_hash_entries(struct snd_card *card, 42962306a36Sopenharmony_ci struct snd_kcontrol *kcontrol) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct snd_ctl_elem_id id = kcontrol->id; 43262306a36Sopenharmony_ci struct snd_kcontrol *matched; 43362306a36Sopenharmony_ci unsigned long h; 43462306a36Sopenharmony_ci int i; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci for (i = 0; i < kcontrol->count; i++) { 43762306a36Sopenharmony_ci xa_erase(&card->ctl_numids, id.numid); 43862306a36Sopenharmony_ci h = get_ctl_id_hash(&id); 43962306a36Sopenharmony_ci matched = xa_load(&card->ctl_hash, h); 44062306a36Sopenharmony_ci if (matched && (matched == kcontrol || 44162306a36Sopenharmony_ci elem_id_matches(matched, &id))) 44262306a36Sopenharmony_ci xa_erase(&card->ctl_hash, h); 44362306a36Sopenharmony_ci id.index++; 44462306a36Sopenharmony_ci id.numid++; 44562306a36Sopenharmony_ci } 44662306a36Sopenharmony_ci} 44762306a36Sopenharmony_ci#else /* CONFIG_SND_CTL_FAST_LOOKUP */ 44862306a36Sopenharmony_cistatic inline void add_hash_entries(struct snd_card *card, 44962306a36Sopenharmony_ci struct snd_kcontrol *kcontrol) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci} 45262306a36Sopenharmony_cistatic inline void remove_hash_entries(struct snd_card *card, 45362306a36Sopenharmony_ci struct snd_kcontrol *kcontrol) 45462306a36Sopenharmony_ci{ 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci#endif /* CONFIG_SND_CTL_FAST_LOOKUP */ 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cienum snd_ctl_add_mode { 45962306a36Sopenharmony_ci CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE, 46062306a36Sopenharmony_ci}; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci/* add/replace a new kcontrol object; call with card->controls_rwsem locked */ 46362306a36Sopenharmony_cistatic int __snd_ctl_add_replace(struct snd_card *card, 46462306a36Sopenharmony_ci struct snd_kcontrol *kcontrol, 46562306a36Sopenharmony_ci enum snd_ctl_add_mode mode) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci struct snd_ctl_elem_id id; 46862306a36Sopenharmony_ci unsigned int idx; 46962306a36Sopenharmony_ci struct snd_kcontrol *old; 47062306a36Sopenharmony_ci int err; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci lockdep_assert_held_write(&card->controls_rwsem); 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci id = kcontrol->id; 47562306a36Sopenharmony_ci if (id.index > UINT_MAX - kcontrol->count) 47662306a36Sopenharmony_ci return -EINVAL; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci old = snd_ctl_find_id_locked(card, &id); 47962306a36Sopenharmony_ci if (!old) { 48062306a36Sopenharmony_ci if (mode == CTL_REPLACE) 48162306a36Sopenharmony_ci return -EINVAL; 48262306a36Sopenharmony_ci } else { 48362306a36Sopenharmony_ci if (mode == CTL_ADD_EXCLUSIVE) { 48462306a36Sopenharmony_ci dev_err(card->dev, 48562306a36Sopenharmony_ci "control %i:%i:%i:%s:%i is already present\n", 48662306a36Sopenharmony_ci id.iface, id.device, id.subdevice, id.name, 48762306a36Sopenharmony_ci id.index); 48862306a36Sopenharmony_ci return -EBUSY; 48962306a36Sopenharmony_ci } 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci err = snd_ctl_remove_locked(card, old); 49262306a36Sopenharmony_ci if (err < 0) 49362306a36Sopenharmony_ci return err; 49462306a36Sopenharmony_ci } 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci if (snd_ctl_find_hole(card, kcontrol->count) < 0) 49762306a36Sopenharmony_ci return -ENOMEM; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci list_add_tail(&kcontrol->list, &card->controls); 50062306a36Sopenharmony_ci card->controls_count += kcontrol->count; 50162306a36Sopenharmony_ci kcontrol->id.numid = card->last_numid + 1; 50262306a36Sopenharmony_ci card->last_numid += kcontrol->count; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci add_hash_entries(card, kcontrol); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci for (idx = 0; idx < kcontrol->count; idx++) 50762306a36Sopenharmony_ci snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx); 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci return 0; 51062306a36Sopenharmony_ci} 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_cistatic int snd_ctl_add_replace(struct snd_card *card, 51362306a36Sopenharmony_ci struct snd_kcontrol *kcontrol, 51462306a36Sopenharmony_ci enum snd_ctl_add_mode mode) 51562306a36Sopenharmony_ci{ 51662306a36Sopenharmony_ci int err = -EINVAL; 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci if (! kcontrol) 51962306a36Sopenharmony_ci return err; 52062306a36Sopenharmony_ci if (snd_BUG_ON(!card || !kcontrol->info)) 52162306a36Sopenharmony_ci goto error; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci down_write(&card->controls_rwsem); 52462306a36Sopenharmony_ci err = __snd_ctl_add_replace(card, kcontrol, mode); 52562306a36Sopenharmony_ci up_write(&card->controls_rwsem); 52662306a36Sopenharmony_ci if (err < 0) 52762306a36Sopenharmony_ci goto error; 52862306a36Sopenharmony_ci return 0; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci error: 53162306a36Sopenharmony_ci snd_ctl_free_one(kcontrol); 53262306a36Sopenharmony_ci return err; 53362306a36Sopenharmony_ci} 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci/** 53662306a36Sopenharmony_ci * snd_ctl_add - add the control instance to the card 53762306a36Sopenharmony_ci * @card: the card instance 53862306a36Sopenharmony_ci * @kcontrol: the control instance to add 53962306a36Sopenharmony_ci * 54062306a36Sopenharmony_ci * Adds the control instance created via snd_ctl_new() or 54162306a36Sopenharmony_ci * snd_ctl_new1() to the given card. Assigns also an unique 54262306a36Sopenharmony_ci * numid used for fast search. 54362306a36Sopenharmony_ci * 54462306a36Sopenharmony_ci * It frees automatically the control which cannot be added. 54562306a36Sopenharmony_ci * 54662306a36Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure. 54762306a36Sopenharmony_ci * 54862306a36Sopenharmony_ci */ 54962306a36Sopenharmony_ciint snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) 55062306a36Sopenharmony_ci{ 55162306a36Sopenharmony_ci return snd_ctl_add_replace(card, kcontrol, CTL_ADD_EXCLUSIVE); 55262306a36Sopenharmony_ci} 55362306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_add); 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci/** 55662306a36Sopenharmony_ci * snd_ctl_replace - replace the control instance of the card 55762306a36Sopenharmony_ci * @card: the card instance 55862306a36Sopenharmony_ci * @kcontrol: the control instance to replace 55962306a36Sopenharmony_ci * @add_on_replace: add the control if not already added 56062306a36Sopenharmony_ci * 56162306a36Sopenharmony_ci * Replaces the given control. If the given control does not exist 56262306a36Sopenharmony_ci * and the add_on_replace flag is set, the control is added. If the 56362306a36Sopenharmony_ci * control exists, it is destroyed first. 56462306a36Sopenharmony_ci * 56562306a36Sopenharmony_ci * It frees automatically the control which cannot be added or replaced. 56662306a36Sopenharmony_ci * 56762306a36Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure. 56862306a36Sopenharmony_ci */ 56962306a36Sopenharmony_ciint snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, 57062306a36Sopenharmony_ci bool add_on_replace) 57162306a36Sopenharmony_ci{ 57262306a36Sopenharmony_ci return snd_ctl_add_replace(card, kcontrol, 57362306a36Sopenharmony_ci add_on_replace ? CTL_ADD_ON_REPLACE : CTL_REPLACE); 57462306a36Sopenharmony_ci} 57562306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_replace); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_cistatic int __snd_ctl_remove(struct snd_card *card, 57862306a36Sopenharmony_ci struct snd_kcontrol *kcontrol, 57962306a36Sopenharmony_ci bool remove_hash) 58062306a36Sopenharmony_ci{ 58162306a36Sopenharmony_ci unsigned int idx; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci lockdep_assert_held_write(&card->controls_rwsem); 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci if (snd_BUG_ON(!card || !kcontrol)) 58662306a36Sopenharmony_ci return -EINVAL; 58762306a36Sopenharmony_ci list_del(&kcontrol->list); 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci if (remove_hash) 59062306a36Sopenharmony_ci remove_hash_entries(card, kcontrol); 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci card->controls_count -= kcontrol->count; 59362306a36Sopenharmony_ci for (idx = 0; idx < kcontrol->count; idx++) 59462306a36Sopenharmony_ci snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx); 59562306a36Sopenharmony_ci snd_ctl_free_one(kcontrol); 59662306a36Sopenharmony_ci return 0; 59762306a36Sopenharmony_ci} 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_cistatic inline int snd_ctl_remove_locked(struct snd_card *card, 60062306a36Sopenharmony_ci struct snd_kcontrol *kcontrol) 60162306a36Sopenharmony_ci{ 60262306a36Sopenharmony_ci return __snd_ctl_remove(card, kcontrol, true); 60362306a36Sopenharmony_ci} 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci/** 60662306a36Sopenharmony_ci * snd_ctl_remove - remove the control from the card and release it 60762306a36Sopenharmony_ci * @card: the card instance 60862306a36Sopenharmony_ci * @kcontrol: the control instance to remove 60962306a36Sopenharmony_ci * 61062306a36Sopenharmony_ci * Removes the control from the card and then releases the instance. 61162306a36Sopenharmony_ci * You don't need to call snd_ctl_free_one(). 61262306a36Sopenharmony_ci * 61362306a36Sopenharmony_ci * Return: 0 if successful, or a negative error code on failure. 61462306a36Sopenharmony_ci * 61562306a36Sopenharmony_ci * Note that this function takes card->controls_rwsem lock internally. 61662306a36Sopenharmony_ci */ 61762306a36Sopenharmony_ciint snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol) 61862306a36Sopenharmony_ci{ 61962306a36Sopenharmony_ci int ret; 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ci down_write(&card->controls_rwsem); 62262306a36Sopenharmony_ci ret = snd_ctl_remove_locked(card, kcontrol); 62362306a36Sopenharmony_ci up_write(&card->controls_rwsem); 62462306a36Sopenharmony_ci return ret; 62562306a36Sopenharmony_ci} 62662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_remove); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci/** 62962306a36Sopenharmony_ci * snd_ctl_remove_id - remove the control of the given id and release it 63062306a36Sopenharmony_ci * @card: the card instance 63162306a36Sopenharmony_ci * @id: the control id to remove 63262306a36Sopenharmony_ci * 63362306a36Sopenharmony_ci * Finds the control instance with the given id, removes it from the 63462306a36Sopenharmony_ci * card list and releases it. 63562306a36Sopenharmony_ci * 63662306a36Sopenharmony_ci * Return: 0 if successful, or a negative error code on failure. 63762306a36Sopenharmony_ci */ 63862306a36Sopenharmony_ciint snd_ctl_remove_id(struct snd_card *card, struct snd_ctl_elem_id *id) 63962306a36Sopenharmony_ci{ 64062306a36Sopenharmony_ci struct snd_kcontrol *kctl; 64162306a36Sopenharmony_ci int ret; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci down_write(&card->controls_rwsem); 64462306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, id); 64562306a36Sopenharmony_ci if (kctl == NULL) { 64662306a36Sopenharmony_ci up_write(&card->controls_rwsem); 64762306a36Sopenharmony_ci return -ENOENT; 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci ret = snd_ctl_remove_locked(card, kctl); 65062306a36Sopenharmony_ci up_write(&card->controls_rwsem); 65162306a36Sopenharmony_ci return ret; 65262306a36Sopenharmony_ci} 65362306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_remove_id); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci/** 65662306a36Sopenharmony_ci * snd_ctl_remove_user_ctl - remove and release the unlocked user control 65762306a36Sopenharmony_ci * @file: active control handle 65862306a36Sopenharmony_ci * @id: the control id to remove 65962306a36Sopenharmony_ci * 66062306a36Sopenharmony_ci * Finds the control instance with the given id, removes it from the 66162306a36Sopenharmony_ci * card list and releases it. 66262306a36Sopenharmony_ci * 66362306a36Sopenharmony_ci * Return: 0 if successful, or a negative error code on failure. 66462306a36Sopenharmony_ci */ 66562306a36Sopenharmony_cistatic int snd_ctl_remove_user_ctl(struct snd_ctl_file * file, 66662306a36Sopenharmony_ci struct snd_ctl_elem_id *id) 66762306a36Sopenharmony_ci{ 66862306a36Sopenharmony_ci struct snd_card *card = file->card; 66962306a36Sopenharmony_ci struct snd_kcontrol *kctl; 67062306a36Sopenharmony_ci int idx, ret; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci down_write(&card->controls_rwsem); 67362306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, id); 67462306a36Sopenharmony_ci if (kctl == NULL) { 67562306a36Sopenharmony_ci ret = -ENOENT; 67662306a36Sopenharmony_ci goto error; 67762306a36Sopenharmony_ci } 67862306a36Sopenharmony_ci if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_USER)) { 67962306a36Sopenharmony_ci ret = -EINVAL; 68062306a36Sopenharmony_ci goto error; 68162306a36Sopenharmony_ci } 68262306a36Sopenharmony_ci for (idx = 0; idx < kctl->count; idx++) 68362306a36Sopenharmony_ci if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) { 68462306a36Sopenharmony_ci ret = -EBUSY; 68562306a36Sopenharmony_ci goto error; 68662306a36Sopenharmony_ci } 68762306a36Sopenharmony_ci ret = snd_ctl_remove_locked(card, kctl); 68862306a36Sopenharmony_cierror: 68962306a36Sopenharmony_ci up_write(&card->controls_rwsem); 69062306a36Sopenharmony_ci return ret; 69162306a36Sopenharmony_ci} 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci/** 69462306a36Sopenharmony_ci * snd_ctl_activate_id - activate/inactivate the control of the given id 69562306a36Sopenharmony_ci * @card: the card instance 69662306a36Sopenharmony_ci * @id: the control id to activate/inactivate 69762306a36Sopenharmony_ci * @active: non-zero to activate 69862306a36Sopenharmony_ci * 69962306a36Sopenharmony_ci * Finds the control instance with the given id, and activate or 70062306a36Sopenharmony_ci * inactivate the control together with notification, if changed. 70162306a36Sopenharmony_ci * The given ID data is filled with full information. 70262306a36Sopenharmony_ci * 70362306a36Sopenharmony_ci * Return: 0 if unchanged, 1 if changed, or a negative error code on failure. 70462306a36Sopenharmony_ci */ 70562306a36Sopenharmony_ciint snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id, 70662306a36Sopenharmony_ci int active) 70762306a36Sopenharmony_ci{ 70862306a36Sopenharmony_ci struct snd_kcontrol *kctl; 70962306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 71062306a36Sopenharmony_ci unsigned int index_offset; 71162306a36Sopenharmony_ci int ret; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci down_write(&card->controls_rwsem); 71462306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, id); 71562306a36Sopenharmony_ci if (kctl == NULL) { 71662306a36Sopenharmony_ci ret = -ENOENT; 71762306a36Sopenharmony_ci goto unlock; 71862306a36Sopenharmony_ci } 71962306a36Sopenharmony_ci index_offset = snd_ctl_get_ioff(kctl, id); 72062306a36Sopenharmony_ci vd = &kctl->vd[index_offset]; 72162306a36Sopenharmony_ci ret = 0; 72262306a36Sopenharmony_ci if (active) { 72362306a36Sopenharmony_ci if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) 72462306a36Sopenharmony_ci goto unlock; 72562306a36Sopenharmony_ci vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; 72662306a36Sopenharmony_ci } else { 72762306a36Sopenharmony_ci if (vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) 72862306a36Sopenharmony_ci goto unlock; 72962306a36Sopenharmony_ci vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; 73062306a36Sopenharmony_ci } 73162306a36Sopenharmony_ci snd_ctl_build_ioff(id, kctl, index_offset); 73262306a36Sopenharmony_ci downgrade_write(&card->controls_rwsem); 73362306a36Sopenharmony_ci snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, index_offset); 73462306a36Sopenharmony_ci up_read(&card->controls_rwsem); 73562306a36Sopenharmony_ci return 1; 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci unlock: 73862306a36Sopenharmony_ci up_write(&card->controls_rwsem); 73962306a36Sopenharmony_ci return ret; 74062306a36Sopenharmony_ci} 74162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_activate_id); 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci/** 74462306a36Sopenharmony_ci * snd_ctl_rename_id - replace the id of a control on the card 74562306a36Sopenharmony_ci * @card: the card instance 74662306a36Sopenharmony_ci * @src_id: the old id 74762306a36Sopenharmony_ci * @dst_id: the new id 74862306a36Sopenharmony_ci * 74962306a36Sopenharmony_ci * Finds the control with the old id from the card, and replaces the 75062306a36Sopenharmony_ci * id with the new one. 75162306a36Sopenharmony_ci * 75262306a36Sopenharmony_ci * The function tries to keep the already assigned numid while replacing 75362306a36Sopenharmony_ci * the rest. 75462306a36Sopenharmony_ci * 75562306a36Sopenharmony_ci * Note that this function should be used only in the card initialization 75662306a36Sopenharmony_ci * phase. Calling after the card instantiation may cause issues with 75762306a36Sopenharmony_ci * user-space expecting persistent numids. 75862306a36Sopenharmony_ci * 75962306a36Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure. 76062306a36Sopenharmony_ci */ 76162306a36Sopenharmony_ciint snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, 76262306a36Sopenharmony_ci struct snd_ctl_elem_id *dst_id) 76362306a36Sopenharmony_ci{ 76462306a36Sopenharmony_ci struct snd_kcontrol *kctl; 76562306a36Sopenharmony_ci int saved_numid; 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci down_write(&card->controls_rwsem); 76862306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, src_id); 76962306a36Sopenharmony_ci if (kctl == NULL) { 77062306a36Sopenharmony_ci up_write(&card->controls_rwsem); 77162306a36Sopenharmony_ci return -ENOENT; 77262306a36Sopenharmony_ci } 77362306a36Sopenharmony_ci saved_numid = kctl->id.numid; 77462306a36Sopenharmony_ci remove_hash_entries(card, kctl); 77562306a36Sopenharmony_ci kctl->id = *dst_id; 77662306a36Sopenharmony_ci kctl->id.numid = saved_numid; 77762306a36Sopenharmony_ci add_hash_entries(card, kctl); 77862306a36Sopenharmony_ci up_write(&card->controls_rwsem); 77962306a36Sopenharmony_ci return 0; 78062306a36Sopenharmony_ci} 78162306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_rename_id); 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci/** 78462306a36Sopenharmony_ci * snd_ctl_rename - rename the control on the card 78562306a36Sopenharmony_ci * @card: the card instance 78662306a36Sopenharmony_ci * @kctl: the control to rename 78762306a36Sopenharmony_ci * @name: the new name 78862306a36Sopenharmony_ci * 78962306a36Sopenharmony_ci * Renames the specified control on the card to the new name. 79062306a36Sopenharmony_ci * 79162306a36Sopenharmony_ci * Note that this function takes card->controls_rwsem lock internally. 79262306a36Sopenharmony_ci */ 79362306a36Sopenharmony_civoid snd_ctl_rename(struct snd_card *card, struct snd_kcontrol *kctl, 79462306a36Sopenharmony_ci const char *name) 79562306a36Sopenharmony_ci{ 79662306a36Sopenharmony_ci down_write(&card->controls_rwsem); 79762306a36Sopenharmony_ci remove_hash_entries(card, kctl); 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci if (strscpy(kctl->id.name, name, sizeof(kctl->id.name)) < 0) 80062306a36Sopenharmony_ci pr_warn("ALSA: Renamed control new name '%s' truncated to '%s'\n", 80162306a36Sopenharmony_ci name, kctl->id.name); 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci add_hash_entries(card, kctl); 80462306a36Sopenharmony_ci up_write(&card->controls_rwsem); 80562306a36Sopenharmony_ci} 80662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_rename); 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci#ifndef CONFIG_SND_CTL_FAST_LOOKUP 80962306a36Sopenharmony_cistatic struct snd_kcontrol * 81062306a36Sopenharmony_cisnd_ctl_find_numid_slow(struct snd_card *card, unsigned int numid) 81162306a36Sopenharmony_ci{ 81262306a36Sopenharmony_ci struct snd_kcontrol *kctl; 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci list_for_each_entry(kctl, &card->controls, list) { 81562306a36Sopenharmony_ci if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid) 81662306a36Sopenharmony_ci return kctl; 81762306a36Sopenharmony_ci } 81862306a36Sopenharmony_ci return NULL; 81962306a36Sopenharmony_ci} 82062306a36Sopenharmony_ci#endif /* !CONFIG_SND_CTL_FAST_LOOKUP */ 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci/** 82362306a36Sopenharmony_ci * snd_ctl_find_numid_locked - find the control instance with the given number-id 82462306a36Sopenharmony_ci * @card: the card instance 82562306a36Sopenharmony_ci * @numid: the number-id to search 82662306a36Sopenharmony_ci * 82762306a36Sopenharmony_ci * Finds the control instance with the given number-id from the card. 82862306a36Sopenharmony_ci * 82962306a36Sopenharmony_ci * The caller must down card->controls_rwsem before calling this function 83062306a36Sopenharmony_ci * (if the race condition can happen). 83162306a36Sopenharmony_ci * 83262306a36Sopenharmony_ci * Return: The pointer of the instance if found, or %NULL if not. 83362306a36Sopenharmony_ci */ 83462306a36Sopenharmony_cistruct snd_kcontrol * 83562306a36Sopenharmony_cisnd_ctl_find_numid_locked(struct snd_card *card, unsigned int numid) 83662306a36Sopenharmony_ci{ 83762306a36Sopenharmony_ci if (snd_BUG_ON(!card || !numid)) 83862306a36Sopenharmony_ci return NULL; 83962306a36Sopenharmony_ci lockdep_assert_held(&card->controls_rwsem); 84062306a36Sopenharmony_ci#ifdef CONFIG_SND_CTL_FAST_LOOKUP 84162306a36Sopenharmony_ci return xa_load(&card->ctl_numids, numid); 84262306a36Sopenharmony_ci#else 84362306a36Sopenharmony_ci return snd_ctl_find_numid_slow(card, numid); 84462306a36Sopenharmony_ci#endif 84562306a36Sopenharmony_ci} 84662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_find_numid_locked); 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci/** 84962306a36Sopenharmony_ci * snd_ctl_find_numid - find the control instance with the given number-id 85062306a36Sopenharmony_ci * @card: the card instance 85162306a36Sopenharmony_ci * @numid: the number-id to search 85262306a36Sopenharmony_ci * 85362306a36Sopenharmony_ci * Finds the control instance with the given number-id from the card. 85462306a36Sopenharmony_ci * 85562306a36Sopenharmony_ci * Return: The pointer of the instance if found, or %NULL if not. 85662306a36Sopenharmony_ci * 85762306a36Sopenharmony_ci * Note that this function takes card->controls_rwsem lock internally. 85862306a36Sopenharmony_ci */ 85962306a36Sopenharmony_cistruct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, 86062306a36Sopenharmony_ci unsigned int numid) 86162306a36Sopenharmony_ci{ 86262306a36Sopenharmony_ci struct snd_kcontrol *kctl; 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ci down_read(&card->controls_rwsem); 86562306a36Sopenharmony_ci kctl = snd_ctl_find_numid_locked(card, numid); 86662306a36Sopenharmony_ci up_read(&card->controls_rwsem); 86762306a36Sopenharmony_ci return kctl; 86862306a36Sopenharmony_ci} 86962306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_find_numid); 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_ci/** 87262306a36Sopenharmony_ci * snd_ctl_find_id_locked - find the control instance with the given id 87362306a36Sopenharmony_ci * @card: the card instance 87462306a36Sopenharmony_ci * @id: the id to search 87562306a36Sopenharmony_ci * 87662306a36Sopenharmony_ci * Finds the control instance with the given id from the card. 87762306a36Sopenharmony_ci * 87862306a36Sopenharmony_ci * The caller must down card->controls_rwsem before calling this function 87962306a36Sopenharmony_ci * (if the race condition can happen). 88062306a36Sopenharmony_ci * 88162306a36Sopenharmony_ci * Return: The pointer of the instance if found, or %NULL if not. 88262306a36Sopenharmony_ci */ 88362306a36Sopenharmony_cistruct snd_kcontrol *snd_ctl_find_id_locked(struct snd_card *card, 88462306a36Sopenharmony_ci const struct snd_ctl_elem_id *id) 88562306a36Sopenharmony_ci{ 88662306a36Sopenharmony_ci struct snd_kcontrol *kctl; 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_ci if (snd_BUG_ON(!card || !id)) 88962306a36Sopenharmony_ci return NULL; 89062306a36Sopenharmony_ci lockdep_assert_held(&card->controls_rwsem); 89162306a36Sopenharmony_ci if (id->numid != 0) 89262306a36Sopenharmony_ci return snd_ctl_find_numid_locked(card, id->numid); 89362306a36Sopenharmony_ci#ifdef CONFIG_SND_CTL_FAST_LOOKUP 89462306a36Sopenharmony_ci kctl = xa_load(&card->ctl_hash, get_ctl_id_hash(id)); 89562306a36Sopenharmony_ci if (kctl && elem_id_matches(kctl, id)) 89662306a36Sopenharmony_ci return kctl; 89762306a36Sopenharmony_ci if (!card->ctl_hash_collision) 89862306a36Sopenharmony_ci return NULL; /* we can rely on only hash table */ 89962306a36Sopenharmony_ci#endif 90062306a36Sopenharmony_ci /* no matching in hash table - try all as the last resort */ 90162306a36Sopenharmony_ci list_for_each_entry(kctl, &card->controls, list) 90262306a36Sopenharmony_ci if (elem_id_matches(kctl, id)) 90362306a36Sopenharmony_ci return kctl; 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci return NULL; 90662306a36Sopenharmony_ci} 90762306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_find_id_locked); 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_ci/** 91062306a36Sopenharmony_ci * snd_ctl_find_id - find the control instance with the given id 91162306a36Sopenharmony_ci * @card: the card instance 91262306a36Sopenharmony_ci * @id: the id to search 91362306a36Sopenharmony_ci * 91462306a36Sopenharmony_ci * Finds the control instance with the given id from the card. 91562306a36Sopenharmony_ci * 91662306a36Sopenharmony_ci * Return: The pointer of the instance if found, or %NULL if not. 91762306a36Sopenharmony_ci * 91862306a36Sopenharmony_ci * Note that this function takes card->controls_rwsem lock internally. 91962306a36Sopenharmony_ci */ 92062306a36Sopenharmony_cistruct snd_kcontrol *snd_ctl_find_id(struct snd_card *card, 92162306a36Sopenharmony_ci const struct snd_ctl_elem_id *id) 92262306a36Sopenharmony_ci{ 92362306a36Sopenharmony_ci struct snd_kcontrol *kctl; 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci down_read(&card->controls_rwsem); 92662306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, id); 92762306a36Sopenharmony_ci up_read(&card->controls_rwsem); 92862306a36Sopenharmony_ci return kctl; 92962306a36Sopenharmony_ci} 93062306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_find_id); 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_cistatic int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, 93362306a36Sopenharmony_ci unsigned int cmd, void __user *arg) 93462306a36Sopenharmony_ci{ 93562306a36Sopenharmony_ci struct snd_ctl_card_info *info; 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci info = kzalloc(sizeof(*info), GFP_KERNEL); 93862306a36Sopenharmony_ci if (! info) 93962306a36Sopenharmony_ci return -ENOMEM; 94062306a36Sopenharmony_ci down_read(&snd_ioctl_rwsem); 94162306a36Sopenharmony_ci info->card = card->number; 94262306a36Sopenharmony_ci strscpy(info->id, card->id, sizeof(info->id)); 94362306a36Sopenharmony_ci strscpy(info->driver, card->driver, sizeof(info->driver)); 94462306a36Sopenharmony_ci strscpy(info->name, card->shortname, sizeof(info->name)); 94562306a36Sopenharmony_ci strscpy(info->longname, card->longname, sizeof(info->longname)); 94662306a36Sopenharmony_ci strscpy(info->mixername, card->mixername, sizeof(info->mixername)); 94762306a36Sopenharmony_ci strscpy(info->components, card->components, sizeof(info->components)); 94862306a36Sopenharmony_ci up_read(&snd_ioctl_rwsem); 94962306a36Sopenharmony_ci if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) { 95062306a36Sopenharmony_ci kfree(info); 95162306a36Sopenharmony_ci return -EFAULT; 95262306a36Sopenharmony_ci } 95362306a36Sopenharmony_ci kfree(info); 95462306a36Sopenharmony_ci return 0; 95562306a36Sopenharmony_ci} 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_cistatic int snd_ctl_elem_list(struct snd_card *card, 95862306a36Sopenharmony_ci struct snd_ctl_elem_list *list) 95962306a36Sopenharmony_ci{ 96062306a36Sopenharmony_ci struct snd_kcontrol *kctl; 96162306a36Sopenharmony_ci struct snd_ctl_elem_id id; 96262306a36Sopenharmony_ci unsigned int offset, space, jidx; 96362306a36Sopenharmony_ci int err = 0; 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci offset = list->offset; 96662306a36Sopenharmony_ci space = list->space; 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_ci down_read(&card->controls_rwsem); 96962306a36Sopenharmony_ci list->count = card->controls_count; 97062306a36Sopenharmony_ci list->used = 0; 97162306a36Sopenharmony_ci if (space > 0) { 97262306a36Sopenharmony_ci list_for_each_entry(kctl, &card->controls, list) { 97362306a36Sopenharmony_ci if (offset >= kctl->count) { 97462306a36Sopenharmony_ci offset -= kctl->count; 97562306a36Sopenharmony_ci continue; 97662306a36Sopenharmony_ci } 97762306a36Sopenharmony_ci for (jidx = offset; jidx < kctl->count; jidx++) { 97862306a36Sopenharmony_ci snd_ctl_build_ioff(&id, kctl, jidx); 97962306a36Sopenharmony_ci if (copy_to_user(list->pids + list->used, &id, 98062306a36Sopenharmony_ci sizeof(id))) { 98162306a36Sopenharmony_ci err = -EFAULT; 98262306a36Sopenharmony_ci goto out; 98362306a36Sopenharmony_ci } 98462306a36Sopenharmony_ci list->used++; 98562306a36Sopenharmony_ci if (!--space) 98662306a36Sopenharmony_ci goto out; 98762306a36Sopenharmony_ci } 98862306a36Sopenharmony_ci offset = 0; 98962306a36Sopenharmony_ci } 99062306a36Sopenharmony_ci } 99162306a36Sopenharmony_ci out: 99262306a36Sopenharmony_ci up_read(&card->controls_rwsem); 99362306a36Sopenharmony_ci return err; 99462306a36Sopenharmony_ci} 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_cistatic int snd_ctl_elem_list_user(struct snd_card *card, 99762306a36Sopenharmony_ci struct snd_ctl_elem_list __user *_list) 99862306a36Sopenharmony_ci{ 99962306a36Sopenharmony_ci struct snd_ctl_elem_list list; 100062306a36Sopenharmony_ci int err; 100162306a36Sopenharmony_ci 100262306a36Sopenharmony_ci if (copy_from_user(&list, _list, sizeof(list))) 100362306a36Sopenharmony_ci return -EFAULT; 100462306a36Sopenharmony_ci err = snd_ctl_elem_list(card, &list); 100562306a36Sopenharmony_ci if (err) 100662306a36Sopenharmony_ci return err; 100762306a36Sopenharmony_ci if (copy_to_user(_list, &list, sizeof(list))) 100862306a36Sopenharmony_ci return -EFAULT; 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_ci return 0; 101162306a36Sopenharmony_ci} 101262306a36Sopenharmony_ci 101362306a36Sopenharmony_ci/* Check whether the given kctl info is valid */ 101462306a36Sopenharmony_cistatic int snd_ctl_check_elem_info(struct snd_card *card, 101562306a36Sopenharmony_ci const struct snd_ctl_elem_info *info) 101662306a36Sopenharmony_ci{ 101762306a36Sopenharmony_ci static const unsigned int max_value_counts[] = { 101862306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128, 101962306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128, 102062306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128, 102162306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_BYTES] = 512, 102262306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_IEC958] = 1, 102362306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64, 102462306a36Sopenharmony_ci }; 102562306a36Sopenharmony_ci 102662306a36Sopenharmony_ci if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || 102762306a36Sopenharmony_ci info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) { 102862306a36Sopenharmony_ci if (card) 102962306a36Sopenharmony_ci dev_err(card->dev, 103062306a36Sopenharmony_ci "control %i:%i:%i:%s:%i: invalid type %d\n", 103162306a36Sopenharmony_ci info->id.iface, info->id.device, 103262306a36Sopenharmony_ci info->id.subdevice, info->id.name, 103362306a36Sopenharmony_ci info->id.index, info->type); 103462306a36Sopenharmony_ci return -EINVAL; 103562306a36Sopenharmony_ci } 103662306a36Sopenharmony_ci if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED && 103762306a36Sopenharmony_ci info->value.enumerated.items == 0) { 103862306a36Sopenharmony_ci if (card) 103962306a36Sopenharmony_ci dev_err(card->dev, 104062306a36Sopenharmony_ci "control %i:%i:%i:%s:%i: zero enum items\n", 104162306a36Sopenharmony_ci info->id.iface, info->id.device, 104262306a36Sopenharmony_ci info->id.subdevice, info->id.name, 104362306a36Sopenharmony_ci info->id.index); 104462306a36Sopenharmony_ci return -EINVAL; 104562306a36Sopenharmony_ci } 104662306a36Sopenharmony_ci if (info->count > max_value_counts[info->type]) { 104762306a36Sopenharmony_ci if (card) 104862306a36Sopenharmony_ci dev_err(card->dev, 104962306a36Sopenharmony_ci "control %i:%i:%i:%s:%i: invalid count %d\n", 105062306a36Sopenharmony_ci info->id.iface, info->id.device, 105162306a36Sopenharmony_ci info->id.subdevice, info->id.name, 105262306a36Sopenharmony_ci info->id.index, info->count); 105362306a36Sopenharmony_ci return -EINVAL; 105462306a36Sopenharmony_ci } 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_ci return 0; 105762306a36Sopenharmony_ci} 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci/* The capacity of struct snd_ctl_elem_value.value.*/ 106062306a36Sopenharmony_cistatic const unsigned int value_sizes[] = { 106162306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long), 106262306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long), 106362306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int), 106462306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char), 106562306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958), 106662306a36Sopenharmony_ci [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), 106762306a36Sopenharmony_ci}; 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_ci/* fill the remaining snd_ctl_elem_value data with the given pattern */ 107062306a36Sopenharmony_cistatic void fill_remaining_elem_value(struct snd_ctl_elem_value *control, 107162306a36Sopenharmony_ci struct snd_ctl_elem_info *info, 107262306a36Sopenharmony_ci u32 pattern) 107362306a36Sopenharmony_ci{ 107462306a36Sopenharmony_ci size_t offset = value_sizes[info->type] * info->count; 107562306a36Sopenharmony_ci 107662306a36Sopenharmony_ci offset = DIV_ROUND_UP(offset, sizeof(u32)); 107762306a36Sopenharmony_ci memset32((u32 *)control->value.bytes.data + offset, pattern, 107862306a36Sopenharmony_ci sizeof(control->value) / sizeof(u32) - offset); 107962306a36Sopenharmony_ci} 108062306a36Sopenharmony_ci 108162306a36Sopenharmony_ci/* check whether the given integer ctl value is valid */ 108262306a36Sopenharmony_cistatic int sanity_check_int_value(struct snd_card *card, 108362306a36Sopenharmony_ci const struct snd_ctl_elem_value *control, 108462306a36Sopenharmony_ci const struct snd_ctl_elem_info *info, 108562306a36Sopenharmony_ci int i, bool print_error) 108662306a36Sopenharmony_ci{ 108762306a36Sopenharmony_ci long long lval, lmin, lmax, lstep; 108862306a36Sopenharmony_ci u64 rem; 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ci switch (info->type) { 109162306a36Sopenharmony_ci default: 109262306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_BOOLEAN: 109362306a36Sopenharmony_ci lval = control->value.integer.value[i]; 109462306a36Sopenharmony_ci lmin = 0; 109562306a36Sopenharmony_ci lmax = 1; 109662306a36Sopenharmony_ci lstep = 0; 109762306a36Sopenharmony_ci break; 109862306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_INTEGER: 109962306a36Sopenharmony_ci lval = control->value.integer.value[i]; 110062306a36Sopenharmony_ci lmin = info->value.integer.min; 110162306a36Sopenharmony_ci lmax = info->value.integer.max; 110262306a36Sopenharmony_ci lstep = info->value.integer.step; 110362306a36Sopenharmony_ci break; 110462306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_INTEGER64: 110562306a36Sopenharmony_ci lval = control->value.integer64.value[i]; 110662306a36Sopenharmony_ci lmin = info->value.integer64.min; 110762306a36Sopenharmony_ci lmax = info->value.integer64.max; 110862306a36Sopenharmony_ci lstep = info->value.integer64.step; 110962306a36Sopenharmony_ci break; 111062306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_ENUMERATED: 111162306a36Sopenharmony_ci lval = control->value.enumerated.item[i]; 111262306a36Sopenharmony_ci lmin = 0; 111362306a36Sopenharmony_ci lmax = info->value.enumerated.items - 1; 111462306a36Sopenharmony_ci lstep = 0; 111562306a36Sopenharmony_ci break; 111662306a36Sopenharmony_ci } 111762306a36Sopenharmony_ci 111862306a36Sopenharmony_ci if (lval < lmin || lval > lmax) { 111962306a36Sopenharmony_ci if (print_error) 112062306a36Sopenharmony_ci dev_err(card->dev, 112162306a36Sopenharmony_ci "control %i:%i:%i:%s:%i: value out of range %lld (%lld/%lld) at count %i\n", 112262306a36Sopenharmony_ci control->id.iface, control->id.device, 112362306a36Sopenharmony_ci control->id.subdevice, control->id.name, 112462306a36Sopenharmony_ci control->id.index, lval, lmin, lmax, i); 112562306a36Sopenharmony_ci return -EINVAL; 112662306a36Sopenharmony_ci } 112762306a36Sopenharmony_ci if (lstep) { 112862306a36Sopenharmony_ci div64_u64_rem(lval, lstep, &rem); 112962306a36Sopenharmony_ci if (rem) { 113062306a36Sopenharmony_ci if (print_error) 113162306a36Sopenharmony_ci dev_err(card->dev, 113262306a36Sopenharmony_ci "control %i:%i:%i:%s:%i: unaligned value %lld (step %lld) at count %i\n", 113362306a36Sopenharmony_ci control->id.iface, control->id.device, 113462306a36Sopenharmony_ci control->id.subdevice, control->id.name, 113562306a36Sopenharmony_ci control->id.index, lval, lstep, i); 113662306a36Sopenharmony_ci return -EINVAL; 113762306a36Sopenharmony_ci } 113862306a36Sopenharmony_ci } 113962306a36Sopenharmony_ci 114062306a36Sopenharmony_ci return 0; 114162306a36Sopenharmony_ci} 114262306a36Sopenharmony_ci 114362306a36Sopenharmony_ci/* check whether the all input values are valid for the given elem value */ 114462306a36Sopenharmony_cistatic int sanity_check_input_values(struct snd_card *card, 114562306a36Sopenharmony_ci const struct snd_ctl_elem_value *control, 114662306a36Sopenharmony_ci const struct snd_ctl_elem_info *info, 114762306a36Sopenharmony_ci bool print_error) 114862306a36Sopenharmony_ci{ 114962306a36Sopenharmony_ci int i, ret; 115062306a36Sopenharmony_ci 115162306a36Sopenharmony_ci switch (info->type) { 115262306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_BOOLEAN: 115362306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_INTEGER: 115462306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_INTEGER64: 115562306a36Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_ENUMERATED: 115662306a36Sopenharmony_ci for (i = 0; i < info->count; i++) { 115762306a36Sopenharmony_ci ret = sanity_check_int_value(card, control, info, i, 115862306a36Sopenharmony_ci print_error); 115962306a36Sopenharmony_ci if (ret < 0) 116062306a36Sopenharmony_ci return ret; 116162306a36Sopenharmony_ci } 116262306a36Sopenharmony_ci break; 116362306a36Sopenharmony_ci default: 116462306a36Sopenharmony_ci break; 116562306a36Sopenharmony_ci } 116662306a36Sopenharmony_ci 116762306a36Sopenharmony_ci return 0; 116862306a36Sopenharmony_ci} 116962306a36Sopenharmony_ci 117062306a36Sopenharmony_ci/* perform sanity checks to the given snd_ctl_elem_value object */ 117162306a36Sopenharmony_cistatic int sanity_check_elem_value(struct snd_card *card, 117262306a36Sopenharmony_ci const struct snd_ctl_elem_value *control, 117362306a36Sopenharmony_ci const struct snd_ctl_elem_info *info, 117462306a36Sopenharmony_ci u32 pattern) 117562306a36Sopenharmony_ci{ 117662306a36Sopenharmony_ci size_t offset; 117762306a36Sopenharmony_ci int ret; 117862306a36Sopenharmony_ci u32 *p; 117962306a36Sopenharmony_ci 118062306a36Sopenharmony_ci ret = sanity_check_input_values(card, control, info, true); 118162306a36Sopenharmony_ci if (ret < 0) 118262306a36Sopenharmony_ci return ret; 118362306a36Sopenharmony_ci 118462306a36Sopenharmony_ci /* check whether the remaining area kept untouched */ 118562306a36Sopenharmony_ci offset = value_sizes[info->type] * info->count; 118662306a36Sopenharmony_ci offset = DIV_ROUND_UP(offset, sizeof(u32)); 118762306a36Sopenharmony_ci p = (u32 *)control->value.bytes.data + offset; 118862306a36Sopenharmony_ci for (; offset < sizeof(control->value) / sizeof(u32); offset++, p++) { 118962306a36Sopenharmony_ci if (*p != pattern) { 119062306a36Sopenharmony_ci ret = -EINVAL; 119162306a36Sopenharmony_ci break; 119262306a36Sopenharmony_ci } 119362306a36Sopenharmony_ci *p = 0; /* clear the checked area */ 119462306a36Sopenharmony_ci } 119562306a36Sopenharmony_ci 119662306a36Sopenharmony_ci return ret; 119762306a36Sopenharmony_ci} 119862306a36Sopenharmony_ci 119962306a36Sopenharmony_cistatic int __snd_ctl_elem_info(struct snd_card *card, 120062306a36Sopenharmony_ci struct snd_kcontrol *kctl, 120162306a36Sopenharmony_ci struct snd_ctl_elem_info *info, 120262306a36Sopenharmony_ci struct snd_ctl_file *ctl) 120362306a36Sopenharmony_ci{ 120462306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 120562306a36Sopenharmony_ci unsigned int index_offset; 120662306a36Sopenharmony_ci int result; 120762306a36Sopenharmony_ci 120862306a36Sopenharmony_ci#ifdef CONFIG_SND_DEBUG 120962306a36Sopenharmony_ci info->access = 0; 121062306a36Sopenharmony_ci#endif 121162306a36Sopenharmony_ci result = snd_power_ref_and_wait(card); 121262306a36Sopenharmony_ci if (!result) 121362306a36Sopenharmony_ci result = kctl->info(kctl, info); 121462306a36Sopenharmony_ci snd_power_unref(card); 121562306a36Sopenharmony_ci if (result >= 0) { 121662306a36Sopenharmony_ci snd_BUG_ON(info->access); 121762306a36Sopenharmony_ci index_offset = snd_ctl_get_ioff(kctl, &info->id); 121862306a36Sopenharmony_ci vd = &kctl->vd[index_offset]; 121962306a36Sopenharmony_ci snd_ctl_build_ioff(&info->id, kctl, index_offset); 122062306a36Sopenharmony_ci info->access = vd->access; 122162306a36Sopenharmony_ci if (vd->owner) { 122262306a36Sopenharmony_ci info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK; 122362306a36Sopenharmony_ci if (vd->owner == ctl) 122462306a36Sopenharmony_ci info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER; 122562306a36Sopenharmony_ci info->owner = pid_vnr(vd->owner->pid); 122662306a36Sopenharmony_ci } else { 122762306a36Sopenharmony_ci info->owner = -1; 122862306a36Sopenharmony_ci } 122962306a36Sopenharmony_ci if (!snd_ctl_skip_validation(info) && 123062306a36Sopenharmony_ci snd_ctl_check_elem_info(card, info) < 0) 123162306a36Sopenharmony_ci result = -EINVAL; 123262306a36Sopenharmony_ci } 123362306a36Sopenharmony_ci return result; 123462306a36Sopenharmony_ci} 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_cistatic int snd_ctl_elem_info(struct snd_ctl_file *ctl, 123762306a36Sopenharmony_ci struct snd_ctl_elem_info *info) 123862306a36Sopenharmony_ci{ 123962306a36Sopenharmony_ci struct snd_card *card = ctl->card; 124062306a36Sopenharmony_ci struct snd_kcontrol *kctl; 124162306a36Sopenharmony_ci int result; 124262306a36Sopenharmony_ci 124362306a36Sopenharmony_ci down_read(&card->controls_rwsem); 124462306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, &info->id); 124562306a36Sopenharmony_ci if (kctl == NULL) 124662306a36Sopenharmony_ci result = -ENOENT; 124762306a36Sopenharmony_ci else 124862306a36Sopenharmony_ci result = __snd_ctl_elem_info(card, kctl, info, ctl); 124962306a36Sopenharmony_ci up_read(&card->controls_rwsem); 125062306a36Sopenharmony_ci return result; 125162306a36Sopenharmony_ci} 125262306a36Sopenharmony_ci 125362306a36Sopenharmony_cistatic int snd_ctl_elem_info_user(struct snd_ctl_file *ctl, 125462306a36Sopenharmony_ci struct snd_ctl_elem_info __user *_info) 125562306a36Sopenharmony_ci{ 125662306a36Sopenharmony_ci struct snd_ctl_elem_info info; 125762306a36Sopenharmony_ci int result; 125862306a36Sopenharmony_ci 125962306a36Sopenharmony_ci if (copy_from_user(&info, _info, sizeof(info))) 126062306a36Sopenharmony_ci return -EFAULT; 126162306a36Sopenharmony_ci result = snd_ctl_elem_info(ctl, &info); 126262306a36Sopenharmony_ci if (result < 0) 126362306a36Sopenharmony_ci return result; 126462306a36Sopenharmony_ci /* drop internal access flags */ 126562306a36Sopenharmony_ci info.access &= ~(SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK| 126662306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_LED_MASK); 126762306a36Sopenharmony_ci if (copy_to_user(_info, &info, sizeof(info))) 126862306a36Sopenharmony_ci return -EFAULT; 126962306a36Sopenharmony_ci return result; 127062306a36Sopenharmony_ci} 127162306a36Sopenharmony_ci 127262306a36Sopenharmony_cistatic int snd_ctl_elem_read(struct snd_card *card, 127362306a36Sopenharmony_ci struct snd_ctl_elem_value *control) 127462306a36Sopenharmony_ci{ 127562306a36Sopenharmony_ci struct snd_kcontrol *kctl; 127662306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 127762306a36Sopenharmony_ci unsigned int index_offset; 127862306a36Sopenharmony_ci struct snd_ctl_elem_info info; 127962306a36Sopenharmony_ci const u32 pattern = 0xdeadbeef; 128062306a36Sopenharmony_ci int ret; 128162306a36Sopenharmony_ci 128262306a36Sopenharmony_ci down_read(&card->controls_rwsem); 128362306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, &control->id); 128462306a36Sopenharmony_ci if (kctl == NULL) { 128562306a36Sopenharmony_ci ret = -ENOENT; 128662306a36Sopenharmony_ci goto unlock; 128762306a36Sopenharmony_ci } 128862306a36Sopenharmony_ci 128962306a36Sopenharmony_ci index_offset = snd_ctl_get_ioff(kctl, &control->id); 129062306a36Sopenharmony_ci vd = &kctl->vd[index_offset]; 129162306a36Sopenharmony_ci if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_READ) || kctl->get == NULL) { 129262306a36Sopenharmony_ci ret = -EPERM; 129362306a36Sopenharmony_ci goto unlock; 129462306a36Sopenharmony_ci } 129562306a36Sopenharmony_ci 129662306a36Sopenharmony_ci snd_ctl_build_ioff(&control->id, kctl, index_offset); 129762306a36Sopenharmony_ci 129862306a36Sopenharmony_ci#ifdef CONFIG_SND_CTL_DEBUG 129962306a36Sopenharmony_ci /* info is needed only for validation */ 130062306a36Sopenharmony_ci memset(&info, 0, sizeof(info)); 130162306a36Sopenharmony_ci info.id = control->id; 130262306a36Sopenharmony_ci ret = __snd_ctl_elem_info(card, kctl, &info, NULL); 130362306a36Sopenharmony_ci if (ret < 0) 130462306a36Sopenharmony_ci goto unlock; 130562306a36Sopenharmony_ci#endif 130662306a36Sopenharmony_ci 130762306a36Sopenharmony_ci if (!snd_ctl_skip_validation(&info)) 130862306a36Sopenharmony_ci fill_remaining_elem_value(control, &info, pattern); 130962306a36Sopenharmony_ci ret = snd_power_ref_and_wait(card); 131062306a36Sopenharmony_ci if (!ret) 131162306a36Sopenharmony_ci ret = kctl->get(kctl, control); 131262306a36Sopenharmony_ci snd_power_unref(card); 131362306a36Sopenharmony_ci if (ret < 0) 131462306a36Sopenharmony_ci goto unlock; 131562306a36Sopenharmony_ci if (!snd_ctl_skip_validation(&info) && 131662306a36Sopenharmony_ci sanity_check_elem_value(card, control, &info, pattern) < 0) { 131762306a36Sopenharmony_ci dev_err(card->dev, 131862306a36Sopenharmony_ci "control %i:%i:%i:%s:%i: access overflow\n", 131962306a36Sopenharmony_ci control->id.iface, control->id.device, 132062306a36Sopenharmony_ci control->id.subdevice, control->id.name, 132162306a36Sopenharmony_ci control->id.index); 132262306a36Sopenharmony_ci ret = -EINVAL; 132362306a36Sopenharmony_ci goto unlock; 132462306a36Sopenharmony_ci } 132562306a36Sopenharmony_ciunlock: 132662306a36Sopenharmony_ci up_read(&card->controls_rwsem); 132762306a36Sopenharmony_ci return ret; 132862306a36Sopenharmony_ci} 132962306a36Sopenharmony_ci 133062306a36Sopenharmony_cistatic int snd_ctl_elem_read_user(struct snd_card *card, 133162306a36Sopenharmony_ci struct snd_ctl_elem_value __user *_control) 133262306a36Sopenharmony_ci{ 133362306a36Sopenharmony_ci struct snd_ctl_elem_value *control; 133462306a36Sopenharmony_ci int result; 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_ci control = memdup_user(_control, sizeof(*control)); 133762306a36Sopenharmony_ci if (IS_ERR(control)) 133862306a36Sopenharmony_ci return PTR_ERR(control); 133962306a36Sopenharmony_ci 134062306a36Sopenharmony_ci result = snd_ctl_elem_read(card, control); 134162306a36Sopenharmony_ci if (result < 0) 134262306a36Sopenharmony_ci goto error; 134362306a36Sopenharmony_ci 134462306a36Sopenharmony_ci if (copy_to_user(_control, control, sizeof(*control))) 134562306a36Sopenharmony_ci result = -EFAULT; 134662306a36Sopenharmony_ci error: 134762306a36Sopenharmony_ci kfree(control); 134862306a36Sopenharmony_ci return result; 134962306a36Sopenharmony_ci} 135062306a36Sopenharmony_ci 135162306a36Sopenharmony_cistatic int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, 135262306a36Sopenharmony_ci struct snd_ctl_elem_value *control) 135362306a36Sopenharmony_ci{ 135462306a36Sopenharmony_ci struct snd_kcontrol *kctl; 135562306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 135662306a36Sopenharmony_ci unsigned int index_offset; 135762306a36Sopenharmony_ci int result; 135862306a36Sopenharmony_ci 135962306a36Sopenharmony_ci down_write(&card->controls_rwsem); 136062306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, &control->id); 136162306a36Sopenharmony_ci if (kctl == NULL) { 136262306a36Sopenharmony_ci up_write(&card->controls_rwsem); 136362306a36Sopenharmony_ci return -ENOENT; 136462306a36Sopenharmony_ci } 136562306a36Sopenharmony_ci 136662306a36Sopenharmony_ci index_offset = snd_ctl_get_ioff(kctl, &control->id); 136762306a36Sopenharmony_ci vd = &kctl->vd[index_offset]; 136862306a36Sopenharmony_ci if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || kctl->put == NULL || 136962306a36Sopenharmony_ci (file && vd->owner && vd->owner != file)) { 137062306a36Sopenharmony_ci up_write(&card->controls_rwsem); 137162306a36Sopenharmony_ci return -EPERM; 137262306a36Sopenharmony_ci } 137362306a36Sopenharmony_ci 137462306a36Sopenharmony_ci snd_ctl_build_ioff(&control->id, kctl, index_offset); 137562306a36Sopenharmony_ci result = snd_power_ref_and_wait(card); 137662306a36Sopenharmony_ci /* validate input values */ 137762306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_SND_CTL_INPUT_VALIDATION) && !result) { 137862306a36Sopenharmony_ci struct snd_ctl_elem_info info; 137962306a36Sopenharmony_ci 138062306a36Sopenharmony_ci memset(&info, 0, sizeof(info)); 138162306a36Sopenharmony_ci info.id = control->id; 138262306a36Sopenharmony_ci result = __snd_ctl_elem_info(card, kctl, &info, NULL); 138362306a36Sopenharmony_ci if (!result) 138462306a36Sopenharmony_ci result = sanity_check_input_values(card, control, &info, 138562306a36Sopenharmony_ci false); 138662306a36Sopenharmony_ci } 138762306a36Sopenharmony_ci if (!result) 138862306a36Sopenharmony_ci result = kctl->put(kctl, control); 138962306a36Sopenharmony_ci snd_power_unref(card); 139062306a36Sopenharmony_ci if (result < 0) { 139162306a36Sopenharmony_ci up_write(&card->controls_rwsem); 139262306a36Sopenharmony_ci return result; 139362306a36Sopenharmony_ci } 139462306a36Sopenharmony_ci 139562306a36Sopenharmony_ci if (result > 0) { 139662306a36Sopenharmony_ci downgrade_write(&card->controls_rwsem); 139762306a36Sopenharmony_ci snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, index_offset); 139862306a36Sopenharmony_ci up_read(&card->controls_rwsem); 139962306a36Sopenharmony_ci } else { 140062306a36Sopenharmony_ci up_write(&card->controls_rwsem); 140162306a36Sopenharmony_ci } 140262306a36Sopenharmony_ci 140362306a36Sopenharmony_ci return 0; 140462306a36Sopenharmony_ci} 140562306a36Sopenharmony_ci 140662306a36Sopenharmony_cistatic int snd_ctl_elem_write_user(struct snd_ctl_file *file, 140762306a36Sopenharmony_ci struct snd_ctl_elem_value __user *_control) 140862306a36Sopenharmony_ci{ 140962306a36Sopenharmony_ci struct snd_ctl_elem_value *control; 141062306a36Sopenharmony_ci struct snd_card *card; 141162306a36Sopenharmony_ci int result; 141262306a36Sopenharmony_ci 141362306a36Sopenharmony_ci control = memdup_user(_control, sizeof(*control)); 141462306a36Sopenharmony_ci if (IS_ERR(control)) 141562306a36Sopenharmony_ci return PTR_ERR(control); 141662306a36Sopenharmony_ci 141762306a36Sopenharmony_ci card = file->card; 141862306a36Sopenharmony_ci result = snd_ctl_elem_write(card, file, control); 141962306a36Sopenharmony_ci if (result < 0) 142062306a36Sopenharmony_ci goto error; 142162306a36Sopenharmony_ci 142262306a36Sopenharmony_ci if (copy_to_user(_control, control, sizeof(*control))) 142362306a36Sopenharmony_ci result = -EFAULT; 142462306a36Sopenharmony_ci error: 142562306a36Sopenharmony_ci kfree(control); 142662306a36Sopenharmony_ci return result; 142762306a36Sopenharmony_ci} 142862306a36Sopenharmony_ci 142962306a36Sopenharmony_cistatic int snd_ctl_elem_lock(struct snd_ctl_file *file, 143062306a36Sopenharmony_ci struct snd_ctl_elem_id __user *_id) 143162306a36Sopenharmony_ci{ 143262306a36Sopenharmony_ci struct snd_card *card = file->card; 143362306a36Sopenharmony_ci struct snd_ctl_elem_id id; 143462306a36Sopenharmony_ci struct snd_kcontrol *kctl; 143562306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 143662306a36Sopenharmony_ci int result; 143762306a36Sopenharmony_ci 143862306a36Sopenharmony_ci if (copy_from_user(&id, _id, sizeof(id))) 143962306a36Sopenharmony_ci return -EFAULT; 144062306a36Sopenharmony_ci down_write(&card->controls_rwsem); 144162306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, &id); 144262306a36Sopenharmony_ci if (kctl == NULL) { 144362306a36Sopenharmony_ci result = -ENOENT; 144462306a36Sopenharmony_ci } else { 144562306a36Sopenharmony_ci vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; 144662306a36Sopenharmony_ci if (vd->owner != NULL) 144762306a36Sopenharmony_ci result = -EBUSY; 144862306a36Sopenharmony_ci else { 144962306a36Sopenharmony_ci vd->owner = file; 145062306a36Sopenharmony_ci result = 0; 145162306a36Sopenharmony_ci } 145262306a36Sopenharmony_ci } 145362306a36Sopenharmony_ci up_write(&card->controls_rwsem); 145462306a36Sopenharmony_ci return result; 145562306a36Sopenharmony_ci} 145662306a36Sopenharmony_ci 145762306a36Sopenharmony_cistatic int snd_ctl_elem_unlock(struct snd_ctl_file *file, 145862306a36Sopenharmony_ci struct snd_ctl_elem_id __user *_id) 145962306a36Sopenharmony_ci{ 146062306a36Sopenharmony_ci struct snd_card *card = file->card; 146162306a36Sopenharmony_ci struct snd_ctl_elem_id id; 146262306a36Sopenharmony_ci struct snd_kcontrol *kctl; 146362306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 146462306a36Sopenharmony_ci int result; 146562306a36Sopenharmony_ci 146662306a36Sopenharmony_ci if (copy_from_user(&id, _id, sizeof(id))) 146762306a36Sopenharmony_ci return -EFAULT; 146862306a36Sopenharmony_ci down_write(&card->controls_rwsem); 146962306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, &id); 147062306a36Sopenharmony_ci if (kctl == NULL) { 147162306a36Sopenharmony_ci result = -ENOENT; 147262306a36Sopenharmony_ci } else { 147362306a36Sopenharmony_ci vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; 147462306a36Sopenharmony_ci if (vd->owner == NULL) 147562306a36Sopenharmony_ci result = -EINVAL; 147662306a36Sopenharmony_ci else if (vd->owner != file) 147762306a36Sopenharmony_ci result = -EPERM; 147862306a36Sopenharmony_ci else { 147962306a36Sopenharmony_ci vd->owner = NULL; 148062306a36Sopenharmony_ci result = 0; 148162306a36Sopenharmony_ci } 148262306a36Sopenharmony_ci } 148362306a36Sopenharmony_ci up_write(&card->controls_rwsem); 148462306a36Sopenharmony_ci return result; 148562306a36Sopenharmony_ci} 148662306a36Sopenharmony_ci 148762306a36Sopenharmony_cistruct user_element { 148862306a36Sopenharmony_ci struct snd_ctl_elem_info info; 148962306a36Sopenharmony_ci struct snd_card *card; 149062306a36Sopenharmony_ci char *elem_data; /* element data */ 149162306a36Sopenharmony_ci unsigned long elem_data_size; /* size of element data in bytes */ 149262306a36Sopenharmony_ci void *tlv_data; /* TLV data */ 149362306a36Sopenharmony_ci unsigned long tlv_data_size; /* TLV data size */ 149462306a36Sopenharmony_ci void *priv_data; /* private data (like strings for enumerated type) */ 149562306a36Sopenharmony_ci}; 149662306a36Sopenharmony_ci 149762306a36Sopenharmony_ci// check whether the addition (in bytes) of user ctl element may overflow the limit. 149862306a36Sopenharmony_cistatic bool check_user_elem_overflow(struct snd_card *card, ssize_t add) 149962306a36Sopenharmony_ci{ 150062306a36Sopenharmony_ci return (ssize_t)card->user_ctl_alloc_size + add > max_user_ctl_alloc_size; 150162306a36Sopenharmony_ci} 150262306a36Sopenharmony_ci 150362306a36Sopenharmony_cistatic int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol, 150462306a36Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 150562306a36Sopenharmony_ci{ 150662306a36Sopenharmony_ci struct user_element *ue = kcontrol->private_data; 150762306a36Sopenharmony_ci unsigned int offset; 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_ci offset = snd_ctl_get_ioff(kcontrol, &uinfo->id); 151062306a36Sopenharmony_ci *uinfo = ue->info; 151162306a36Sopenharmony_ci snd_ctl_build_ioff(&uinfo->id, kcontrol, offset); 151262306a36Sopenharmony_ci 151362306a36Sopenharmony_ci return 0; 151462306a36Sopenharmony_ci} 151562306a36Sopenharmony_ci 151662306a36Sopenharmony_cistatic int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol, 151762306a36Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 151862306a36Sopenharmony_ci{ 151962306a36Sopenharmony_ci struct user_element *ue = kcontrol->private_data; 152062306a36Sopenharmony_ci const char *names; 152162306a36Sopenharmony_ci unsigned int item; 152262306a36Sopenharmony_ci unsigned int offset; 152362306a36Sopenharmony_ci 152462306a36Sopenharmony_ci item = uinfo->value.enumerated.item; 152562306a36Sopenharmony_ci 152662306a36Sopenharmony_ci offset = snd_ctl_get_ioff(kcontrol, &uinfo->id); 152762306a36Sopenharmony_ci *uinfo = ue->info; 152862306a36Sopenharmony_ci snd_ctl_build_ioff(&uinfo->id, kcontrol, offset); 152962306a36Sopenharmony_ci 153062306a36Sopenharmony_ci item = min(item, uinfo->value.enumerated.items - 1); 153162306a36Sopenharmony_ci uinfo->value.enumerated.item = item; 153262306a36Sopenharmony_ci 153362306a36Sopenharmony_ci names = ue->priv_data; 153462306a36Sopenharmony_ci for (; item > 0; --item) 153562306a36Sopenharmony_ci names += strlen(names) + 1; 153662306a36Sopenharmony_ci strcpy(uinfo->value.enumerated.name, names); 153762306a36Sopenharmony_ci 153862306a36Sopenharmony_ci return 0; 153962306a36Sopenharmony_ci} 154062306a36Sopenharmony_ci 154162306a36Sopenharmony_cistatic int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, 154262306a36Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 154362306a36Sopenharmony_ci{ 154462306a36Sopenharmony_ci struct user_element *ue = kcontrol->private_data; 154562306a36Sopenharmony_ci unsigned int size = ue->elem_data_size; 154662306a36Sopenharmony_ci char *src = ue->elem_data + 154762306a36Sopenharmony_ci snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; 154862306a36Sopenharmony_ci 154962306a36Sopenharmony_ci memcpy(&ucontrol->value, src, size); 155062306a36Sopenharmony_ci return 0; 155162306a36Sopenharmony_ci} 155262306a36Sopenharmony_ci 155362306a36Sopenharmony_cistatic int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, 155462306a36Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 155562306a36Sopenharmony_ci{ 155662306a36Sopenharmony_ci int change; 155762306a36Sopenharmony_ci struct user_element *ue = kcontrol->private_data; 155862306a36Sopenharmony_ci unsigned int size = ue->elem_data_size; 155962306a36Sopenharmony_ci char *dst = ue->elem_data + 156062306a36Sopenharmony_ci snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; 156162306a36Sopenharmony_ci 156262306a36Sopenharmony_ci change = memcmp(&ucontrol->value, dst, size) != 0; 156362306a36Sopenharmony_ci if (change) 156462306a36Sopenharmony_ci memcpy(dst, &ucontrol->value, size); 156562306a36Sopenharmony_ci return change; 156662306a36Sopenharmony_ci} 156762306a36Sopenharmony_ci 156862306a36Sopenharmony_ci/* called in controls_rwsem write lock */ 156962306a36Sopenharmony_cistatic int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, 157062306a36Sopenharmony_ci unsigned int size) 157162306a36Sopenharmony_ci{ 157262306a36Sopenharmony_ci struct user_element *ue = kctl->private_data; 157362306a36Sopenharmony_ci unsigned int *container; 157462306a36Sopenharmony_ci unsigned int mask = 0; 157562306a36Sopenharmony_ci int i; 157662306a36Sopenharmony_ci int change; 157762306a36Sopenharmony_ci 157862306a36Sopenharmony_ci lockdep_assert_held_write(&ue->card->controls_rwsem); 157962306a36Sopenharmony_ci 158062306a36Sopenharmony_ci if (size > 1024 * 128) /* sane value */ 158162306a36Sopenharmony_ci return -EINVAL; 158262306a36Sopenharmony_ci 158362306a36Sopenharmony_ci // does the TLV size change cause overflow? 158462306a36Sopenharmony_ci if (check_user_elem_overflow(ue->card, (ssize_t)(size - ue->tlv_data_size))) 158562306a36Sopenharmony_ci return -ENOMEM; 158662306a36Sopenharmony_ci 158762306a36Sopenharmony_ci container = vmemdup_user(buf, size); 158862306a36Sopenharmony_ci if (IS_ERR(container)) 158962306a36Sopenharmony_ci return PTR_ERR(container); 159062306a36Sopenharmony_ci 159162306a36Sopenharmony_ci change = ue->tlv_data_size != size; 159262306a36Sopenharmony_ci if (!change) 159362306a36Sopenharmony_ci change = memcmp(ue->tlv_data, container, size) != 0; 159462306a36Sopenharmony_ci if (!change) { 159562306a36Sopenharmony_ci kvfree(container); 159662306a36Sopenharmony_ci return 0; 159762306a36Sopenharmony_ci } 159862306a36Sopenharmony_ci 159962306a36Sopenharmony_ci if (ue->tlv_data == NULL) { 160062306a36Sopenharmony_ci /* Now TLV data is available. */ 160162306a36Sopenharmony_ci for (i = 0; i < kctl->count; ++i) 160262306a36Sopenharmony_ci kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; 160362306a36Sopenharmony_ci mask = SNDRV_CTL_EVENT_MASK_INFO; 160462306a36Sopenharmony_ci } else { 160562306a36Sopenharmony_ci ue->card->user_ctl_alloc_size -= ue->tlv_data_size; 160662306a36Sopenharmony_ci ue->tlv_data_size = 0; 160762306a36Sopenharmony_ci kvfree(ue->tlv_data); 160862306a36Sopenharmony_ci } 160962306a36Sopenharmony_ci 161062306a36Sopenharmony_ci ue->tlv_data = container; 161162306a36Sopenharmony_ci ue->tlv_data_size = size; 161262306a36Sopenharmony_ci // decremented at private_free. 161362306a36Sopenharmony_ci ue->card->user_ctl_alloc_size += size; 161462306a36Sopenharmony_ci 161562306a36Sopenharmony_ci mask |= SNDRV_CTL_EVENT_MASK_TLV; 161662306a36Sopenharmony_ci for (i = 0; i < kctl->count; ++i) 161762306a36Sopenharmony_ci snd_ctl_notify_one(ue->card, mask, kctl, i); 161862306a36Sopenharmony_ci 161962306a36Sopenharmony_ci return change; 162062306a36Sopenharmony_ci} 162162306a36Sopenharmony_ci 162262306a36Sopenharmony_cistatic int read_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, 162362306a36Sopenharmony_ci unsigned int size) 162462306a36Sopenharmony_ci{ 162562306a36Sopenharmony_ci struct user_element *ue = kctl->private_data; 162662306a36Sopenharmony_ci 162762306a36Sopenharmony_ci if (ue->tlv_data_size == 0 || ue->tlv_data == NULL) 162862306a36Sopenharmony_ci return -ENXIO; 162962306a36Sopenharmony_ci 163062306a36Sopenharmony_ci if (size < ue->tlv_data_size) 163162306a36Sopenharmony_ci return -ENOSPC; 163262306a36Sopenharmony_ci 163362306a36Sopenharmony_ci if (copy_to_user(buf, ue->tlv_data, ue->tlv_data_size)) 163462306a36Sopenharmony_ci return -EFAULT; 163562306a36Sopenharmony_ci 163662306a36Sopenharmony_ci return 0; 163762306a36Sopenharmony_ci} 163862306a36Sopenharmony_ci 163962306a36Sopenharmony_cistatic int snd_ctl_elem_user_tlv(struct snd_kcontrol *kctl, int op_flag, 164062306a36Sopenharmony_ci unsigned int size, unsigned int __user *buf) 164162306a36Sopenharmony_ci{ 164262306a36Sopenharmony_ci if (op_flag == SNDRV_CTL_TLV_OP_WRITE) 164362306a36Sopenharmony_ci return replace_user_tlv(kctl, buf, size); 164462306a36Sopenharmony_ci else 164562306a36Sopenharmony_ci return read_user_tlv(kctl, buf, size); 164662306a36Sopenharmony_ci} 164762306a36Sopenharmony_ci 164862306a36Sopenharmony_ci/* called in controls_rwsem write lock */ 164962306a36Sopenharmony_cistatic int snd_ctl_elem_init_enum_names(struct user_element *ue) 165062306a36Sopenharmony_ci{ 165162306a36Sopenharmony_ci char *names, *p; 165262306a36Sopenharmony_ci size_t buf_len, name_len; 165362306a36Sopenharmony_ci unsigned int i; 165462306a36Sopenharmony_ci const uintptr_t user_ptrval = ue->info.value.enumerated.names_ptr; 165562306a36Sopenharmony_ci 165662306a36Sopenharmony_ci lockdep_assert_held_write(&ue->card->controls_rwsem); 165762306a36Sopenharmony_ci 165862306a36Sopenharmony_ci buf_len = ue->info.value.enumerated.names_length; 165962306a36Sopenharmony_ci if (buf_len > 64 * 1024) 166062306a36Sopenharmony_ci return -EINVAL; 166162306a36Sopenharmony_ci 166262306a36Sopenharmony_ci if (check_user_elem_overflow(ue->card, buf_len)) 166362306a36Sopenharmony_ci return -ENOMEM; 166462306a36Sopenharmony_ci names = vmemdup_user((const void __user *)user_ptrval, buf_len); 166562306a36Sopenharmony_ci if (IS_ERR(names)) 166662306a36Sopenharmony_ci return PTR_ERR(names); 166762306a36Sopenharmony_ci 166862306a36Sopenharmony_ci /* check that there are enough valid names */ 166962306a36Sopenharmony_ci p = names; 167062306a36Sopenharmony_ci for (i = 0; i < ue->info.value.enumerated.items; ++i) { 167162306a36Sopenharmony_ci name_len = strnlen(p, buf_len); 167262306a36Sopenharmony_ci if (name_len == 0 || name_len >= 64 || name_len == buf_len) { 167362306a36Sopenharmony_ci kvfree(names); 167462306a36Sopenharmony_ci return -EINVAL; 167562306a36Sopenharmony_ci } 167662306a36Sopenharmony_ci p += name_len + 1; 167762306a36Sopenharmony_ci buf_len -= name_len + 1; 167862306a36Sopenharmony_ci } 167962306a36Sopenharmony_ci 168062306a36Sopenharmony_ci ue->priv_data = names; 168162306a36Sopenharmony_ci ue->info.value.enumerated.names_ptr = 0; 168262306a36Sopenharmony_ci // increment the allocation size; decremented again at private_free. 168362306a36Sopenharmony_ci ue->card->user_ctl_alloc_size += ue->info.value.enumerated.names_length; 168462306a36Sopenharmony_ci 168562306a36Sopenharmony_ci return 0; 168662306a36Sopenharmony_ci} 168762306a36Sopenharmony_ci 168862306a36Sopenharmony_cistatic size_t compute_user_elem_size(size_t size, unsigned int count) 168962306a36Sopenharmony_ci{ 169062306a36Sopenharmony_ci return sizeof(struct user_element) + size * count; 169162306a36Sopenharmony_ci} 169262306a36Sopenharmony_ci 169362306a36Sopenharmony_cistatic void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) 169462306a36Sopenharmony_ci{ 169562306a36Sopenharmony_ci struct user_element *ue = kcontrol->private_data; 169662306a36Sopenharmony_ci 169762306a36Sopenharmony_ci // decrement the allocation size. 169862306a36Sopenharmony_ci ue->card->user_ctl_alloc_size -= compute_user_elem_size(ue->elem_data_size, kcontrol->count); 169962306a36Sopenharmony_ci ue->card->user_ctl_alloc_size -= ue->tlv_data_size; 170062306a36Sopenharmony_ci if (ue->priv_data) 170162306a36Sopenharmony_ci ue->card->user_ctl_alloc_size -= ue->info.value.enumerated.names_length; 170262306a36Sopenharmony_ci 170362306a36Sopenharmony_ci kvfree(ue->tlv_data); 170462306a36Sopenharmony_ci kvfree(ue->priv_data); 170562306a36Sopenharmony_ci kfree(ue); 170662306a36Sopenharmony_ci} 170762306a36Sopenharmony_ci 170862306a36Sopenharmony_cistatic int snd_ctl_elem_add(struct snd_ctl_file *file, 170962306a36Sopenharmony_ci struct snd_ctl_elem_info *info, int replace) 171062306a36Sopenharmony_ci{ 171162306a36Sopenharmony_ci struct snd_card *card = file->card; 171262306a36Sopenharmony_ci struct snd_kcontrol *kctl; 171362306a36Sopenharmony_ci unsigned int count; 171462306a36Sopenharmony_ci unsigned int access; 171562306a36Sopenharmony_ci long private_size; 171662306a36Sopenharmony_ci size_t alloc_size; 171762306a36Sopenharmony_ci struct user_element *ue; 171862306a36Sopenharmony_ci unsigned int offset; 171962306a36Sopenharmony_ci int err; 172062306a36Sopenharmony_ci 172162306a36Sopenharmony_ci if (!*info->id.name) 172262306a36Sopenharmony_ci return -EINVAL; 172362306a36Sopenharmony_ci if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name)) 172462306a36Sopenharmony_ci return -EINVAL; 172562306a36Sopenharmony_ci 172662306a36Sopenharmony_ci /* Delete a control to replace them if needed. */ 172762306a36Sopenharmony_ci if (replace) { 172862306a36Sopenharmony_ci info->id.numid = 0; 172962306a36Sopenharmony_ci err = snd_ctl_remove_user_ctl(file, &info->id); 173062306a36Sopenharmony_ci if (err) 173162306a36Sopenharmony_ci return err; 173262306a36Sopenharmony_ci } 173362306a36Sopenharmony_ci 173462306a36Sopenharmony_ci /* Check the number of elements for this userspace control. */ 173562306a36Sopenharmony_ci count = info->owner; 173662306a36Sopenharmony_ci if (count == 0) 173762306a36Sopenharmony_ci count = 1; 173862306a36Sopenharmony_ci 173962306a36Sopenharmony_ci /* Arrange access permissions if needed. */ 174062306a36Sopenharmony_ci access = info->access; 174162306a36Sopenharmony_ci if (access == 0) 174262306a36Sopenharmony_ci access = SNDRV_CTL_ELEM_ACCESS_READWRITE; 174362306a36Sopenharmony_ci access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE | 174462306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_INACTIVE | 174562306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_TLV_WRITE); 174662306a36Sopenharmony_ci 174762306a36Sopenharmony_ci /* In initial state, nothing is available as TLV container. */ 174862306a36Sopenharmony_ci if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) 174962306a36Sopenharmony_ci access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; 175062306a36Sopenharmony_ci access |= SNDRV_CTL_ELEM_ACCESS_USER; 175162306a36Sopenharmony_ci 175262306a36Sopenharmony_ci /* 175362306a36Sopenharmony_ci * Check information and calculate the size of data specific to 175462306a36Sopenharmony_ci * this userspace control. 175562306a36Sopenharmony_ci */ 175662306a36Sopenharmony_ci /* pass NULL to card for suppressing error messages */ 175762306a36Sopenharmony_ci err = snd_ctl_check_elem_info(NULL, info); 175862306a36Sopenharmony_ci if (err < 0) 175962306a36Sopenharmony_ci return err; 176062306a36Sopenharmony_ci /* user-space control doesn't allow zero-size data */ 176162306a36Sopenharmony_ci if (info->count < 1) 176262306a36Sopenharmony_ci return -EINVAL; 176362306a36Sopenharmony_ci private_size = value_sizes[info->type] * info->count; 176462306a36Sopenharmony_ci alloc_size = compute_user_elem_size(private_size, count); 176562306a36Sopenharmony_ci 176662306a36Sopenharmony_ci down_write(&card->controls_rwsem); 176762306a36Sopenharmony_ci if (check_user_elem_overflow(card, alloc_size)) { 176862306a36Sopenharmony_ci err = -ENOMEM; 176962306a36Sopenharmony_ci goto unlock; 177062306a36Sopenharmony_ci } 177162306a36Sopenharmony_ci 177262306a36Sopenharmony_ci /* 177362306a36Sopenharmony_ci * Keep memory object for this userspace control. After passing this 177462306a36Sopenharmony_ci * code block, the instance should be freed by snd_ctl_free_one(). 177562306a36Sopenharmony_ci * 177662306a36Sopenharmony_ci * Note that these elements in this control are locked. 177762306a36Sopenharmony_ci */ 177862306a36Sopenharmony_ci err = snd_ctl_new(&kctl, count, access, file); 177962306a36Sopenharmony_ci if (err < 0) 178062306a36Sopenharmony_ci goto unlock; 178162306a36Sopenharmony_ci memcpy(&kctl->id, &info->id, sizeof(kctl->id)); 178262306a36Sopenharmony_ci ue = kzalloc(alloc_size, GFP_KERNEL); 178362306a36Sopenharmony_ci if (!ue) { 178462306a36Sopenharmony_ci kfree(kctl); 178562306a36Sopenharmony_ci err = -ENOMEM; 178662306a36Sopenharmony_ci goto unlock; 178762306a36Sopenharmony_ci } 178862306a36Sopenharmony_ci kctl->private_data = ue; 178962306a36Sopenharmony_ci kctl->private_free = snd_ctl_elem_user_free; 179062306a36Sopenharmony_ci 179162306a36Sopenharmony_ci // increment the allocated size; decremented again at private_free. 179262306a36Sopenharmony_ci card->user_ctl_alloc_size += alloc_size; 179362306a36Sopenharmony_ci 179462306a36Sopenharmony_ci /* Set private data for this userspace control. */ 179562306a36Sopenharmony_ci ue->card = card; 179662306a36Sopenharmony_ci ue->info = *info; 179762306a36Sopenharmony_ci ue->info.access = 0; 179862306a36Sopenharmony_ci ue->elem_data = (char *)ue + sizeof(*ue); 179962306a36Sopenharmony_ci ue->elem_data_size = private_size; 180062306a36Sopenharmony_ci if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) { 180162306a36Sopenharmony_ci err = snd_ctl_elem_init_enum_names(ue); 180262306a36Sopenharmony_ci if (err < 0) { 180362306a36Sopenharmony_ci snd_ctl_free_one(kctl); 180462306a36Sopenharmony_ci goto unlock; 180562306a36Sopenharmony_ci } 180662306a36Sopenharmony_ci } 180762306a36Sopenharmony_ci 180862306a36Sopenharmony_ci /* Set callback functions. */ 180962306a36Sopenharmony_ci if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) 181062306a36Sopenharmony_ci kctl->info = snd_ctl_elem_user_enum_info; 181162306a36Sopenharmony_ci else 181262306a36Sopenharmony_ci kctl->info = snd_ctl_elem_user_info; 181362306a36Sopenharmony_ci if (access & SNDRV_CTL_ELEM_ACCESS_READ) 181462306a36Sopenharmony_ci kctl->get = snd_ctl_elem_user_get; 181562306a36Sopenharmony_ci if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) 181662306a36Sopenharmony_ci kctl->put = snd_ctl_elem_user_put; 181762306a36Sopenharmony_ci if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) 181862306a36Sopenharmony_ci kctl->tlv.c = snd_ctl_elem_user_tlv; 181962306a36Sopenharmony_ci 182062306a36Sopenharmony_ci /* This function manage to free the instance on failure. */ 182162306a36Sopenharmony_ci err = __snd_ctl_add_replace(card, kctl, CTL_ADD_EXCLUSIVE); 182262306a36Sopenharmony_ci if (err < 0) { 182362306a36Sopenharmony_ci snd_ctl_free_one(kctl); 182462306a36Sopenharmony_ci goto unlock; 182562306a36Sopenharmony_ci } 182662306a36Sopenharmony_ci offset = snd_ctl_get_ioff(kctl, &info->id); 182762306a36Sopenharmony_ci snd_ctl_build_ioff(&info->id, kctl, offset); 182862306a36Sopenharmony_ci /* 182962306a36Sopenharmony_ci * Here we cannot fill any field for the number of elements added by 183062306a36Sopenharmony_ci * this operation because there're no specific fields. The usage of 183162306a36Sopenharmony_ci * 'owner' field for this purpose may cause any bugs to userspace 183262306a36Sopenharmony_ci * applications because the field originally means PID of a process 183362306a36Sopenharmony_ci * which locks the element. 183462306a36Sopenharmony_ci */ 183562306a36Sopenharmony_ci unlock: 183662306a36Sopenharmony_ci up_write(&card->controls_rwsem); 183762306a36Sopenharmony_ci return err; 183862306a36Sopenharmony_ci} 183962306a36Sopenharmony_ci 184062306a36Sopenharmony_cistatic int snd_ctl_elem_add_user(struct snd_ctl_file *file, 184162306a36Sopenharmony_ci struct snd_ctl_elem_info __user *_info, int replace) 184262306a36Sopenharmony_ci{ 184362306a36Sopenharmony_ci struct snd_ctl_elem_info info; 184462306a36Sopenharmony_ci int err; 184562306a36Sopenharmony_ci 184662306a36Sopenharmony_ci if (copy_from_user(&info, _info, sizeof(info))) 184762306a36Sopenharmony_ci return -EFAULT; 184862306a36Sopenharmony_ci err = snd_ctl_elem_add(file, &info, replace); 184962306a36Sopenharmony_ci if (err < 0) 185062306a36Sopenharmony_ci return err; 185162306a36Sopenharmony_ci if (copy_to_user(_info, &info, sizeof(info))) { 185262306a36Sopenharmony_ci snd_ctl_remove_user_ctl(file, &info.id); 185362306a36Sopenharmony_ci return -EFAULT; 185462306a36Sopenharmony_ci } 185562306a36Sopenharmony_ci 185662306a36Sopenharmony_ci return 0; 185762306a36Sopenharmony_ci} 185862306a36Sopenharmony_ci 185962306a36Sopenharmony_cistatic int snd_ctl_elem_remove(struct snd_ctl_file *file, 186062306a36Sopenharmony_ci struct snd_ctl_elem_id __user *_id) 186162306a36Sopenharmony_ci{ 186262306a36Sopenharmony_ci struct snd_ctl_elem_id id; 186362306a36Sopenharmony_ci 186462306a36Sopenharmony_ci if (copy_from_user(&id, _id, sizeof(id))) 186562306a36Sopenharmony_ci return -EFAULT; 186662306a36Sopenharmony_ci return snd_ctl_remove_user_ctl(file, &id); 186762306a36Sopenharmony_ci} 186862306a36Sopenharmony_ci 186962306a36Sopenharmony_cistatic int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr) 187062306a36Sopenharmony_ci{ 187162306a36Sopenharmony_ci int subscribe; 187262306a36Sopenharmony_ci if (get_user(subscribe, ptr)) 187362306a36Sopenharmony_ci return -EFAULT; 187462306a36Sopenharmony_ci if (subscribe < 0) { 187562306a36Sopenharmony_ci subscribe = file->subscribed; 187662306a36Sopenharmony_ci if (put_user(subscribe, ptr)) 187762306a36Sopenharmony_ci return -EFAULT; 187862306a36Sopenharmony_ci return 0; 187962306a36Sopenharmony_ci } 188062306a36Sopenharmony_ci if (subscribe) { 188162306a36Sopenharmony_ci file->subscribed = 1; 188262306a36Sopenharmony_ci return 0; 188362306a36Sopenharmony_ci } else if (file->subscribed) { 188462306a36Sopenharmony_ci snd_ctl_empty_read_queue(file); 188562306a36Sopenharmony_ci file->subscribed = 0; 188662306a36Sopenharmony_ci } 188762306a36Sopenharmony_ci return 0; 188862306a36Sopenharmony_ci} 188962306a36Sopenharmony_ci 189062306a36Sopenharmony_cistatic int call_tlv_handler(struct snd_ctl_file *file, int op_flag, 189162306a36Sopenharmony_ci struct snd_kcontrol *kctl, 189262306a36Sopenharmony_ci struct snd_ctl_elem_id *id, 189362306a36Sopenharmony_ci unsigned int __user *buf, unsigned int size) 189462306a36Sopenharmony_ci{ 189562306a36Sopenharmony_ci static const struct { 189662306a36Sopenharmony_ci int op; 189762306a36Sopenharmony_ci int perm; 189862306a36Sopenharmony_ci } pairs[] = { 189962306a36Sopenharmony_ci {SNDRV_CTL_TLV_OP_READ, SNDRV_CTL_ELEM_ACCESS_TLV_READ}, 190062306a36Sopenharmony_ci {SNDRV_CTL_TLV_OP_WRITE, SNDRV_CTL_ELEM_ACCESS_TLV_WRITE}, 190162306a36Sopenharmony_ci {SNDRV_CTL_TLV_OP_CMD, SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND}, 190262306a36Sopenharmony_ci }; 190362306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; 190462306a36Sopenharmony_ci int i, ret; 190562306a36Sopenharmony_ci 190662306a36Sopenharmony_ci /* Check support of the request for this element. */ 190762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(pairs); ++i) { 190862306a36Sopenharmony_ci if (op_flag == pairs[i].op && (vd->access & pairs[i].perm)) 190962306a36Sopenharmony_ci break; 191062306a36Sopenharmony_ci } 191162306a36Sopenharmony_ci if (i == ARRAY_SIZE(pairs)) 191262306a36Sopenharmony_ci return -ENXIO; 191362306a36Sopenharmony_ci 191462306a36Sopenharmony_ci if (kctl->tlv.c == NULL) 191562306a36Sopenharmony_ci return -ENXIO; 191662306a36Sopenharmony_ci 191762306a36Sopenharmony_ci /* Write and command operations are not allowed for locked element. */ 191862306a36Sopenharmony_ci if (op_flag != SNDRV_CTL_TLV_OP_READ && 191962306a36Sopenharmony_ci vd->owner != NULL && vd->owner != file) 192062306a36Sopenharmony_ci return -EPERM; 192162306a36Sopenharmony_ci 192262306a36Sopenharmony_ci ret = snd_power_ref_and_wait(file->card); 192362306a36Sopenharmony_ci if (!ret) 192462306a36Sopenharmony_ci ret = kctl->tlv.c(kctl, op_flag, size, buf); 192562306a36Sopenharmony_ci snd_power_unref(file->card); 192662306a36Sopenharmony_ci return ret; 192762306a36Sopenharmony_ci} 192862306a36Sopenharmony_ci 192962306a36Sopenharmony_cistatic int read_tlv_buf(struct snd_kcontrol *kctl, struct snd_ctl_elem_id *id, 193062306a36Sopenharmony_ci unsigned int __user *buf, unsigned int size) 193162306a36Sopenharmony_ci{ 193262306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; 193362306a36Sopenharmony_ci unsigned int len; 193462306a36Sopenharmony_ci 193562306a36Sopenharmony_ci if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)) 193662306a36Sopenharmony_ci return -ENXIO; 193762306a36Sopenharmony_ci 193862306a36Sopenharmony_ci if (kctl->tlv.p == NULL) 193962306a36Sopenharmony_ci return -ENXIO; 194062306a36Sopenharmony_ci 194162306a36Sopenharmony_ci len = sizeof(unsigned int) * 2 + kctl->tlv.p[1]; 194262306a36Sopenharmony_ci if (size < len) 194362306a36Sopenharmony_ci return -ENOMEM; 194462306a36Sopenharmony_ci 194562306a36Sopenharmony_ci if (copy_to_user(buf, kctl->tlv.p, len)) 194662306a36Sopenharmony_ci return -EFAULT; 194762306a36Sopenharmony_ci 194862306a36Sopenharmony_ci return 0; 194962306a36Sopenharmony_ci} 195062306a36Sopenharmony_ci 195162306a36Sopenharmony_cistatic int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, 195262306a36Sopenharmony_ci struct snd_ctl_tlv __user *buf, 195362306a36Sopenharmony_ci int op_flag) 195462306a36Sopenharmony_ci{ 195562306a36Sopenharmony_ci struct snd_ctl_tlv header; 195662306a36Sopenharmony_ci unsigned int __user *container; 195762306a36Sopenharmony_ci unsigned int container_size; 195862306a36Sopenharmony_ci struct snd_kcontrol *kctl; 195962306a36Sopenharmony_ci struct snd_ctl_elem_id id; 196062306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 196162306a36Sopenharmony_ci 196262306a36Sopenharmony_ci lockdep_assert_held(&file->card->controls_rwsem); 196362306a36Sopenharmony_ci 196462306a36Sopenharmony_ci if (copy_from_user(&header, buf, sizeof(header))) 196562306a36Sopenharmony_ci return -EFAULT; 196662306a36Sopenharmony_ci 196762306a36Sopenharmony_ci /* In design of control core, numerical ID starts at 1. */ 196862306a36Sopenharmony_ci if (header.numid == 0) 196962306a36Sopenharmony_ci return -EINVAL; 197062306a36Sopenharmony_ci 197162306a36Sopenharmony_ci /* At least, container should include type and length fields. */ 197262306a36Sopenharmony_ci if (header.length < sizeof(unsigned int) * 2) 197362306a36Sopenharmony_ci return -EINVAL; 197462306a36Sopenharmony_ci container_size = header.length; 197562306a36Sopenharmony_ci container = buf->tlv; 197662306a36Sopenharmony_ci 197762306a36Sopenharmony_ci kctl = snd_ctl_find_numid_locked(file->card, header.numid); 197862306a36Sopenharmony_ci if (kctl == NULL) 197962306a36Sopenharmony_ci return -ENOENT; 198062306a36Sopenharmony_ci 198162306a36Sopenharmony_ci /* Calculate index of the element in this set. */ 198262306a36Sopenharmony_ci id = kctl->id; 198362306a36Sopenharmony_ci snd_ctl_build_ioff(&id, kctl, header.numid - id.numid); 198462306a36Sopenharmony_ci vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; 198562306a36Sopenharmony_ci 198662306a36Sopenharmony_ci if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { 198762306a36Sopenharmony_ci return call_tlv_handler(file, op_flag, kctl, &id, container, 198862306a36Sopenharmony_ci container_size); 198962306a36Sopenharmony_ci } else { 199062306a36Sopenharmony_ci if (op_flag == SNDRV_CTL_TLV_OP_READ) { 199162306a36Sopenharmony_ci return read_tlv_buf(kctl, &id, container, 199262306a36Sopenharmony_ci container_size); 199362306a36Sopenharmony_ci } 199462306a36Sopenharmony_ci } 199562306a36Sopenharmony_ci 199662306a36Sopenharmony_ci /* Not supported. */ 199762306a36Sopenharmony_ci return -ENXIO; 199862306a36Sopenharmony_ci} 199962306a36Sopenharmony_ci 200062306a36Sopenharmony_cistatic long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 200162306a36Sopenharmony_ci{ 200262306a36Sopenharmony_ci struct snd_ctl_file *ctl; 200362306a36Sopenharmony_ci struct snd_card *card; 200462306a36Sopenharmony_ci struct snd_kctl_ioctl *p; 200562306a36Sopenharmony_ci void __user *argp = (void __user *)arg; 200662306a36Sopenharmony_ci int __user *ip = argp; 200762306a36Sopenharmony_ci int err; 200862306a36Sopenharmony_ci 200962306a36Sopenharmony_ci ctl = file->private_data; 201062306a36Sopenharmony_ci card = ctl->card; 201162306a36Sopenharmony_ci if (snd_BUG_ON(!card)) 201262306a36Sopenharmony_ci return -ENXIO; 201362306a36Sopenharmony_ci switch (cmd) { 201462306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_PVERSION: 201562306a36Sopenharmony_ci return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; 201662306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_CARD_INFO: 201762306a36Sopenharmony_ci return snd_ctl_card_info(card, ctl, cmd, argp); 201862306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_LIST: 201962306a36Sopenharmony_ci return snd_ctl_elem_list_user(card, argp); 202062306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_INFO: 202162306a36Sopenharmony_ci return snd_ctl_elem_info_user(ctl, argp); 202262306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_READ: 202362306a36Sopenharmony_ci return snd_ctl_elem_read_user(card, argp); 202462306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_WRITE: 202562306a36Sopenharmony_ci return snd_ctl_elem_write_user(ctl, argp); 202662306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_LOCK: 202762306a36Sopenharmony_ci return snd_ctl_elem_lock(ctl, argp); 202862306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_UNLOCK: 202962306a36Sopenharmony_ci return snd_ctl_elem_unlock(ctl, argp); 203062306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_ADD: 203162306a36Sopenharmony_ci return snd_ctl_elem_add_user(ctl, argp, 0); 203262306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_REPLACE: 203362306a36Sopenharmony_ci return snd_ctl_elem_add_user(ctl, argp, 1); 203462306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_ELEM_REMOVE: 203562306a36Sopenharmony_ci return snd_ctl_elem_remove(ctl, argp); 203662306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: 203762306a36Sopenharmony_ci return snd_ctl_subscribe_events(ctl, ip); 203862306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_TLV_READ: 203962306a36Sopenharmony_ci down_read(&ctl->card->controls_rwsem); 204062306a36Sopenharmony_ci err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ); 204162306a36Sopenharmony_ci up_read(&ctl->card->controls_rwsem); 204262306a36Sopenharmony_ci return err; 204362306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_TLV_WRITE: 204462306a36Sopenharmony_ci down_write(&ctl->card->controls_rwsem); 204562306a36Sopenharmony_ci err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE); 204662306a36Sopenharmony_ci up_write(&ctl->card->controls_rwsem); 204762306a36Sopenharmony_ci return err; 204862306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_TLV_COMMAND: 204962306a36Sopenharmony_ci down_write(&ctl->card->controls_rwsem); 205062306a36Sopenharmony_ci err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD); 205162306a36Sopenharmony_ci up_write(&ctl->card->controls_rwsem); 205262306a36Sopenharmony_ci return err; 205362306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_POWER: 205462306a36Sopenharmony_ci return -ENOPROTOOPT; 205562306a36Sopenharmony_ci case SNDRV_CTL_IOCTL_POWER_STATE: 205662306a36Sopenharmony_ci return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0; 205762306a36Sopenharmony_ci } 205862306a36Sopenharmony_ci down_read(&snd_ioctl_rwsem); 205962306a36Sopenharmony_ci list_for_each_entry(p, &snd_control_ioctls, list) { 206062306a36Sopenharmony_ci err = p->fioctl(card, ctl, cmd, arg); 206162306a36Sopenharmony_ci if (err != -ENOIOCTLCMD) { 206262306a36Sopenharmony_ci up_read(&snd_ioctl_rwsem); 206362306a36Sopenharmony_ci return err; 206462306a36Sopenharmony_ci } 206562306a36Sopenharmony_ci } 206662306a36Sopenharmony_ci up_read(&snd_ioctl_rwsem); 206762306a36Sopenharmony_ci dev_dbg(card->dev, "unknown ioctl = 0x%x\n", cmd); 206862306a36Sopenharmony_ci return -ENOTTY; 206962306a36Sopenharmony_ci} 207062306a36Sopenharmony_ci 207162306a36Sopenharmony_cistatic ssize_t snd_ctl_read(struct file *file, char __user *buffer, 207262306a36Sopenharmony_ci size_t count, loff_t * offset) 207362306a36Sopenharmony_ci{ 207462306a36Sopenharmony_ci struct snd_ctl_file *ctl; 207562306a36Sopenharmony_ci int err = 0; 207662306a36Sopenharmony_ci ssize_t result = 0; 207762306a36Sopenharmony_ci 207862306a36Sopenharmony_ci ctl = file->private_data; 207962306a36Sopenharmony_ci if (snd_BUG_ON(!ctl || !ctl->card)) 208062306a36Sopenharmony_ci return -ENXIO; 208162306a36Sopenharmony_ci if (!ctl->subscribed) 208262306a36Sopenharmony_ci return -EBADFD; 208362306a36Sopenharmony_ci if (count < sizeof(struct snd_ctl_event)) 208462306a36Sopenharmony_ci return -EINVAL; 208562306a36Sopenharmony_ci spin_lock_irq(&ctl->read_lock); 208662306a36Sopenharmony_ci while (count >= sizeof(struct snd_ctl_event)) { 208762306a36Sopenharmony_ci struct snd_ctl_event ev; 208862306a36Sopenharmony_ci struct snd_kctl_event *kev; 208962306a36Sopenharmony_ci while (list_empty(&ctl->events)) { 209062306a36Sopenharmony_ci wait_queue_entry_t wait; 209162306a36Sopenharmony_ci if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { 209262306a36Sopenharmony_ci err = -EAGAIN; 209362306a36Sopenharmony_ci goto __end_lock; 209462306a36Sopenharmony_ci } 209562306a36Sopenharmony_ci init_waitqueue_entry(&wait, current); 209662306a36Sopenharmony_ci add_wait_queue(&ctl->change_sleep, &wait); 209762306a36Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 209862306a36Sopenharmony_ci spin_unlock_irq(&ctl->read_lock); 209962306a36Sopenharmony_ci schedule(); 210062306a36Sopenharmony_ci remove_wait_queue(&ctl->change_sleep, &wait); 210162306a36Sopenharmony_ci if (ctl->card->shutdown) 210262306a36Sopenharmony_ci return -ENODEV; 210362306a36Sopenharmony_ci if (signal_pending(current)) 210462306a36Sopenharmony_ci return -ERESTARTSYS; 210562306a36Sopenharmony_ci spin_lock_irq(&ctl->read_lock); 210662306a36Sopenharmony_ci } 210762306a36Sopenharmony_ci kev = snd_kctl_event(ctl->events.next); 210862306a36Sopenharmony_ci ev.type = SNDRV_CTL_EVENT_ELEM; 210962306a36Sopenharmony_ci ev.data.elem.mask = kev->mask; 211062306a36Sopenharmony_ci ev.data.elem.id = kev->id; 211162306a36Sopenharmony_ci list_del(&kev->list); 211262306a36Sopenharmony_ci spin_unlock_irq(&ctl->read_lock); 211362306a36Sopenharmony_ci kfree(kev); 211462306a36Sopenharmony_ci if (copy_to_user(buffer, &ev, sizeof(struct snd_ctl_event))) { 211562306a36Sopenharmony_ci err = -EFAULT; 211662306a36Sopenharmony_ci goto __end; 211762306a36Sopenharmony_ci } 211862306a36Sopenharmony_ci spin_lock_irq(&ctl->read_lock); 211962306a36Sopenharmony_ci buffer += sizeof(struct snd_ctl_event); 212062306a36Sopenharmony_ci count -= sizeof(struct snd_ctl_event); 212162306a36Sopenharmony_ci result += sizeof(struct snd_ctl_event); 212262306a36Sopenharmony_ci } 212362306a36Sopenharmony_ci __end_lock: 212462306a36Sopenharmony_ci spin_unlock_irq(&ctl->read_lock); 212562306a36Sopenharmony_ci __end: 212662306a36Sopenharmony_ci return result > 0 ? result : err; 212762306a36Sopenharmony_ci} 212862306a36Sopenharmony_ci 212962306a36Sopenharmony_cistatic __poll_t snd_ctl_poll(struct file *file, poll_table * wait) 213062306a36Sopenharmony_ci{ 213162306a36Sopenharmony_ci __poll_t mask; 213262306a36Sopenharmony_ci struct snd_ctl_file *ctl; 213362306a36Sopenharmony_ci 213462306a36Sopenharmony_ci ctl = file->private_data; 213562306a36Sopenharmony_ci if (!ctl->subscribed) 213662306a36Sopenharmony_ci return 0; 213762306a36Sopenharmony_ci poll_wait(file, &ctl->change_sleep, wait); 213862306a36Sopenharmony_ci 213962306a36Sopenharmony_ci mask = 0; 214062306a36Sopenharmony_ci if (!list_empty(&ctl->events)) 214162306a36Sopenharmony_ci mask |= EPOLLIN | EPOLLRDNORM; 214262306a36Sopenharmony_ci 214362306a36Sopenharmony_ci return mask; 214462306a36Sopenharmony_ci} 214562306a36Sopenharmony_ci 214662306a36Sopenharmony_ci/* 214762306a36Sopenharmony_ci * register the device-specific control-ioctls. 214862306a36Sopenharmony_ci * called from each device manager like pcm.c, hwdep.c, etc. 214962306a36Sopenharmony_ci */ 215062306a36Sopenharmony_cistatic int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists) 215162306a36Sopenharmony_ci{ 215262306a36Sopenharmony_ci struct snd_kctl_ioctl *pn; 215362306a36Sopenharmony_ci 215462306a36Sopenharmony_ci pn = kzalloc(sizeof(struct snd_kctl_ioctl), GFP_KERNEL); 215562306a36Sopenharmony_ci if (pn == NULL) 215662306a36Sopenharmony_ci return -ENOMEM; 215762306a36Sopenharmony_ci pn->fioctl = fcn; 215862306a36Sopenharmony_ci down_write(&snd_ioctl_rwsem); 215962306a36Sopenharmony_ci list_add_tail(&pn->list, lists); 216062306a36Sopenharmony_ci up_write(&snd_ioctl_rwsem); 216162306a36Sopenharmony_ci return 0; 216262306a36Sopenharmony_ci} 216362306a36Sopenharmony_ci 216462306a36Sopenharmony_ci/** 216562306a36Sopenharmony_ci * snd_ctl_register_ioctl - register the device-specific control-ioctls 216662306a36Sopenharmony_ci * @fcn: ioctl callback function 216762306a36Sopenharmony_ci * 216862306a36Sopenharmony_ci * called from each device manager like pcm.c, hwdep.c, etc. 216962306a36Sopenharmony_ci * 217062306a36Sopenharmony_ci * Return: zero if successful, or a negative error code 217162306a36Sopenharmony_ci */ 217262306a36Sopenharmony_ciint snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn) 217362306a36Sopenharmony_ci{ 217462306a36Sopenharmony_ci return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls); 217562306a36Sopenharmony_ci} 217662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_register_ioctl); 217762306a36Sopenharmony_ci 217862306a36Sopenharmony_ci#ifdef CONFIG_COMPAT 217962306a36Sopenharmony_ci/** 218062306a36Sopenharmony_ci * snd_ctl_register_ioctl_compat - register the device-specific 32bit compat 218162306a36Sopenharmony_ci * control-ioctls 218262306a36Sopenharmony_ci * @fcn: ioctl callback function 218362306a36Sopenharmony_ci * 218462306a36Sopenharmony_ci * Return: zero if successful, or a negative error code 218562306a36Sopenharmony_ci */ 218662306a36Sopenharmony_ciint snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn) 218762306a36Sopenharmony_ci{ 218862306a36Sopenharmony_ci return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls); 218962306a36Sopenharmony_ci} 219062306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_register_ioctl_compat); 219162306a36Sopenharmony_ci#endif 219262306a36Sopenharmony_ci 219362306a36Sopenharmony_ci/* 219462306a36Sopenharmony_ci * de-register the device-specific control-ioctls. 219562306a36Sopenharmony_ci */ 219662306a36Sopenharmony_cistatic int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, 219762306a36Sopenharmony_ci struct list_head *lists) 219862306a36Sopenharmony_ci{ 219962306a36Sopenharmony_ci struct snd_kctl_ioctl *p; 220062306a36Sopenharmony_ci 220162306a36Sopenharmony_ci if (snd_BUG_ON(!fcn)) 220262306a36Sopenharmony_ci return -EINVAL; 220362306a36Sopenharmony_ci down_write(&snd_ioctl_rwsem); 220462306a36Sopenharmony_ci list_for_each_entry(p, lists, list) { 220562306a36Sopenharmony_ci if (p->fioctl == fcn) { 220662306a36Sopenharmony_ci list_del(&p->list); 220762306a36Sopenharmony_ci up_write(&snd_ioctl_rwsem); 220862306a36Sopenharmony_ci kfree(p); 220962306a36Sopenharmony_ci return 0; 221062306a36Sopenharmony_ci } 221162306a36Sopenharmony_ci } 221262306a36Sopenharmony_ci up_write(&snd_ioctl_rwsem); 221362306a36Sopenharmony_ci snd_BUG(); 221462306a36Sopenharmony_ci return -EINVAL; 221562306a36Sopenharmony_ci} 221662306a36Sopenharmony_ci 221762306a36Sopenharmony_ci/** 221862306a36Sopenharmony_ci * snd_ctl_unregister_ioctl - de-register the device-specific control-ioctls 221962306a36Sopenharmony_ci * @fcn: ioctl callback function to unregister 222062306a36Sopenharmony_ci * 222162306a36Sopenharmony_ci * Return: zero if successful, or a negative error code 222262306a36Sopenharmony_ci */ 222362306a36Sopenharmony_ciint snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn) 222462306a36Sopenharmony_ci{ 222562306a36Sopenharmony_ci return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls); 222662306a36Sopenharmony_ci} 222762306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_unregister_ioctl); 222862306a36Sopenharmony_ci 222962306a36Sopenharmony_ci#ifdef CONFIG_COMPAT 223062306a36Sopenharmony_ci/** 223162306a36Sopenharmony_ci * snd_ctl_unregister_ioctl_compat - de-register the device-specific compat 223262306a36Sopenharmony_ci * 32bit control-ioctls 223362306a36Sopenharmony_ci * @fcn: ioctl callback function to unregister 223462306a36Sopenharmony_ci * 223562306a36Sopenharmony_ci * Return: zero if successful, or a negative error code 223662306a36Sopenharmony_ci */ 223762306a36Sopenharmony_ciint snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn) 223862306a36Sopenharmony_ci{ 223962306a36Sopenharmony_ci return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls); 224062306a36Sopenharmony_ci} 224162306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat); 224262306a36Sopenharmony_ci#endif 224362306a36Sopenharmony_ci 224462306a36Sopenharmony_cistatic int snd_ctl_fasync(int fd, struct file * file, int on) 224562306a36Sopenharmony_ci{ 224662306a36Sopenharmony_ci struct snd_ctl_file *ctl; 224762306a36Sopenharmony_ci 224862306a36Sopenharmony_ci ctl = file->private_data; 224962306a36Sopenharmony_ci return snd_fasync_helper(fd, file, on, &ctl->fasync); 225062306a36Sopenharmony_ci} 225162306a36Sopenharmony_ci 225262306a36Sopenharmony_ci/* return the preferred subdevice number if already assigned; 225362306a36Sopenharmony_ci * otherwise return -1 225462306a36Sopenharmony_ci */ 225562306a36Sopenharmony_ciint snd_ctl_get_preferred_subdevice(struct snd_card *card, int type) 225662306a36Sopenharmony_ci{ 225762306a36Sopenharmony_ci struct snd_ctl_file *kctl; 225862306a36Sopenharmony_ci int subdevice = -1; 225962306a36Sopenharmony_ci unsigned long flags; 226062306a36Sopenharmony_ci 226162306a36Sopenharmony_ci read_lock_irqsave(&card->ctl_files_rwlock, flags); 226262306a36Sopenharmony_ci list_for_each_entry(kctl, &card->ctl_files, list) { 226362306a36Sopenharmony_ci if (kctl->pid == task_pid(current)) { 226462306a36Sopenharmony_ci subdevice = kctl->preferred_subdevice[type]; 226562306a36Sopenharmony_ci if (subdevice != -1) 226662306a36Sopenharmony_ci break; 226762306a36Sopenharmony_ci } 226862306a36Sopenharmony_ci } 226962306a36Sopenharmony_ci read_unlock_irqrestore(&card->ctl_files_rwlock, flags); 227062306a36Sopenharmony_ci return subdevice; 227162306a36Sopenharmony_ci} 227262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_get_preferred_subdevice); 227362306a36Sopenharmony_ci 227462306a36Sopenharmony_ci/* 227562306a36Sopenharmony_ci * ioctl32 compat 227662306a36Sopenharmony_ci */ 227762306a36Sopenharmony_ci#ifdef CONFIG_COMPAT 227862306a36Sopenharmony_ci#include "control_compat.c" 227962306a36Sopenharmony_ci#else 228062306a36Sopenharmony_ci#define snd_ctl_ioctl_compat NULL 228162306a36Sopenharmony_ci#endif 228262306a36Sopenharmony_ci 228362306a36Sopenharmony_ci/* 228462306a36Sopenharmony_ci * control layers (audio LED etc.) 228562306a36Sopenharmony_ci */ 228662306a36Sopenharmony_ci 228762306a36Sopenharmony_ci/** 228862306a36Sopenharmony_ci * snd_ctl_request_layer - request to use the layer 228962306a36Sopenharmony_ci * @module_name: Name of the kernel module (NULL == build-in) 229062306a36Sopenharmony_ci * 229162306a36Sopenharmony_ci * Return: zero if successful, or an error code when the module cannot be loaded 229262306a36Sopenharmony_ci */ 229362306a36Sopenharmony_ciint snd_ctl_request_layer(const char *module_name) 229462306a36Sopenharmony_ci{ 229562306a36Sopenharmony_ci struct snd_ctl_layer_ops *lops; 229662306a36Sopenharmony_ci 229762306a36Sopenharmony_ci if (module_name == NULL) 229862306a36Sopenharmony_ci return 0; 229962306a36Sopenharmony_ci down_read(&snd_ctl_layer_rwsem); 230062306a36Sopenharmony_ci for (lops = snd_ctl_layer; lops; lops = lops->next) 230162306a36Sopenharmony_ci if (strcmp(lops->module_name, module_name) == 0) 230262306a36Sopenharmony_ci break; 230362306a36Sopenharmony_ci up_read(&snd_ctl_layer_rwsem); 230462306a36Sopenharmony_ci if (lops) 230562306a36Sopenharmony_ci return 0; 230662306a36Sopenharmony_ci return request_module(module_name); 230762306a36Sopenharmony_ci} 230862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_request_layer); 230962306a36Sopenharmony_ci 231062306a36Sopenharmony_ci/** 231162306a36Sopenharmony_ci * snd_ctl_register_layer - register new control layer 231262306a36Sopenharmony_ci * @lops: operation structure 231362306a36Sopenharmony_ci * 231462306a36Sopenharmony_ci * The new layer can track all control elements and do additional 231562306a36Sopenharmony_ci * operations on top (like audio LED handling). 231662306a36Sopenharmony_ci */ 231762306a36Sopenharmony_civoid snd_ctl_register_layer(struct snd_ctl_layer_ops *lops) 231862306a36Sopenharmony_ci{ 231962306a36Sopenharmony_ci struct snd_card *card; 232062306a36Sopenharmony_ci int card_number; 232162306a36Sopenharmony_ci 232262306a36Sopenharmony_ci down_write(&snd_ctl_layer_rwsem); 232362306a36Sopenharmony_ci lops->next = snd_ctl_layer; 232462306a36Sopenharmony_ci snd_ctl_layer = lops; 232562306a36Sopenharmony_ci up_write(&snd_ctl_layer_rwsem); 232662306a36Sopenharmony_ci for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { 232762306a36Sopenharmony_ci card = snd_card_ref(card_number); 232862306a36Sopenharmony_ci if (card) { 232962306a36Sopenharmony_ci down_read(&card->controls_rwsem); 233062306a36Sopenharmony_ci lops->lregister(card); 233162306a36Sopenharmony_ci up_read(&card->controls_rwsem); 233262306a36Sopenharmony_ci snd_card_unref(card); 233362306a36Sopenharmony_ci } 233462306a36Sopenharmony_ci } 233562306a36Sopenharmony_ci} 233662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_register_layer); 233762306a36Sopenharmony_ci 233862306a36Sopenharmony_ci/** 233962306a36Sopenharmony_ci * snd_ctl_disconnect_layer - disconnect control layer 234062306a36Sopenharmony_ci * @lops: operation structure 234162306a36Sopenharmony_ci * 234262306a36Sopenharmony_ci * It is expected that the information about tracked cards 234362306a36Sopenharmony_ci * is freed before this call (the disconnect callback is 234462306a36Sopenharmony_ci * not called here). 234562306a36Sopenharmony_ci */ 234662306a36Sopenharmony_civoid snd_ctl_disconnect_layer(struct snd_ctl_layer_ops *lops) 234762306a36Sopenharmony_ci{ 234862306a36Sopenharmony_ci struct snd_ctl_layer_ops *lops2, *prev_lops2; 234962306a36Sopenharmony_ci 235062306a36Sopenharmony_ci down_write(&snd_ctl_layer_rwsem); 235162306a36Sopenharmony_ci for (lops2 = snd_ctl_layer, prev_lops2 = NULL; lops2; lops2 = lops2->next) { 235262306a36Sopenharmony_ci if (lops2 == lops) { 235362306a36Sopenharmony_ci if (!prev_lops2) 235462306a36Sopenharmony_ci snd_ctl_layer = lops->next; 235562306a36Sopenharmony_ci else 235662306a36Sopenharmony_ci prev_lops2->next = lops->next; 235762306a36Sopenharmony_ci break; 235862306a36Sopenharmony_ci } 235962306a36Sopenharmony_ci prev_lops2 = lops2; 236062306a36Sopenharmony_ci } 236162306a36Sopenharmony_ci up_write(&snd_ctl_layer_rwsem); 236262306a36Sopenharmony_ci} 236362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer); 236462306a36Sopenharmony_ci 236562306a36Sopenharmony_ci/* 236662306a36Sopenharmony_ci * INIT PART 236762306a36Sopenharmony_ci */ 236862306a36Sopenharmony_ci 236962306a36Sopenharmony_cistatic const struct file_operations snd_ctl_f_ops = 237062306a36Sopenharmony_ci{ 237162306a36Sopenharmony_ci .owner = THIS_MODULE, 237262306a36Sopenharmony_ci .read = snd_ctl_read, 237362306a36Sopenharmony_ci .open = snd_ctl_open, 237462306a36Sopenharmony_ci .release = snd_ctl_release, 237562306a36Sopenharmony_ci .llseek = no_llseek, 237662306a36Sopenharmony_ci .poll = snd_ctl_poll, 237762306a36Sopenharmony_ci .unlocked_ioctl = snd_ctl_ioctl, 237862306a36Sopenharmony_ci .compat_ioctl = snd_ctl_ioctl_compat, 237962306a36Sopenharmony_ci .fasync = snd_ctl_fasync, 238062306a36Sopenharmony_ci}; 238162306a36Sopenharmony_ci 238262306a36Sopenharmony_ci/* 238362306a36Sopenharmony_ci * registration of the control device 238462306a36Sopenharmony_ci */ 238562306a36Sopenharmony_cistatic int snd_ctl_dev_register(struct snd_device *device) 238662306a36Sopenharmony_ci{ 238762306a36Sopenharmony_ci struct snd_card *card = device->device_data; 238862306a36Sopenharmony_ci struct snd_ctl_layer_ops *lops; 238962306a36Sopenharmony_ci int err; 239062306a36Sopenharmony_ci 239162306a36Sopenharmony_ci err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, 239262306a36Sopenharmony_ci &snd_ctl_f_ops, card, card->ctl_dev); 239362306a36Sopenharmony_ci if (err < 0) 239462306a36Sopenharmony_ci return err; 239562306a36Sopenharmony_ci down_read(&card->controls_rwsem); 239662306a36Sopenharmony_ci down_read(&snd_ctl_layer_rwsem); 239762306a36Sopenharmony_ci for (lops = snd_ctl_layer; lops; lops = lops->next) 239862306a36Sopenharmony_ci lops->lregister(card); 239962306a36Sopenharmony_ci up_read(&snd_ctl_layer_rwsem); 240062306a36Sopenharmony_ci up_read(&card->controls_rwsem); 240162306a36Sopenharmony_ci return 0; 240262306a36Sopenharmony_ci} 240362306a36Sopenharmony_ci 240462306a36Sopenharmony_ci/* 240562306a36Sopenharmony_ci * disconnection of the control device 240662306a36Sopenharmony_ci */ 240762306a36Sopenharmony_cistatic int snd_ctl_dev_disconnect(struct snd_device *device) 240862306a36Sopenharmony_ci{ 240962306a36Sopenharmony_ci struct snd_card *card = device->device_data; 241062306a36Sopenharmony_ci struct snd_ctl_file *ctl; 241162306a36Sopenharmony_ci struct snd_ctl_layer_ops *lops; 241262306a36Sopenharmony_ci unsigned long flags; 241362306a36Sopenharmony_ci 241462306a36Sopenharmony_ci read_lock_irqsave(&card->ctl_files_rwlock, flags); 241562306a36Sopenharmony_ci list_for_each_entry(ctl, &card->ctl_files, list) { 241662306a36Sopenharmony_ci wake_up(&ctl->change_sleep); 241762306a36Sopenharmony_ci snd_kill_fasync(ctl->fasync, SIGIO, POLL_ERR); 241862306a36Sopenharmony_ci } 241962306a36Sopenharmony_ci read_unlock_irqrestore(&card->ctl_files_rwlock, flags); 242062306a36Sopenharmony_ci 242162306a36Sopenharmony_ci down_read(&card->controls_rwsem); 242262306a36Sopenharmony_ci down_read(&snd_ctl_layer_rwsem); 242362306a36Sopenharmony_ci for (lops = snd_ctl_layer; lops; lops = lops->next) 242462306a36Sopenharmony_ci lops->ldisconnect(card); 242562306a36Sopenharmony_ci up_read(&snd_ctl_layer_rwsem); 242662306a36Sopenharmony_ci up_read(&card->controls_rwsem); 242762306a36Sopenharmony_ci 242862306a36Sopenharmony_ci return snd_unregister_device(card->ctl_dev); 242962306a36Sopenharmony_ci} 243062306a36Sopenharmony_ci 243162306a36Sopenharmony_ci/* 243262306a36Sopenharmony_ci * free all controls 243362306a36Sopenharmony_ci */ 243462306a36Sopenharmony_cistatic int snd_ctl_dev_free(struct snd_device *device) 243562306a36Sopenharmony_ci{ 243662306a36Sopenharmony_ci struct snd_card *card = device->device_data; 243762306a36Sopenharmony_ci struct snd_kcontrol *control; 243862306a36Sopenharmony_ci 243962306a36Sopenharmony_ci down_write(&card->controls_rwsem); 244062306a36Sopenharmony_ci while (!list_empty(&card->controls)) { 244162306a36Sopenharmony_ci control = snd_kcontrol(card->controls.next); 244262306a36Sopenharmony_ci __snd_ctl_remove(card, control, false); 244362306a36Sopenharmony_ci } 244462306a36Sopenharmony_ci 244562306a36Sopenharmony_ci#ifdef CONFIG_SND_CTL_FAST_LOOKUP 244662306a36Sopenharmony_ci xa_destroy(&card->ctl_numids); 244762306a36Sopenharmony_ci xa_destroy(&card->ctl_hash); 244862306a36Sopenharmony_ci#endif 244962306a36Sopenharmony_ci up_write(&card->controls_rwsem); 245062306a36Sopenharmony_ci put_device(card->ctl_dev); 245162306a36Sopenharmony_ci return 0; 245262306a36Sopenharmony_ci} 245362306a36Sopenharmony_ci 245462306a36Sopenharmony_ci/* 245562306a36Sopenharmony_ci * create control core: 245662306a36Sopenharmony_ci * called from init.c 245762306a36Sopenharmony_ci */ 245862306a36Sopenharmony_ciint snd_ctl_create(struct snd_card *card) 245962306a36Sopenharmony_ci{ 246062306a36Sopenharmony_ci static const struct snd_device_ops ops = { 246162306a36Sopenharmony_ci .dev_free = snd_ctl_dev_free, 246262306a36Sopenharmony_ci .dev_register = snd_ctl_dev_register, 246362306a36Sopenharmony_ci .dev_disconnect = snd_ctl_dev_disconnect, 246462306a36Sopenharmony_ci }; 246562306a36Sopenharmony_ci int err; 246662306a36Sopenharmony_ci 246762306a36Sopenharmony_ci if (snd_BUG_ON(!card)) 246862306a36Sopenharmony_ci return -ENXIO; 246962306a36Sopenharmony_ci if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS)) 247062306a36Sopenharmony_ci return -ENXIO; 247162306a36Sopenharmony_ci 247262306a36Sopenharmony_ci err = snd_device_alloc(&card->ctl_dev, card); 247362306a36Sopenharmony_ci if (err < 0) 247462306a36Sopenharmony_ci return err; 247562306a36Sopenharmony_ci dev_set_name(card->ctl_dev, "controlC%d", card->number); 247662306a36Sopenharmony_ci 247762306a36Sopenharmony_ci err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); 247862306a36Sopenharmony_ci if (err < 0) 247962306a36Sopenharmony_ci put_device(card->ctl_dev); 248062306a36Sopenharmony_ci return err; 248162306a36Sopenharmony_ci} 248262306a36Sopenharmony_ci 248362306a36Sopenharmony_ci/* 248462306a36Sopenharmony_ci * Frequently used control callbacks/helpers 248562306a36Sopenharmony_ci */ 248662306a36Sopenharmony_ci 248762306a36Sopenharmony_ci/** 248862306a36Sopenharmony_ci * snd_ctl_boolean_mono_info - Helper function for a standard boolean info 248962306a36Sopenharmony_ci * callback with a mono channel 249062306a36Sopenharmony_ci * @kcontrol: the kcontrol instance 249162306a36Sopenharmony_ci * @uinfo: info to store 249262306a36Sopenharmony_ci * 249362306a36Sopenharmony_ci * This is a function that can be used as info callback for a standard 249462306a36Sopenharmony_ci * boolean control with a single mono channel. 249562306a36Sopenharmony_ci * 249662306a36Sopenharmony_ci * Return: Zero (always successful) 249762306a36Sopenharmony_ci */ 249862306a36Sopenharmony_ciint snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol, 249962306a36Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 250062306a36Sopenharmony_ci{ 250162306a36Sopenharmony_ci uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; 250262306a36Sopenharmony_ci uinfo->count = 1; 250362306a36Sopenharmony_ci uinfo->value.integer.min = 0; 250462306a36Sopenharmony_ci uinfo->value.integer.max = 1; 250562306a36Sopenharmony_ci return 0; 250662306a36Sopenharmony_ci} 250762306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_boolean_mono_info); 250862306a36Sopenharmony_ci 250962306a36Sopenharmony_ci/** 251062306a36Sopenharmony_ci * snd_ctl_boolean_stereo_info - Helper function for a standard boolean info 251162306a36Sopenharmony_ci * callback with stereo two channels 251262306a36Sopenharmony_ci * @kcontrol: the kcontrol instance 251362306a36Sopenharmony_ci * @uinfo: info to store 251462306a36Sopenharmony_ci * 251562306a36Sopenharmony_ci * This is a function that can be used as info callback for a standard 251662306a36Sopenharmony_ci * boolean control with stereo two channels. 251762306a36Sopenharmony_ci * 251862306a36Sopenharmony_ci * Return: Zero (always successful) 251962306a36Sopenharmony_ci */ 252062306a36Sopenharmony_ciint snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol, 252162306a36Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 252262306a36Sopenharmony_ci{ 252362306a36Sopenharmony_ci uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; 252462306a36Sopenharmony_ci uinfo->count = 2; 252562306a36Sopenharmony_ci uinfo->value.integer.min = 0; 252662306a36Sopenharmony_ci uinfo->value.integer.max = 1; 252762306a36Sopenharmony_ci return 0; 252862306a36Sopenharmony_ci} 252962306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_boolean_stereo_info); 253062306a36Sopenharmony_ci 253162306a36Sopenharmony_ci/** 253262306a36Sopenharmony_ci * snd_ctl_enum_info - fills the info structure for an enumerated control 253362306a36Sopenharmony_ci * @info: the structure to be filled 253462306a36Sopenharmony_ci * @channels: the number of the control's channels; often one 253562306a36Sopenharmony_ci * @items: the number of control values; also the size of @names 253662306a36Sopenharmony_ci * @names: an array containing the names of all control values 253762306a36Sopenharmony_ci * 253862306a36Sopenharmony_ci * Sets all required fields in @info to their appropriate values. 253962306a36Sopenharmony_ci * If the control's accessibility is not the default (readable and writable), 254062306a36Sopenharmony_ci * the caller has to fill @info->access. 254162306a36Sopenharmony_ci * 254262306a36Sopenharmony_ci * Return: Zero (always successful) 254362306a36Sopenharmony_ci */ 254462306a36Sopenharmony_ciint snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels, 254562306a36Sopenharmony_ci unsigned int items, const char *const names[]) 254662306a36Sopenharmony_ci{ 254762306a36Sopenharmony_ci info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; 254862306a36Sopenharmony_ci info->count = channels; 254962306a36Sopenharmony_ci info->value.enumerated.items = items; 255062306a36Sopenharmony_ci if (!items) 255162306a36Sopenharmony_ci return 0; 255262306a36Sopenharmony_ci if (info->value.enumerated.item >= items) 255362306a36Sopenharmony_ci info->value.enumerated.item = items - 1; 255462306a36Sopenharmony_ci WARN(strlen(names[info->value.enumerated.item]) >= sizeof(info->value.enumerated.name), 255562306a36Sopenharmony_ci "ALSA: too long item name '%s'\n", 255662306a36Sopenharmony_ci names[info->value.enumerated.item]); 255762306a36Sopenharmony_ci strscpy(info->value.enumerated.name, 255862306a36Sopenharmony_ci names[info->value.enumerated.item], 255962306a36Sopenharmony_ci sizeof(info->value.enumerated.name)); 256062306a36Sopenharmony_ci return 0; 256162306a36Sopenharmony_ci} 256262306a36Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_enum_info); 2563