2019-05-27 13:55:05 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Routines for driver control interface
|
2007-10-15 14:50:19 +07:00
|
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/threads.h>
|
|
|
|
#include <linux/interrupt.h>
|
2011-07-15 23:38:28 +07:00
|
|
|
#include <linux/module.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <linux/time.h>
|
2018-01-08 01:09:15 +07:00
|
|
|
#include <linux/mm.h>
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
#include <linux/math64.h>
|
2017-02-03 01:15:33 +07:00
|
|
|
#include <linux/sched/signal.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <sound/core.h>
|
|
|
|
#include <sound/minors.h>
|
|
|
|
#include <sound/info.h>
|
|
|
|
#include <sound/control.h>
|
|
|
|
|
|
|
|
/* max number of user-defined controls */
|
|
|
|
#define MAX_USER_CONTROLS 32
|
2010-09-29 01:18:20 +07:00
|
|
|
#define MAX_CONTROL_COUNT 1028
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kctl_ioctl {
|
2005-04-17 05:20:36 +07:00
|
|
|
struct list_head list; /* list of all ioctls */
|
|
|
|
snd_kctl_ioctl_func_t fioctl;
|
2005-11-17 19:53:23 +07:00
|
|
|
};
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
static DECLARE_RWSEM(snd_ioctl_rwsem);
|
|
|
|
static LIST_HEAD(snd_control_ioctls);
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
static LIST_HEAD(snd_control_compat_ioctls);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int snd_ctl_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card;
|
|
|
|
struct snd_ctl_file *ctl;
|
2014-02-19 20:30:29 +07:00
|
|
|
int i, err;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2019-03-27 03:51:19 +07:00
|
|
|
err = stream_open(inode, file);
|
2010-04-13 16:49:04 +07:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2005-11-20 20:06:59 +07:00
|
|
|
card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!card) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto __error1;
|
|
|
|
}
|
|
|
|
err = snd_card_file_add(card, file);
|
|
|
|
if (err < 0) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto __error1;
|
|
|
|
}
|
|
|
|
if (!try_module_get(card->module)) {
|
|
|
|
err = -EFAULT;
|
|
|
|
goto __error2;
|
|
|
|
}
|
2005-09-09 19:20:23 +07:00
|
|
|
ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (ctl == NULL) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto __error;
|
|
|
|
}
|
|
|
|
INIT_LIST_HEAD(&ctl->events);
|
|
|
|
init_waitqueue_head(&ctl->change_sleep);
|
|
|
|
spin_lock_init(&ctl->read_lock);
|
|
|
|
ctl->card = card;
|
2014-02-19 20:30:29 +07:00
|
|
|
for (i = 0; i < SND_CTL_SUBDEV_ITEMS; i++)
|
|
|
|
ctl->preferred_subdevice[i] = -1;
|
2009-11-02 15:35:44 +07:00
|
|
|
ctl->pid = get_pid(task_pid(current));
|
2005-04-17 05:20:36 +07:00
|
|
|
file->private_data = ctl;
|
|
|
|
write_lock_irqsave(&card->ctl_files_rwlock, flags);
|
|
|
|
list_add_tail(&ctl->list, &card->ctl_files);
|
|
|
|
write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
|
2012-10-16 18:05:59 +07:00
|
|
|
snd_card_unref(card);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
__error:
|
|
|
|
module_put(card->module);
|
|
|
|
__error2:
|
|
|
|
snd_card_file_remove(card, file);
|
|
|
|
__error1:
|
2012-10-16 18:05:59 +07:00
|
|
|
if (card)
|
|
|
|
snd_card_unref(card);
|
2005-04-17 05:20:36 +07:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static void snd_ctl_empty_read_queue(struct snd_ctl_file * ctl)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2007-10-22 22:11:09 +07:00
|
|
|
unsigned long flags;
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kctl_event *cread;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2007-10-22 22:11:09 +07:00
|
|
|
spin_lock_irqsave(&ctl->read_lock, flags);
|
2005-04-17 05:20:36 +07:00
|
|
|
while (!list_empty(&ctl->events)) {
|
|
|
|
cread = snd_kctl_event(ctl->events.next);
|
|
|
|
list_del(&cread->list);
|
|
|
|
kfree(cread);
|
|
|
|
}
|
2007-10-22 22:11:09 +07:00
|
|
|
spin_unlock_irqrestore(&ctl->read_lock, flags);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ctl_release(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card;
|
|
|
|
struct snd_ctl_file *ctl;
|
|
|
|
struct snd_kcontrol *control;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int idx;
|
|
|
|
|
|
|
|
ctl = file->private_data;
|
|
|
|
file->private_data = NULL;
|
|
|
|
card = ctl->card;
|
|
|
|
write_lock_irqsave(&card->ctl_files_rwlock, flags);
|
|
|
|
list_del(&ctl->list);
|
|
|
|
write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
|
|
|
|
down_write(&card->controls_rwsem);
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(control, &card->controls, list)
|
2005-04-17 05:20:36 +07:00
|
|
|
for (idx = 0; idx < control->count; idx++)
|
|
|
|
if (control->vd[idx].owner == ctl)
|
|
|
|
control->vd[idx].owner = NULL;
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
snd_ctl_empty_read_queue(ctl);
|
2009-11-02 15:35:44 +07:00
|
|
|
put_pid(ctl->pid);
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(ctl);
|
|
|
|
module_put(card->module);
|
|
|
|
snd_card_file_remove(card, file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-30 19:44:34 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_notify - Send notification to user-space for a control change
|
|
|
|
* @card: the card to send notification
|
|
|
|
* @mask: the event mask, SNDRV_CTL_EVENT_*
|
|
|
|
* @id: the ctl element id to send notification
|
|
|
|
*
|
|
|
|
* This function adds an event record with the given id and mask, appends
|
|
|
|
* to the list and wakes up the user-space for notification. This can be
|
|
|
|
* called in the atomic context.
|
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
void snd_ctl_notify(struct snd_card *card, unsigned int mask,
|
|
|
|
struct snd_ctl_elem_id *id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned long flags;
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_file *ctl;
|
|
|
|
struct snd_kctl_event *ev;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !id))
|
|
|
|
return;
|
2016-07-08 13:05:19 +07:00
|
|
|
if (card->shutdown)
|
|
|
|
return;
|
2005-04-17 05:20:36 +07:00
|
|
|
read_lock(&card->ctl_files_rwlock);
|
2014-02-10 15:48:47 +07:00
|
|
|
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
|
2005-04-17 05:20:36 +07:00
|
|
|
card->mixer_oss_change_count++;
|
|
|
|
#endif
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(ctl, &card->ctl_files, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!ctl->subscribed)
|
|
|
|
continue;
|
|
|
|
spin_lock_irqsave(&ctl->read_lock, flags);
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(ev, &ctl->events, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (ev->id.numid == id->numid) {
|
|
|
|
ev->mask |= mask;
|
|
|
|
goto _found;
|
|
|
|
}
|
|
|
|
}
|
2005-09-09 19:20:23 +07:00
|
|
|
ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (ev) {
|
|
|
|
ev->id = *id;
|
|
|
|
ev->mask = mask;
|
|
|
|
list_add_tail(&ev->list, &ctl->events);
|
|
|
|
} else {
|
2014-02-05 00:18:16 +07:00
|
|
|
dev_err(card->dev, "No memory available to allocate event\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
_found:
|
|
|
|
wake_up(&ctl->change_sleep);
|
|
|
|
spin_unlock_irqrestore(&ctl->read_lock, flags);
|
|
|
|
kill_fasync(&ctl->fasync, SIGIO, POLL_IN);
|
|
|
|
}
|
|
|
|
read_unlock(&card->ctl_files_rwlock);
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_notify);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
2015-03-10 20:13:31 +07:00
|
|
|
* snd_ctl_new - create a new control instance with some elements
|
|
|
|
* @kctl: the pointer to store new control instance
|
|
|
|
* @count: the number of elements in this control
|
|
|
|
* @access: the default access flags for elements in this control
|
|
|
|
* @file: given when locking these elements
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2015-03-10 20:13:31 +07:00
|
|
|
* Allocates a memory object for a new control instance. The instance has
|
|
|
|
* elements as many as the given number (@count). Each element has given
|
|
|
|
* access permissions (@access). Each element is locked when @file is given.
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2015-03-10 20:13:31 +07:00
|
|
|
* Return: 0 on success, error code on failure
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2015-03-10 20:13:31 +07:00
|
|
|
static int snd_ctl_new(struct snd_kcontrol **kctl, unsigned int count,
|
|
|
|
unsigned int access, struct snd_ctl_file *file)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned int idx;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2015-03-10 20:13:31 +07:00
|
|
|
if (count == 0 || count > MAX_CONTROL_COUNT)
|
|
|
|
return -EINVAL;
|
2010-09-29 01:18:20 +07:00
|
|
|
|
2019-05-31 16:44:44 +07:00
|
|
|
*kctl = kzalloc(struct_size(*kctl, vd, count), GFP_KERNEL);
|
2015-03-10 21:42:14 +07:00
|
|
|
if (!*kctl)
|
2015-03-10 20:13:31 +07:00
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
for (idx = 0; idx < count; idx++) {
|
|
|
|
(*kctl)->vd[idx].access = access;
|
|
|
|
(*kctl)->vd[idx].owner = file;
|
|
|
|
}
|
|
|
|
(*kctl)->count = count;
|
|
|
|
|
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_ctl_new1 - create a control instance from the template
|
|
|
|
* @ncontrol: the initialization record
|
|
|
|
* @private_data: the private data to set
|
|
|
|
*
|
2018-02-28 00:29:54 +07:00
|
|
|
* Allocates a new struct snd_kcontrol instance and initialize from the given
|
2005-04-17 05:20:36 +07:00
|
|
|
* template. When the access field of ncontrol is 0, it's assumed as
|
|
|
|
* READWRITE access. When the count field is 0, it's assumes as one.
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: The pointer of the newly generated instance, or %NULL on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
|
|
|
|
void *private_data)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2015-03-10 20:13:31 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
unsigned int count;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int access;
|
2015-03-10 20:13:31 +07:00
|
|
|
int err;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!ncontrol || !ncontrol->info))
|
|
|
|
return NULL;
|
2015-03-10 20:13:31 +07:00
|
|
|
|
|
|
|
count = ncontrol->count;
|
|
|
|
if (count == 0)
|
|
|
|
count = 1;
|
|
|
|
|
|
|
|
access = ncontrol->access;
|
|
|
|
if (access == 0)
|
|
|
|
access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
|
|
|
access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_VOLATILE |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_INACTIVE |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);
|
2015-03-10 20:13:31 +07:00
|
|
|
|
|
|
|
err = snd_ctl_new(&kctl, count, access, NULL);
|
|
|
|
if (err < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* The 'numid' member is decided when calling snd_ctl_add(). */
|
|
|
|
kctl->id.iface = ncontrol->iface;
|
|
|
|
kctl->id.device = ncontrol->device;
|
|
|
|
kctl->id.subdevice = ncontrol->subdevice;
|
2008-10-29 21:40:30 +07:00
|
|
|
if (ncontrol->name) {
|
2015-03-10 20:13:31 +07:00
|
|
|
strlcpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
|
|
|
|
if (strcmp(ncontrol->name, kctl->id.name) != 0)
|
2014-02-05 00:18:16 +07:00
|
|
|
pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
|
2015-03-10 20:13:31 +07:00
|
|
|
ncontrol->name, kctl->id.name);
|
2008-10-29 21:40:30 +07:00
|
|
|
}
|
2015-03-10 20:13:31 +07:00
|
|
|
kctl->id.index = ncontrol->index;
|
|
|
|
|
|
|
|
kctl->info = ncontrol->info;
|
|
|
|
kctl->get = ncontrol->get;
|
|
|
|
kctl->put = ncontrol->put;
|
|
|
|
kctl->tlv.p = ncontrol->tlv.p;
|
|
|
|
|
|
|
|
kctl->private_value = ncontrol->private_value;
|
|
|
|
kctl->private_data = private_data;
|
|
|
|
|
|
|
|
return kctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_new1);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_free_one - release the control instance
|
|
|
|
* @kcontrol: the control instance
|
|
|
|
*
|
|
|
|
* Releases the control instance created via snd_ctl_new()
|
|
|
|
* or snd_ctl_new1().
|
|
|
|
* Don't call this after the control was added to the card.
|
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
void snd_ctl_free_one(struct snd_kcontrol *kcontrol)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (kcontrol) {
|
|
|
|
if (kcontrol->private_free)
|
|
|
|
kcontrol->private_free(kcontrol);
|
|
|
|
kfree(kcontrol);
|
|
|
|
}
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_free_one);
|
|
|
|
|
2011-03-07 19:24:30 +07:00
|
|
|
static bool snd_ctl_remove_numid_conflict(struct snd_card *card,
|
|
|
|
unsigned int count)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2014-06-18 18:32:34 +07:00
|
|
|
/* Make sure that the ids assigned to the control do not wrap around */
|
|
|
|
if (card->last_numid >= UINT_MAX - count)
|
|
|
|
card->last_numid = 0;
|
|
|
|
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(kctl, &card->controls, list) {
|
2011-03-07 19:22:50 +07:00
|
|
|
if (kctl->id.numid < card->last_numid + 1 + count &&
|
2011-03-07 19:24:30 +07:00
|
|
|
kctl->id.numid + kctl->count > card->last_numid + 1) {
|
|
|
|
card->last_numid = kctl->id.numid + kctl->count - 1;
|
|
|
|
return true;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2011-03-07 19:24:30 +07:00
|
|
|
return false;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_find_hole(struct snd_card *card, unsigned int count)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-03-07 19:24:30 +07:00
|
|
|
unsigned int iter = 100000;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-03-07 19:24:30 +07:00
|
|
|
while (snd_ctl_remove_numid_conflict(card, count)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (--iter == 0) {
|
|
|
|
/* this situation is very unlikely */
|
2014-02-05 00:18:16 +07:00
|
|
|
dev_err(card->dev, "unable to allocate new control numid\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-22 21:22:40 +07:00
|
|
|
enum snd_ctl_add_mode {
|
|
|
|
CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* add/replace a new kcontrol object; call with card->controls_rwsem locked */
|
|
|
|
static int __snd_ctl_add_replace(struct snd_card *card,
|
|
|
|
struct snd_kcontrol *kcontrol,
|
|
|
|
enum snd_ctl_add_mode mode)
|
2018-11-22 20:36:17 +07:00
|
|
|
{
|
|
|
|
struct snd_ctl_elem_id id;
|
|
|
|
unsigned int idx;
|
|
|
|
unsigned int count;
|
2018-11-22 21:22:40 +07:00
|
|
|
struct snd_kcontrol *old;
|
|
|
|
int err;
|
2018-11-22 20:36:17 +07:00
|
|
|
|
|
|
|
id = kcontrol->id;
|
|
|
|
if (id.index > UINT_MAX - kcontrol->count)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2018-11-22 21:22:40 +07:00
|
|
|
old = snd_ctl_find_id(card, &id);
|
|
|
|
if (!old) {
|
|
|
|
if (mode == CTL_REPLACE)
|
|
|
|
return -EINVAL;
|
|
|
|
} else {
|
|
|
|
if (mode == CTL_ADD_EXCLUSIVE) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"control %i:%i:%i:%s:%i is already present\n",
|
|
|
|
id.iface, id.device, id.subdevice, id.name,
|
|
|
|
id.index);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = snd_ctl_remove(card, old);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2018-11-22 20:36:17 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (snd_ctl_find_hole(card, kcontrol->count) < 0)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
list_add_tail(&kcontrol->list, &card->controls);
|
|
|
|
card->controls_count += kcontrol->count;
|
|
|
|
kcontrol->id.numid = card->last_numid + 1;
|
|
|
|
card->last_numid += kcontrol->count;
|
|
|
|
|
|
|
|
id = kcontrol->id;
|
|
|
|
count = kcontrol->count;
|
|
|
|
for (idx = 0; idx < count; idx++, id.index++, id.numid++)
|
|
|
|
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-22 21:22:40 +07:00
|
|
|
static int snd_ctl_add_replace(struct snd_card *card,
|
|
|
|
struct snd_kcontrol *kcontrol,
|
|
|
|
enum snd_ctl_add_mode mode)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-03-21 22:07:13 +07:00
|
|
|
int err = -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-11-17 23:44:01 +07:00
|
|
|
if (! kcontrol)
|
2006-03-21 22:07:13 +07:00
|
|
|
return err;
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !kcontrol->info))
|
|
|
|
goto error;
|
2014-06-18 18:32:35 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
down_write(&card->controls_rwsem);
|
2018-11-22 21:22:40 +07:00
|
|
|
err = __snd_ctl_add_replace(card, kcontrol, mode);
|
2005-04-17 05:20:36 +07:00
|
|
|
up_write(&card->controls_rwsem);
|
2018-11-22 20:36:17 +07:00
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
2006-03-21 22:07:13 +07:00
|
|
|
|
|
|
|
error:
|
|
|
|
snd_ctl_free_one(kcontrol);
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2018-11-22 21:22:40 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_ctl_add - add the control instance to the card
|
|
|
|
* @card: the card instance
|
|
|
|
* @kcontrol: the control instance to add
|
|
|
|
*
|
|
|
|
* Adds the control instance created via snd_ctl_new() or
|
|
|
|
* snd_ctl_new1() to the given card. Assigns also an unique
|
|
|
|
* numid used for fast search.
|
|
|
|
*
|
|
|
|
* It frees automatically the control which cannot be added.
|
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
|
|
|
|
{
|
|
|
|
return snd_ctl_add_replace(card, kcontrol, CTL_ADD_EXCLUSIVE);
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_add);
|
|
|
|
|
2011-03-16 19:16:39 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_replace - replace the control instance of the card
|
|
|
|
* @card: the card instance
|
|
|
|
* @kcontrol: the control instance to replace
|
|
|
|
* @add_on_replace: add the control if not already added
|
|
|
|
*
|
|
|
|
* Replaces the given control. If the given control does not exist
|
|
|
|
* and the add_on_replace flag is set, the control is added. If the
|
|
|
|
* control exists, it is destroyed first.
|
|
|
|
*
|
|
|
|
* It frees automatically the control which cannot be added or replaced.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2011-03-16 19:16:39 +07:00
|
|
|
*/
|
|
|
|
int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol,
|
|
|
|
bool add_on_replace)
|
|
|
|
{
|
2018-11-22 21:22:40 +07:00
|
|
|
return snd_ctl_add_replace(card, kcontrol,
|
|
|
|
add_on_replace ? CTL_ADD_ON_REPLACE : CTL_REPLACE);
|
2011-03-16 19:16:39 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(snd_ctl_replace);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_remove - remove the control from the card and release it
|
|
|
|
* @card: the card instance
|
|
|
|
* @kcontrol: the control instance to remove
|
|
|
|
*
|
|
|
|
* Removes the control from the card and then releases the instance.
|
|
|
|
* You don't need to call snd_ctl_free_one(). You must be in
|
|
|
|
* the write lock - down_write(&card->controls_rwsem).
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: 0 if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_id id;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int idx;
|
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !kcontrol))
|
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
list_del(&kcontrol->list);
|
|
|
|
card->controls_count -= kcontrol->count;
|
|
|
|
id = kcontrol->id;
|
|
|
|
for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
|
|
|
|
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id);
|
|
|
|
snd_ctl_free_one(kcontrol);
|
|
|
|
return 0;
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_remove);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_remove_id - remove the control of the given id and release it
|
|
|
|
* @card: the card instance
|
|
|
|
* @id: the control id to remove
|
|
|
|
*
|
|
|
|
* Finds the control instance with the given id, removes it from the
|
|
|
|
* card list and releases it.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: 0 if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
int snd_ctl_remove_id(struct snd_card *card, struct snd_ctl_elem_id *id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
kctl = snd_ctl_find_id(card, id);
|
|
|
|
if (kctl == NULL) {
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
ret = snd_ctl_remove(card, kctl);
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
return ret;
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_remove_id);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
2009-08-17 17:27:22 +07:00
|
|
|
* snd_ctl_remove_user_ctl - remove and release the unlocked user control
|
2005-04-17 05:20:36 +07:00
|
|
|
* @file: active control handle
|
|
|
|
* @id: the control id to remove
|
|
|
|
*
|
|
|
|
* Finds the control instance with the given id, removes it from the
|
|
|
|
* card list and releases it.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: 0 if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2009-08-17 17:27:22 +07:00
|
|
|
static int snd_ctl_remove_user_ctl(struct snd_ctl_file * file,
|
|
|
|
struct snd_ctl_elem_id *id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card = file->card;
|
|
|
|
struct snd_kcontrol *kctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
int idx, ret;
|
|
|
|
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
kctl = snd_ctl_find_id(card, id);
|
|
|
|
if (kctl == NULL) {
|
2009-08-17 17:26:34 +07:00
|
|
|
ret = -ENOENT;
|
|
|
|
goto error;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2009-08-17 17:28:09 +07:00
|
|
|
if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_USER)) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
for (idx = 0; idx < kctl->count; idx++)
|
|
|
|
if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) {
|
2009-08-17 17:26:34 +07:00
|
|
|
ret = -EBUSY;
|
|
|
|
goto error;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
ret = snd_ctl_remove(card, kctl);
|
2009-08-17 17:27:22 +07:00
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
card->user_ctl_count--;
|
2009-08-17 17:26:34 +07:00
|
|
|
error:
|
2005-04-17 05:20:36 +07:00
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-08-29 21:09:01 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_activate_id - activate/inactivate the control of the given id
|
|
|
|
* @card: the card instance
|
|
|
|
* @id: the control id to activate/inactivate
|
|
|
|
* @active: non-zero to activate
|
|
|
|
*
|
|
|
|
* Finds the control instance with the given id, and activate or
|
|
|
|
* inactivate the control together with notification, if changed.
|
2015-04-11 15:41:02 +07:00
|
|
|
* The given ID data is filled with full information.
|
2008-08-29 21:09:01 +07:00
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: 0 if unchanged, 1 if changed, or a negative error code on failure.
|
2008-08-29 21:09:01 +07:00
|
|
|
*/
|
|
|
|
int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,
|
|
|
|
int active)
|
|
|
|
{
|
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
struct snd_kcontrol_volatile *vd;
|
|
|
|
unsigned int index_offset;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
kctl = snd_ctl_find_id(card, id);
|
|
|
|
if (kctl == NULL) {
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto unlock;
|
|
|
|
}
|
2014-11-07 20:12:34 +07:00
|
|
|
index_offset = snd_ctl_get_ioff(kctl, id);
|
2008-08-29 21:09:01 +07:00
|
|
|
vd = &kctl->vd[index_offset];
|
|
|
|
ret = 0;
|
|
|
|
if (active) {
|
|
|
|
if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE))
|
|
|
|
goto unlock;
|
|
|
|
vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
|
|
|
|
} else {
|
|
|
|
if (vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)
|
|
|
|
goto unlock;
|
|
|
|
vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
|
|
|
|
}
|
2015-04-11 15:41:02 +07:00
|
|
|
snd_ctl_build_ioff(id, kctl, index_offset);
|
2008-08-29 21:09:01 +07:00
|
|
|
ret = 1;
|
|
|
|
unlock:
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
if (ret > 0)
|
|
|
|
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, id);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_ctl_activate_id);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_rename_id - replace the id of a control on the card
|
|
|
|
* @card: the card instance
|
|
|
|
* @src_id: the old id
|
|
|
|
* @dst_id: the new id
|
|
|
|
*
|
|
|
|
* Finds the control with the old id from the card, and replaces the
|
|
|
|
* id with the new one.
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id,
|
|
|
|
struct snd_ctl_elem_id *dst_id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
kctl = snd_ctl_find_id(card, src_id);
|
|
|
|
if (kctl == NULL) {
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
kctl->id = *dst_id;
|
|
|
|
kctl->id.numid = card->last_numid + 1;
|
|
|
|
card->last_numid += kctl->count;
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
return 0;
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_rename_id);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_find_numid - find the control instance with the given number-id
|
|
|
|
* @card: the card instance
|
|
|
|
* @numid: the number-id to search
|
|
|
|
*
|
|
|
|
* Finds the control instance with the given number-id from the card.
|
|
|
|
*
|
|
|
|
* The caller must down card->controls_rwsem before calling this function
|
|
|
|
* (if the race condition can happen).
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: The pointer of the instance if found, or %NULL if not.
|
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !numid))
|
|
|
|
return NULL;
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(kctl, &card->controls, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
|
|
|
|
return kctl;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_find_numid);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_find_id - find the control instance with the given id
|
|
|
|
* @card: the card instance
|
|
|
|
* @id: the id to search
|
|
|
|
*
|
|
|
|
* Finds the control instance with the given id from the card.
|
|
|
|
*
|
|
|
|
* The caller must down card->controls_rwsem before calling this function
|
|
|
|
* (if the race condition can happen).
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: The pointer of the instance if found, or %NULL if not.
|
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,
|
|
|
|
struct snd_ctl_elem_id *id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !id))
|
|
|
|
return NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (id->numid != 0)
|
|
|
|
return snd_ctl_find_numid(card, id->numid);
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(kctl, &card->controls, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (kctl->id.iface != id->iface)
|
|
|
|
continue;
|
|
|
|
if (kctl->id.device != id->device)
|
|
|
|
continue;
|
|
|
|
if (kctl->id.subdevice != id->subdevice)
|
|
|
|
continue;
|
|
|
|
if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))
|
|
|
|
continue;
|
|
|
|
if (kctl->id.index > id->index)
|
|
|
|
continue;
|
|
|
|
if (kctl->id.index + kctl->count <= id->index)
|
|
|
|
continue;
|
|
|
|
return kctl;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_find_id);
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int cmd, void __user *arg)
|
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_card_info *info;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-09-09 19:20:23 +07:00
|
|
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (! info)
|
|
|
|
return -ENOMEM;
|
|
|
|
down_read(&snd_ioctl_rwsem);
|
|
|
|
info->card = card->number;
|
|
|
|
strlcpy(info->id, card->id, sizeof(info->id));
|
|
|
|
strlcpy(info->driver, card->driver, sizeof(info->driver));
|
|
|
|
strlcpy(info->name, card->shortname, sizeof(info->name));
|
|
|
|
strlcpy(info->longname, card->longname, sizeof(info->longname));
|
|
|
|
strlcpy(info->mixername, card->mixername, sizeof(info->mixername));
|
|
|
|
strlcpy(info->components, card->components, sizeof(info->components));
|
|
|
|
up_read(&snd_ioctl_rwsem);
|
2005-11-17 19:53:23 +07:00
|
|
|
if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) {
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(info);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
kfree(info);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_list(struct snd_card *card,
|
|
|
|
struct snd_ctl_elem_list __user *_list)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_list list;
|
|
|
|
struct snd_kcontrol *kctl;
|
2017-05-22 22:43:04 +07:00
|
|
|
struct snd_ctl_elem_id id;
|
2011-05-26 03:43:27 +07:00
|
|
|
unsigned int offset, space, jidx;
|
2017-05-22 22:43:04 +07:00
|
|
|
int err = 0;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (copy_from_user(&list, _list, sizeof(list)))
|
|
|
|
return -EFAULT;
|
|
|
|
offset = list.offset;
|
|
|
|
space = list.space;
|
2017-05-24 08:04:30 +07:00
|
|
|
|
2017-05-22 22:43:04 +07:00
|
|
|
down_read(&card->controls_rwsem);
|
|
|
|
list.count = card->controls_count;
|
|
|
|
list.used = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (space > 0) {
|
2017-05-22 22:43:04 +07:00
|
|
|
list_for_each_entry(kctl, &card->controls, list) {
|
|
|
|
if (offset >= kctl->count) {
|
|
|
|
offset -= kctl->count;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (jidx = offset; jidx < kctl->count; jidx++) {
|
|
|
|
snd_ctl_build_ioff(&id, kctl, jidx);
|
|
|
|
if (copy_to_user(list.pids + list.used, &id,
|
|
|
|
sizeof(id))) {
|
|
|
|
err = -EFAULT;
|
|
|
|
goto out;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
list.used++;
|
2017-05-22 22:43:04 +07:00
|
|
|
if (!--space)
|
|
|
|
goto out;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
offset = 0;
|
|
|
|
}
|
|
|
|
}
|
2017-05-22 22:43:04 +07:00
|
|
|
out:
|
|
|
|
up_read(&card->controls_rwsem);
|
|
|
|
if (!err && copy_to_user(_list, &list, sizeof(list)))
|
|
|
|
err = -EFAULT;
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
/* Check whether the given kctl info is valid */
|
|
|
|
static int snd_ctl_check_elem_info(struct snd_card *card,
|
|
|
|
const struct snd_ctl_elem_info *info)
|
|
|
|
{
|
|
|
|
static const unsigned int max_value_counts[] = {
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128,
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_INTEGER] = 128,
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128,
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_BYTES] = 512,
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_IEC958] = 1,
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
|
|
|
|
info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) {
|
|
|
|
if (card)
|
|
|
|
dev_err(card->dev,
|
|
|
|
"control %i:%i:%i:%s:%i: invalid type %d\n",
|
|
|
|
info->id.iface, info->id.device,
|
|
|
|
info->id.subdevice, info->id.name,
|
|
|
|
info->id.index, info->type);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED &&
|
|
|
|
info->value.enumerated.items == 0) {
|
|
|
|
if (card)
|
|
|
|
dev_err(card->dev,
|
|
|
|
"control %i:%i:%i:%s:%i: zero enum items\n",
|
|
|
|
info->id.iface, info->id.device,
|
|
|
|
info->id.subdevice, info->id.name,
|
|
|
|
info->id.index);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if (info->count > max_value_counts[info->type]) {
|
|
|
|
if (card)
|
|
|
|
dev_err(card->dev,
|
|
|
|
"control %i:%i:%i:%s:%i: invalid count %d\n",
|
|
|
|
info->id.iface, info->id.device,
|
|
|
|
info->id.subdevice, info->id.name,
|
|
|
|
info->id.index, info->count);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The capacity of struct snd_ctl_elem_value.value.*/
|
|
|
|
static const unsigned int value_sizes[] = {
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long),
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long),
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int),
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char),
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958),
|
|
|
|
[SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long),
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_SND_CTL_VALIDATION
|
|
|
|
/* fill the remaining snd_ctl_elem_value data with the given pattern */
|
|
|
|
static void fill_remaining_elem_value(struct snd_ctl_elem_value *control,
|
|
|
|
struct snd_ctl_elem_info *info,
|
|
|
|
u32 pattern)
|
|
|
|
{
|
|
|
|
size_t offset = value_sizes[info->type] * info->count;
|
|
|
|
|
|
|
|
offset = (offset + sizeof(u32) - 1) / sizeof(u32);
|
|
|
|
memset32((u32 *)control->value.bytes.data + offset, pattern,
|
|
|
|
sizeof(control->value) / sizeof(u32) - offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check whether the given integer ctl value is valid */
|
|
|
|
static int sanity_check_int_value(struct snd_card *card,
|
|
|
|
const struct snd_ctl_elem_value *control,
|
|
|
|
const struct snd_ctl_elem_info *info,
|
|
|
|
int i)
|
|
|
|
{
|
|
|
|
long long lval, lmin, lmax, lstep;
|
|
|
|
u64 rem;
|
|
|
|
|
|
|
|
switch (info->type) {
|
|
|
|
default:
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
|
|
|
lval = control->value.integer.value[i];
|
|
|
|
lmin = 0;
|
|
|
|
lmax = 1;
|
|
|
|
lstep = 0;
|
|
|
|
break;
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
|
|
|
lval = control->value.integer.value[i];
|
|
|
|
lmin = info->value.integer.min;
|
|
|
|
lmax = info->value.integer.max;
|
|
|
|
lstep = info->value.integer.step;
|
|
|
|
break;
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
|
|
|
|
lval = control->value.integer64.value[i];
|
|
|
|
lmin = info->value.integer64.min;
|
|
|
|
lmax = info->value.integer64.max;
|
|
|
|
lstep = info->value.integer64.step;
|
|
|
|
break;
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
|
|
|
|
lval = control->value.enumerated.item[i];
|
|
|
|
lmin = 0;
|
|
|
|
lmax = info->value.enumerated.items - 1;
|
|
|
|
lstep = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lval < lmin || lval > lmax) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"control %i:%i:%i:%s:%i: value out of range %lld (%lld/%lld) at count %i\n",
|
|
|
|
control->id.iface, control->id.device,
|
|
|
|
control->id.subdevice, control->id.name,
|
|
|
|
control->id.index, lval, lmin, lmax, i);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if (lstep) {
|
|
|
|
div64_u64_rem(lval, lstep, &rem);
|
|
|
|
if (rem) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"control %i:%i:%i:%s:%i: unaligned value %lld (step %lld) at count %i\n",
|
|
|
|
control->id.iface, control->id.device,
|
|
|
|
control->id.subdevice, control->id.name,
|
|
|
|
control->id.index, lval, lstep, i);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* perform sanity checks to the given snd_ctl_elem_value object */
|
|
|
|
static int sanity_check_elem_value(struct snd_card *card,
|
|
|
|
const struct snd_ctl_elem_value *control,
|
|
|
|
const struct snd_ctl_elem_info *info,
|
|
|
|
u32 pattern)
|
|
|
|
{
|
|
|
|
size_t offset;
|
2020-01-08 12:37:06 +07:00
|
|
|
int i, ret = 0;
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
u32 *p;
|
|
|
|
|
|
|
|
switch (info->type) {
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
|
|
|
|
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
|
|
|
|
for (i = 0; i < info->count; i++) {
|
|
|
|
ret = sanity_check_int_value(card, control, info, i);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check whether the remaining area kept untouched */
|
|
|
|
offset = value_sizes[info->type] * info->count;
|
|
|
|
offset = (offset + sizeof(u32) - 1) / sizeof(u32);
|
|
|
|
p = (u32 *)control->value.bytes.data + offset;
|
|
|
|
for (; offset < sizeof(control->value) / sizeof(u32); offset++, p++) {
|
|
|
|
if (*p != pattern) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
*p = 0; /* clear the checked area */
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void fill_remaining_elem_value(struct snd_ctl_elem_value *control,
|
|
|
|
struct snd_ctl_elem_info *info,
|
|
|
|
u32 pattern)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int sanity_check_elem_value(struct snd_card *card,
|
|
|
|
struct snd_ctl_elem_value *control,
|
|
|
|
struct snd_ctl_elem_info *info,
|
|
|
|
u32 pattern)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int __snd_ctl_elem_info(struct snd_card *card,
|
|
|
|
struct snd_kcontrol *kctl,
|
|
|
|
struct snd_ctl_elem_info *info,
|
|
|
|
struct snd_ctl_file *ctl)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol_volatile *vd;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int index_offset;
|
|
|
|
int result;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
#ifdef CONFIG_SND_DEBUG
|
|
|
|
info->access = 0;
|
|
|
|
#endif
|
|
|
|
result = kctl->info(kctl, info);
|
|
|
|
if (result >= 0) {
|
2008-08-08 22:09:09 +07:00
|
|
|
snd_BUG_ON(info->access);
|
2005-04-17 05:20:36 +07:00
|
|
|
index_offset = snd_ctl_get_ioff(kctl, &info->id);
|
|
|
|
vd = &kctl->vd[index_offset];
|
|
|
|
snd_ctl_build_ioff(&info->id, kctl, index_offset);
|
|
|
|
info->access = vd->access;
|
|
|
|
if (vd->owner) {
|
|
|
|
info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK;
|
|
|
|
if (vd->owner == ctl)
|
|
|
|
info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER;
|
2009-11-02 15:35:44 +07:00
|
|
|
info->owner = pid_vnr(vd->owner->pid);
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
|
|
|
info->owner = -1;
|
|
|
|
}
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
if (!snd_ctl_skip_validation(info) &&
|
|
|
|
snd_ctl_check_elem_info(card, info) < 0)
|
|
|
|
result = -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ctl_elem_info(struct snd_ctl_file *ctl,
|
|
|
|
struct snd_ctl_elem_info *info)
|
|
|
|
{
|
|
|
|
struct snd_card *card = ctl->card;
|
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
int result;
|
|
|
|
|
|
|
|
down_read(&card->controls_rwsem);
|
|
|
|
kctl = snd_ctl_find_id(card, &info->id);
|
|
|
|
if (kctl == NULL)
|
|
|
|
result = -ENOENT;
|
|
|
|
else
|
|
|
|
result = __snd_ctl_elem_info(card, kctl, info, ctl);
|
2005-04-17 05:20:36 +07:00
|
|
|
up_read(&card->controls_rwsem);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl,
|
|
|
|
struct snd_ctl_elem_info __user *_info)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_info info;
|
2005-04-17 05:20:36 +07:00
|
|
|
int result;
|
|
|
|
|
|
|
|
if (copy_from_user(&info, _info, sizeof(info)))
|
|
|
|
return -EFAULT;
|
2006-03-27 17:38:07 +07:00
|
|
|
result = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0);
|
2017-08-30 21:13:25 +07:00
|
|
|
if (result < 0)
|
|
|
|
return result;
|
|
|
|
result = snd_ctl_elem_info(ctl, &info);
|
|
|
|
if (result < 0)
|
|
|
|
return result;
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
/* drop internal access flags */
|
|
|
|
info.access &= ~SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK;
|
2017-08-30 21:13:25 +07:00
|
|
|
if (copy_to_user(_info, &info, sizeof(info)))
|
|
|
|
return -EFAULT;
|
2005-04-17 05:20:36 +07:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2008-06-12 23:17:26 +07:00
|
|
|
static int snd_ctl_elem_read(struct snd_card *card,
|
|
|
|
struct snd_ctl_elem_value *control)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
struct snd_kcontrol_volatile *vd;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int index_offset;
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
struct snd_ctl_elem_info info;
|
|
|
|
const u32 pattern = 0xdeadbeef;
|
|
|
|
int ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
kctl = snd_ctl_find_id(card, &control->id);
|
2017-08-20 11:49:07 +07:00
|
|
|
if (kctl == NULL)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
index_offset = snd_ctl_get_ioff(kctl, &control->id);
|
|
|
|
vd = &kctl->vd[index_offset];
|
2018-02-28 00:01:18 +07:00
|
|
|
if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_READ) || kctl->get == NULL)
|
2017-08-20 11:49:07 +07:00
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
snd_ctl_build_ioff(&control->id, kctl, index_offset);
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
|
|
|
|
#ifdef CONFIG_SND_CTL_VALIDATION
|
|
|
|
/* info is needed only for validation */
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
info.id = control->id;
|
|
|
|
ret = __snd_ctl_elem_info(card, kctl, &info, NULL);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!snd_ctl_skip_validation(&info))
|
|
|
|
fill_remaining_elem_value(control, &info, pattern);
|
|
|
|
ret = kctl->get(kctl, control);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
if (!snd_ctl_skip_validation(&info) &&
|
|
|
|
sanity_check_elem_value(card, control, &info, pattern) < 0) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"control %i:%i:%i:%s:%i: access overflow\n",
|
|
|
|
control->id.iface, control->id.device,
|
|
|
|
control->id.subdevice, control->id.name,
|
|
|
|
control->id.index);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return ret;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_read_user(struct snd_card *card,
|
|
|
|
struct snd_ctl_elem_value __user *_control)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_value *control;
|
2005-04-17 05:20:36 +07:00
|
|
|
int result;
|
2009-04-10 08:43:08 +07:00
|
|
|
|
|
|
|
control = memdup_user(_control, sizeof(*control));
|
|
|
|
if (IS_ERR(control))
|
|
|
|
return PTR_ERR(control);
|
|
|
|
|
2006-03-27 17:38:07 +07:00
|
|
|
result = snd_power_wait(card, SNDRV_CTL_POWER_D0);
|
2017-08-30 21:13:25 +07:00
|
|
|
if (result < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
down_read(&card->controls_rwsem);
|
|
|
|
result = snd_ctl_elem_read(card, control);
|
|
|
|
up_read(&card->controls_rwsem);
|
|
|
|
if (result < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (copy_to_user(_control, control, sizeof(*control)))
|
|
|
|
result = -EFAULT;
|
|
|
|
error:
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(control);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2008-06-12 23:17:26 +07:00
|
|
|
static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
|
|
|
|
struct snd_ctl_elem_value *control)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
struct snd_kcontrol_volatile *vd;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int index_offset;
|
2008-01-08 23:57:26 +07:00
|
|
|
int result;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
kctl = snd_ctl_find_id(card, &control->id);
|
2017-08-20 11:49:07 +07:00
|
|
|
if (kctl == NULL)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
index_offset = snd_ctl_get_ioff(kctl, &control->id);
|
|
|
|
vd = &kctl->vd[index_offset];
|
|
|
|
if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || kctl->put == NULL ||
|
|
|
|
(file && vd->owner && vd->owner != file)) {
|
|
|
|
return -EPERM;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2017-08-20 11:49:07 +07:00
|
|
|
|
|
|
|
snd_ctl_build_ioff(&control->id, kctl, index_offset);
|
|
|
|
result = kctl->put(kctl, control);
|
|
|
|
if (result < 0)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
if (result > 0) {
|
|
|
|
struct snd_ctl_elem_id id = control->id;
|
|
|
|
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_write_user(struct snd_ctl_file *file,
|
|
|
|
struct snd_ctl_elem_value __user *_control)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_value *control;
|
2006-03-13 20:11:11 +07:00
|
|
|
struct snd_card *card;
|
2005-04-17 05:20:36 +07:00
|
|
|
int result;
|
|
|
|
|
2009-04-10 08:43:08 +07:00
|
|
|
control = memdup_user(_control, sizeof(*control));
|
|
|
|
if (IS_ERR(control))
|
|
|
|
return PTR_ERR(control);
|
|
|
|
|
2006-03-13 20:11:11 +07:00
|
|
|
card = file->card;
|
2006-03-27 17:38:07 +07:00
|
|
|
result = snd_power_wait(card, SNDRV_CTL_POWER_D0);
|
2017-08-30 21:13:25 +07:00
|
|
|
if (result < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
result = snd_ctl_elem_write(card, file, control);
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
if (result < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (copy_to_user(_control, control, sizeof(*control)))
|
|
|
|
result = -EFAULT;
|
|
|
|
error:
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(control);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_lock(struct snd_ctl_file *file,
|
|
|
|
struct snd_ctl_elem_id __user *_id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card = file->card;
|
|
|
|
struct snd_ctl_elem_id id;
|
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
struct snd_kcontrol_volatile *vd;
|
2005-04-17 05:20:36 +07:00
|
|
|
int result;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (copy_from_user(&id, _id, sizeof(id)))
|
|
|
|
return -EFAULT;
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
kctl = snd_ctl_find_id(card, &id);
|
|
|
|
if (kctl == NULL) {
|
|
|
|
result = -ENOENT;
|
|
|
|
} else {
|
|
|
|
vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
|
|
|
|
if (vd->owner != NULL)
|
|
|
|
result = -EBUSY;
|
|
|
|
else {
|
|
|
|
vd->owner = file;
|
|
|
|
result = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_unlock(struct snd_ctl_file *file,
|
|
|
|
struct snd_ctl_elem_id __user *_id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card = file->card;
|
|
|
|
struct snd_ctl_elem_id id;
|
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
struct snd_kcontrol_volatile *vd;
|
2005-04-17 05:20:36 +07:00
|
|
|
int result;
|
2018-02-28 00:29:54 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (copy_from_user(&id, _id, sizeof(id)))
|
|
|
|
return -EFAULT;
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
kctl = snd_ctl_find_id(card, &id);
|
|
|
|
if (kctl == NULL) {
|
|
|
|
result = -ENOENT;
|
|
|
|
} else {
|
|
|
|
vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
|
|
|
|
if (vd->owner == NULL)
|
|
|
|
result = -EINVAL;
|
|
|
|
else if (vd->owner != file)
|
|
|
|
result = -EPERM;
|
|
|
|
else {
|
|
|
|
vd->owner = NULL;
|
|
|
|
result = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
up_write(&card->controls_rwsem);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct user_element {
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_info info;
|
2014-06-18 18:32:31 +07:00
|
|
|
struct snd_card *card;
|
2015-04-12 08:12:25 +07:00
|
|
|
char *elem_data; /* element data */
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned long elem_data_size; /* size of element data in bytes */
|
2006-07-05 22:34:51 +07:00
|
|
|
void *tlv_data; /* TLV data */
|
|
|
|
unsigned long tlv_data_size; /* TLV data size */
|
2005-04-17 05:20:36 +07:00
|
|
|
void *priv_data; /* private data (like strings for enumerated type) */
|
|
|
|
};
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct user_element *ue = kcontrol->private_data;
|
2015-04-11 15:41:03 +07:00
|
|
|
unsigned int offset;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2015-04-11 15:41:03 +07:00
|
|
|
offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);
|
2005-04-17 05:20:36 +07:00
|
|
|
*uinfo = ue->info;
|
2015-04-11 15:41:03 +07:00
|
|
|
snd_ctl_build_ioff(&uinfo->id, kcontrol, offset);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-10-08 03:38:59 +07:00
|
|
|
static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct user_element *ue = kcontrol->private_data;
|
|
|
|
const char *names;
|
|
|
|
unsigned int item;
|
2015-04-11 15:41:03 +07:00
|
|
|
unsigned int offset;
|
2011-10-08 03:38:59 +07:00
|
|
|
|
|
|
|
item = uinfo->value.enumerated.item;
|
|
|
|
|
2015-04-11 15:41:03 +07:00
|
|
|
offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);
|
2011-10-08 03:38:59 +07:00
|
|
|
*uinfo = ue->info;
|
2015-04-11 15:41:03 +07:00
|
|
|
snd_ctl_build_ioff(&uinfo->id, kcontrol, offset);
|
2011-10-08 03:38:59 +07:00
|
|
|
|
|
|
|
item = min(item, uinfo->value.enumerated.items - 1);
|
|
|
|
uinfo->value.enumerated.item = item;
|
|
|
|
|
|
|
|
names = ue->priv_data;
|
|
|
|
for (; item > 0; --item)
|
|
|
|
names += strlen(names) + 1;
|
|
|
|
strcpy(uinfo->value.enumerated.name, names);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct user_element *ue = kcontrol->private_data;
|
2015-04-12 08:12:25 +07:00
|
|
|
unsigned int size = ue->elem_data_size;
|
|
|
|
char *src = ue->elem_data +
|
|
|
|
snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2015-04-12 08:12:25 +07:00
|
|
|
memcpy(&ucontrol->value, src, size);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int change;
|
|
|
|
struct user_element *ue = kcontrol->private_data;
|
2015-04-12 08:12:25 +07:00
|
|
|
unsigned int size = ue->elem_data_size;
|
|
|
|
char *dst = ue->elem_data +
|
|
|
|
snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size;
|
2014-06-18 18:32:31 +07:00
|
|
|
|
2015-04-12 08:12:25 +07:00
|
|
|
change = memcmp(&ucontrol->value, dst, size) != 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (change)
|
2015-04-12 08:12:25 +07:00
|
|
|
memcpy(dst, &ucontrol->value, size);
|
2005-04-17 05:20:36 +07:00
|
|
|
return change;
|
|
|
|
}
|
|
|
|
|
2017-08-03 18:20:44 +07:00
|
|
|
static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf,
|
|
|
|
unsigned int size)
|
2006-07-05 22:34:51 +07:00
|
|
|
{
|
2017-08-03 18:20:44 +07:00
|
|
|
struct user_element *ue = kctl->private_data;
|
|
|
|
unsigned int *container;
|
2017-08-24 08:46:15 +07:00
|
|
|
struct snd_ctl_elem_id id;
|
2017-08-24 08:46:16 +07:00
|
|
|
unsigned int mask = 0;
|
2017-08-24 08:46:15 +07:00
|
|
|
int i;
|
2017-08-03 18:20:44 +07:00
|
|
|
int change;
|
2006-07-05 22:34:51 +07:00
|
|
|
|
2017-08-03 18:20:44 +07:00
|
|
|
if (size > 1024 * 128) /* sane value */
|
|
|
|
return -EINVAL;
|
2017-08-03 18:20:42 +07:00
|
|
|
|
2018-01-08 01:09:15 +07:00
|
|
|
container = vmemdup_user(buf, size);
|
2017-08-03 18:20:44 +07:00
|
|
|
if (IS_ERR(container))
|
|
|
|
return PTR_ERR(container);
|
2009-04-10 08:43:08 +07:00
|
|
|
|
2017-08-03 18:20:44 +07:00
|
|
|
change = ue->tlv_data_size != size;
|
|
|
|
if (!change)
|
2017-08-22 20:44:45 +07:00
|
|
|
change = memcmp(ue->tlv_data, container, size) != 0;
|
2017-08-03 18:20:44 +07:00
|
|
|
if (!change) {
|
2018-01-08 01:09:15 +07:00
|
|
|
kvfree(container);
|
2017-08-03 18:20:44 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2017-08-03 18:20:42 +07:00
|
|
|
|
2017-08-24 08:46:16 +07:00
|
|
|
if (ue->tlv_data == NULL) {
|
|
|
|
/* Now TLV data is available. */
|
|
|
|
for (i = 0; i < kctl->count; ++i)
|
|
|
|
kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
|
|
|
|
mask = SNDRV_CTL_EVENT_MASK_INFO;
|
|
|
|
}
|
|
|
|
|
2018-01-08 01:09:15 +07:00
|
|
|
kvfree(ue->tlv_data);
|
2017-08-03 18:20:44 +07:00
|
|
|
ue->tlv_data = container;
|
|
|
|
ue->tlv_data_size = size;
|
2014-06-18 18:32:31 +07:00
|
|
|
|
2017-08-24 08:46:16 +07:00
|
|
|
mask |= SNDRV_CTL_EVENT_MASK_TLV;
|
2017-08-24 08:46:15 +07:00
|
|
|
for (i = 0; i < kctl->count; ++i) {
|
|
|
|
snd_ctl_build_ioff(&id, kctl, i);
|
2017-08-24 08:46:16 +07:00
|
|
|
snd_ctl_notify(ue->card, mask, &id);
|
2017-08-24 08:46:15 +07:00
|
|
|
}
|
ALSA: control: delegate TLV eventing to each driver
In a design of ALSA control core, a set of elements is represented by
'struct snd_kcontrol' to share common attributes. The set of elements
shares TLV (Type-Length-Value) data, too.
On the other hand, in ALSA control interface/protocol for applications,
a TLV operation is committed to an element. Totally, the operation can
have sub-effect to the other elements in the set. For example, TLV_WRITE
operation is expected to change TLV data, which returns to applications.
Applications attempt to change the TLV data per element, but in the above
design, they can effect to elements in the same set.
As a default, ALSA control core has no implementation except for TLV_READ
operation. Thus, the above design looks to have no issue. However, in
kernel APIs of ALSA control component, developers can program a handler
for any request of the TLV operation. Therefore, for elements in a set
which has the handler, applications can commit TLV_WRITE and TLV_COMMAND
requests.
For the above scenario, ALSA control core assist notification. When the
handler returns positive value, the core queueing an event for a requested
element. However, this includes design defects that the event is not
queued for the other element in a set. Actually, developers can program
the handlers to keep per-element TLV data, but it depends on each driver.
As of v4.13-rc6, there's no driver in tree to utilize the notification,
except for user-defined element set. This commit delegates the notification
into each driver to prevent developers from the design defects.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-08-24 08:46:14 +07:00
|
|
|
|
2017-08-03 18:20:44 +07:00
|
|
|
return change;
|
|
|
|
}
|
2017-08-03 18:20:42 +07:00
|
|
|
|
2017-08-03 18:20:44 +07:00
|
|
|
static int read_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf,
|
|
|
|
unsigned int size)
|
|
|
|
{
|
|
|
|
struct user_element *ue = kctl->private_data;
|
|
|
|
|
|
|
|
if (ue->tlv_data_size == 0 || ue->tlv_data == NULL)
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
if (size < ue->tlv_data_size)
|
|
|
|
return -ENOSPC;
|
|
|
|
|
|
|
|
if (copy_to_user(buf, ue->tlv_data, ue->tlv_data_size))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kctl, int op_flag,
|
|
|
|
unsigned int size, unsigned int __user *buf)
|
|
|
|
{
|
|
|
|
if (op_flag == SNDRV_CTL_TLV_OP_WRITE)
|
|
|
|
return replace_user_tlv(kctl, buf, size);
|
|
|
|
else
|
|
|
|
return read_user_tlv(kctl, buf, size);
|
2006-07-05 22:34:51 +07:00
|
|
|
}
|
|
|
|
|
2011-10-08 03:38:59 +07:00
|
|
|
static int snd_ctl_elem_init_enum_names(struct user_element *ue)
|
|
|
|
{
|
|
|
|
char *names, *p;
|
|
|
|
size_t buf_len, name_len;
|
|
|
|
unsigned int i;
|
2011-11-06 04:51:54 +07:00
|
|
|
const uintptr_t user_ptrval = ue->info.value.enumerated.names_ptr;
|
2011-10-08 03:38:59 +07:00
|
|
|
|
|
|
|
if (ue->info.value.enumerated.names_length > 64 * 1024)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2018-01-08 01:11:03 +07:00
|
|
|
names = vmemdup_user((const void __user *)user_ptrval,
|
2011-10-08 03:38:59 +07:00
|
|
|
ue->info.value.enumerated.names_length);
|
|
|
|
if (IS_ERR(names))
|
|
|
|
return PTR_ERR(names);
|
|
|
|
|
|
|
|
/* check that there are enough valid names */
|
|
|
|
buf_len = ue->info.value.enumerated.names_length;
|
|
|
|
p = names;
|
|
|
|
for (i = 0; i < ue->info.value.enumerated.items; ++i) {
|
|
|
|
name_len = strnlen(p, buf_len);
|
|
|
|
if (name_len == 0 || name_len >= 64 || name_len == buf_len) {
|
2018-01-08 01:11:03 +07:00
|
|
|
kvfree(names);
|
2011-10-08 03:38:59 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
p += name_len + 1;
|
|
|
|
buf_len -= name_len + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ue->priv_data = names;
|
|
|
|
ue->info.value.enumerated.names_ptr = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-07-05 22:34:51 +07:00
|
|
|
struct user_element *ue = kcontrol->private_data;
|
2011-10-08 03:38:59 +07:00
|
|
|
|
2018-01-08 01:09:15 +07:00
|
|
|
kvfree(ue->tlv_data);
|
2018-01-08 01:11:03 +07:00
|
|
|
kvfree(ue->priv_data);
|
2006-07-05 22:34:51 +07:00
|
|
|
kfree(ue);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
|
|
|
struct snd_ctl_elem_info *info, int replace)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card = file->card;
|
2015-03-10 20:13:31 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
|
|
|
unsigned int count;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int access;
|
|
|
|
long private_size;
|
|
|
|
struct user_element *ue;
|
2015-04-11 15:41:04 +07:00
|
|
|
unsigned int offset;
|
2015-03-10 20:13:31 +07:00
|
|
|
int err;
|
2014-06-18 18:32:32 +07:00
|
|
|
|
2015-03-12 00:12:49 +07:00
|
|
|
if (!*info->id.name)
|
|
|
|
return -EINVAL;
|
|
|
|
if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name))
|
|
|
|
return -EINVAL;
|
2014-06-18 18:32:32 +07:00
|
|
|
|
2015-03-10 20:13:31 +07:00
|
|
|
/* Delete a control to replace them if needed. */
|
2014-06-18 18:32:32 +07:00
|
|
|
if (replace) {
|
2015-03-10 20:13:31 +07:00
|
|
|
info->id.numid = 0;
|
2014-06-18 18:32:32 +07:00
|
|
|
err = snd_ctl_remove_user_ctl(file, &info->id);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-06-18 18:32:32 +07:00
|
|
|
|
2015-03-10 20:13:31 +07:00
|
|
|
/*
|
|
|
|
* The number of userspace controls are counted control by control,
|
|
|
|
* not element by element.
|
|
|
|
*/
|
|
|
|
if (card->user_ctl_count + 1 > MAX_USER_CONTROLS)
|
2014-06-18 18:32:32 +07:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2015-03-10 20:13:31 +07:00
|
|
|
/* Check the number of elements for this userspace control. */
|
|
|
|
count = info->owner;
|
|
|
|
if (count == 0)
|
|
|
|
count = 1;
|
|
|
|
|
|
|
|
/* Arrange access permissions if needed. */
|
|
|
|
access = info->access;
|
|
|
|
if (access == 0)
|
|
|
|
access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
|
|
|
access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_INACTIVE |
|
2017-08-24 08:46:16 +07:00
|
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_WRITE);
|
|
|
|
|
|
|
|
/* In initial state, nothing is available as TLV container. */
|
|
|
|
if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE)
|
2006-07-05 22:34:51 +07:00
|
|
|
access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
|
2015-03-10 20:13:31 +07:00
|
|
|
access |= SNDRV_CTL_ELEM_ACCESS_USER;
|
2015-03-10 20:13:30 +07:00
|
|
|
|
2015-03-10 20:13:31 +07:00
|
|
|
/*
|
|
|
|
* Check information and calculate the size of data specific to
|
|
|
|
* this userspace control.
|
|
|
|
*/
|
ALSA: control: Add verification for kctl accesses
The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.
The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.
When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary
Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count
The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.
When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.
The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.
Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.
Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-01-04 15:35:56 +07:00
|
|
|
/* pass NULL to card for suppressing error messages */
|
|
|
|
err = snd_ctl_check_elem_info(NULL, info);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
/* user-space control doesn't allow zero-size data */
|
|
|
|
if (info->count < 1)
|
2015-03-10 20:13:30 +07:00
|
|
|
return -EINVAL;
|
|
|
|
private_size = value_sizes[info->type] * info->count;
|
2015-03-10 20:13:31 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Keep memory object for this userspace control. After passing this
|
|
|
|
* code block, the instance should be freed by snd_ctl_free_one().
|
|
|
|
*
|
|
|
|
* Note that these elements in this control are locked.
|
|
|
|
*/
|
|
|
|
err = snd_ctl_new(&kctl, count, access, file);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2015-03-12 22:57:51 +07:00
|
|
|
memcpy(&kctl->id, &info->id, sizeof(kctl->id));
|
2015-04-12 08:12:25 +07:00
|
|
|
kctl->private_data = kzalloc(sizeof(struct user_element) + private_size * count,
|
2015-03-10 20:13:31 +07:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (kctl->private_data == NULL) {
|
|
|
|
kfree(kctl);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOMEM;
|
2015-03-10 20:13:31 +07:00
|
|
|
}
|
|
|
|
kctl->private_free = snd_ctl_elem_user_free;
|
|
|
|
|
|
|
|
/* Set private data for this userspace control. */
|
|
|
|
ue = (struct user_element *)kctl->private_data;
|
2014-06-18 18:32:31 +07:00
|
|
|
ue->card = card;
|
2005-04-17 05:20:36 +07:00
|
|
|
ue->info = *info;
|
2006-08-24 17:36:36 +07:00
|
|
|
ue->info.access = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
ue->elem_data = (char *)ue + sizeof(*ue);
|
|
|
|
ue->elem_data_size = private_size;
|
2011-10-08 03:38:59 +07:00
|
|
|
if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
|
|
|
|
err = snd_ctl_elem_init_enum_names(ue);
|
|
|
|
if (err < 0) {
|
2015-03-10 20:13:31 +07:00
|
|
|
snd_ctl_free_one(kctl);
|
2011-10-08 03:38:59 +07:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
2015-03-10 20:13:31 +07:00
|
|
|
|
|
|
|
/* Set callback functions. */
|
|
|
|
if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
|
|
|
|
kctl->info = snd_ctl_elem_user_enum_info;
|
|
|
|
else
|
|
|
|
kctl->info = snd_ctl_elem_user_info;
|
|
|
|
if (access & SNDRV_CTL_ELEM_ACCESS_READ)
|
|
|
|
kctl->get = snd_ctl_elem_user_get;
|
|
|
|
if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
|
|
|
|
kctl->put = snd_ctl_elem_user_put;
|
2017-08-24 08:46:16 +07:00
|
|
|
if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE)
|
2015-03-10 20:13:31 +07:00
|
|
|
kctl->tlv.c = snd_ctl_elem_user_tlv;
|
|
|
|
|
|
|
|
/* This function manage to free the instance on failure. */
|
2018-11-22 20:36:17 +07:00
|
|
|
down_write(&card->controls_rwsem);
|
2018-11-22 21:22:40 +07:00
|
|
|
err = __snd_ctl_add_replace(card, kctl, CTL_ADD_EXCLUSIVE);
|
2018-11-22 20:36:17 +07:00
|
|
|
if (err < 0) {
|
|
|
|
snd_ctl_free_one(kctl);
|
|
|
|
goto unlock;
|
|
|
|
}
|
2015-04-11 15:41:04 +07:00
|
|
|
offset = snd_ctl_get_ioff(kctl, &info->id);
|
|
|
|
snd_ctl_build_ioff(&info->id, kctl, offset);
|
|
|
|
/*
|
|
|
|
* Here we cannot fill any field for the number of elements added by
|
|
|
|
* this operation because there're no specific fields. The usage of
|
|
|
|
* 'owner' field for this purpose may cause any bugs to userspace
|
|
|
|
* applications because the field originally means PID of a process
|
|
|
|
* which locks the element.
|
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
card->user_ctl_count++;
|
|
|
|
|
2018-11-22 20:36:17 +07:00
|
|
|
unlock:
|
|
|
|
up_write(&card->controls_rwsem);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_add_user(struct snd_ctl_file *file,
|
|
|
|
struct snd_ctl_elem_info __user *_info, int replace)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_info info;
|
2015-04-11 15:41:04 +07:00
|
|
|
int err;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (copy_from_user(&info, _info, sizeof(info)))
|
|
|
|
return -EFAULT;
|
2015-04-11 15:41:04 +07:00
|
|
|
err = snd_ctl_elem_add(file, &info, replace);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
if (copy_to_user(_info, &info, sizeof(info))) {
|
|
|
|
snd_ctl_remove_user_ctl(file, &info.id);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_elem_remove(struct snd_ctl_file *file,
|
|
|
|
struct snd_ctl_elem_id __user *_id)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_elem_id id;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (copy_from_user(&id, _id, sizeof(id)))
|
|
|
|
return -EFAULT;
|
2009-08-17 17:27:22 +07:00
|
|
|
return snd_ctl_remove_user_ctl(file, &id);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int subscribe;
|
|
|
|
if (get_user(subscribe, ptr))
|
|
|
|
return -EFAULT;
|
|
|
|
if (subscribe < 0) {
|
|
|
|
subscribe = file->subscribed;
|
|
|
|
if (put_user(subscribe, ptr))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (subscribe) {
|
|
|
|
file->subscribed = 1;
|
|
|
|
return 0;
|
|
|
|
} else if (file->subscribed) {
|
|
|
|
snd_ctl_empty_read_queue(file);
|
|
|
|
file->subscribed = 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-08-03 18:20:43 +07:00
|
|
|
static int call_tlv_handler(struct snd_ctl_file *file, int op_flag,
|
|
|
|
struct snd_kcontrol *kctl,
|
|
|
|
struct snd_ctl_elem_id *id,
|
|
|
|
unsigned int __user *buf, unsigned int size)
|
|
|
|
{
|
|
|
|
static const struct {
|
|
|
|
int op;
|
|
|
|
int perm;
|
|
|
|
} pairs[] = {
|
|
|
|
{SNDRV_CTL_TLV_OP_READ, SNDRV_CTL_ELEM_ACCESS_TLV_READ},
|
|
|
|
{SNDRV_CTL_TLV_OP_WRITE, SNDRV_CTL_ELEM_ACCESS_TLV_WRITE},
|
|
|
|
{SNDRV_CTL_TLV_OP_CMD, SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND},
|
|
|
|
};
|
|
|
|
struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)];
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Check support of the request for this element. */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pairs); ++i) {
|
|
|
|
if (op_flag == pairs[i].op && (vd->access & pairs[i].perm))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(pairs))
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
if (kctl->tlv.c == NULL)
|
|
|
|
return -ENXIO;
|
|
|
|
|
2019-12-23 16:33:47 +07:00
|
|
|
/* Write and command operations are not allowed for locked element. */
|
|
|
|
if (op_flag != SNDRV_CTL_TLV_OP_READ &&
|
|
|
|
vd->owner != NULL && vd->owner != file)
|
2017-08-03 18:20:43 +07:00
|
|
|
return -EPERM;
|
|
|
|
|
ALSA: control: delegate TLV eventing to each driver
In a design of ALSA control core, a set of elements is represented by
'struct snd_kcontrol' to share common attributes. The set of elements
shares TLV (Type-Length-Value) data, too.
On the other hand, in ALSA control interface/protocol for applications,
a TLV operation is committed to an element. Totally, the operation can
have sub-effect to the other elements in the set. For example, TLV_WRITE
operation is expected to change TLV data, which returns to applications.
Applications attempt to change the TLV data per element, but in the above
design, they can effect to elements in the same set.
As a default, ALSA control core has no implementation except for TLV_READ
operation. Thus, the above design looks to have no issue. However, in
kernel APIs of ALSA control component, developers can program a handler
for any request of the TLV operation. Therefore, for elements in a set
which has the handler, applications can commit TLV_WRITE and TLV_COMMAND
requests.
For the above scenario, ALSA control core assist notification. When the
handler returns positive value, the core queueing an event for a requested
element. However, this includes design defects that the event is not
queued for the other element in a set. Actually, developers can program
the handlers to keep per-element TLV data, but it depends on each driver.
As of v4.13-rc6, there's no driver in tree to utilize the notification,
except for user-defined element set. This commit delegates the notification
into each driver to prevent developers from the design defects.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-08-24 08:46:14 +07:00
|
|
|
return kctl->tlv.c(kctl, op_flag, size, buf);
|
2017-08-03 18:20:43 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int read_tlv_buf(struct snd_kcontrol *kctl, struct snd_ctl_elem_id *id,
|
|
|
|
unsigned int __user *buf, unsigned int size)
|
|
|
|
{
|
|
|
|
struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)];
|
|
|
|
unsigned int len;
|
|
|
|
|
|
|
|
if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ))
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
if (kctl->tlv.p == NULL)
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
len = sizeof(unsigned int) * 2 + kctl->tlv.p[1];
|
|
|
|
if (size < len)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
if (copy_to_user(buf, kctl->tlv.p, len))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-07-05 22:34:51 +07:00
|
|
|
static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
|
2017-08-03 18:20:43 +07:00
|
|
|
struct snd_ctl_tlv __user *buf,
|
2006-07-05 22:34:51 +07:00
|
|
|
int op_flag)
|
2006-06-01 23:34:01 +07:00
|
|
|
{
|
2017-08-03 18:20:43 +07:00
|
|
|
struct snd_ctl_tlv header;
|
2018-04-23 20:01:44 +07:00
|
|
|
unsigned int __user *container;
|
2017-08-03 18:20:43 +07:00
|
|
|
unsigned int container_size;
|
2006-06-01 23:34:01 +07:00
|
|
|
struct snd_kcontrol *kctl;
|
2017-08-03 18:20:43 +07:00
|
|
|
struct snd_ctl_elem_id id;
|
2006-07-05 22:34:51 +07:00
|
|
|
struct snd_kcontrol_volatile *vd;
|
2006-06-01 23:34:01 +07:00
|
|
|
|
2017-08-03 18:20:43 +07:00
|
|
|
if (copy_from_user(&header, buf, sizeof(header)))
|
2006-06-01 23:34:01 +07:00
|
|
|
return -EFAULT;
|
2017-08-03 18:20:43 +07:00
|
|
|
|
|
|
|
/* In design of control core, numerical ID starts at 1. */
|
|
|
|
if (header.numid == 0)
|
2006-07-05 22:34:51 +07:00
|
|
|
return -EINVAL;
|
2017-08-03 18:20:43 +07:00
|
|
|
|
|
|
|
/* At least, container should include type and length fields. */
|
|
|
|
if (header.length < sizeof(unsigned int) * 2)
|
2016-01-18 20:12:40 +07:00
|
|
|
return -EINVAL;
|
2017-08-03 18:20:43 +07:00
|
|
|
container_size = header.length;
|
|
|
|
container = buf->tlv;
|
2017-08-03 18:20:41 +07:00
|
|
|
|
2017-08-03 18:20:43 +07:00
|
|
|
kctl = snd_ctl_find_numid(file->card, header.numid);
|
2017-08-03 18:20:41 +07:00
|
|
|
if (kctl == NULL)
|
|
|
|
return -ENOENT;
|
|
|
|
|
2017-08-03 18:20:43 +07:00
|
|
|
/* Calculate index of the element in this set. */
|
|
|
|
id = kctl->id;
|
|
|
|
snd_ctl_build_ioff(&id, kctl, header.numid - id.numid);
|
|
|
|
vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
|
2017-08-03 18:20:41 +07:00
|
|
|
|
2006-07-05 22:34:51 +07:00
|
|
|
if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
|
2017-08-03 18:20:43 +07:00
|
|
|
return call_tlv_handler(file, op_flag, kctl, &id, container,
|
|
|
|
container_size);
|
2006-07-05 22:34:51 +07:00
|
|
|
} else {
|
2017-08-03 18:20:43 +07:00
|
|
|
if (op_flag == SNDRV_CTL_TLV_OP_READ) {
|
|
|
|
return read_tlv_buf(kctl, &id, container,
|
|
|
|
container_size);
|
|
|
|
}
|
2006-07-05 22:34:51 +07:00
|
|
|
}
|
2017-08-03 18:20:41 +07:00
|
|
|
|
2017-08-03 18:20:43 +07:00
|
|
|
/* Not supported. */
|
|
|
|
return -ENXIO;
|
2006-06-01 23:34:01 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_file *ctl;
|
|
|
|
struct snd_card *card;
|
|
|
|
struct snd_kctl_ioctl *p;
|
2005-04-17 05:20:36 +07:00
|
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
int __user *ip = argp;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ctl = file->private_data;
|
|
|
|
card = ctl->card;
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card))
|
|
|
|
return -ENXIO;
|
2005-04-17 05:20:36 +07:00
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_CTL_IOCTL_PVERSION:
|
|
|
|
return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
|
|
|
|
case SNDRV_CTL_IOCTL_CARD_INFO:
|
|
|
|
return snd_ctl_card_info(card, ctl, cmd, argp);
|
|
|
|
case SNDRV_CTL_IOCTL_ELEM_LIST:
|
2006-06-01 23:34:01 +07:00
|
|
|
return snd_ctl_elem_list(card, argp);
|
2005-04-17 05:20:36 +07:00
|
|
|
case SNDRV_CTL_IOCTL_ELEM_INFO:
|
|
|
|
return snd_ctl_elem_info_user(ctl, argp);
|
|
|
|
case SNDRV_CTL_IOCTL_ELEM_READ:
|
2006-06-01 23:34:01 +07:00
|
|
|
return snd_ctl_elem_read_user(card, argp);
|
2005-04-17 05:20:36 +07:00
|
|
|
case SNDRV_CTL_IOCTL_ELEM_WRITE:
|
|
|
|
return snd_ctl_elem_write_user(ctl, argp);
|
|
|
|
case SNDRV_CTL_IOCTL_ELEM_LOCK:
|
|
|
|
return snd_ctl_elem_lock(ctl, argp);
|
|
|
|
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
|
|
|
|
return snd_ctl_elem_unlock(ctl, argp);
|
|
|
|
case SNDRV_CTL_IOCTL_ELEM_ADD:
|
|
|
|
return snd_ctl_elem_add_user(ctl, argp, 0);
|
|
|
|
case SNDRV_CTL_IOCTL_ELEM_REPLACE:
|
|
|
|
return snd_ctl_elem_add_user(ctl, argp, 1);
|
|
|
|
case SNDRV_CTL_IOCTL_ELEM_REMOVE:
|
|
|
|
return snd_ctl_elem_remove(ctl, argp);
|
|
|
|
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
|
|
|
|
return snd_ctl_subscribe_events(ctl, ip);
|
2006-07-05 22:34:51 +07:00
|
|
|
case SNDRV_CTL_IOCTL_TLV_READ:
|
2017-08-03 18:20:41 +07:00
|
|
|
down_read(&ctl->card->controls_rwsem);
|
|
|
|
err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ);
|
|
|
|
up_read(&ctl->card->controls_rwsem);
|
|
|
|
return err;
|
2006-07-05 22:34:51 +07:00
|
|
|
case SNDRV_CTL_IOCTL_TLV_WRITE:
|
2017-08-03 18:20:41 +07:00
|
|
|
down_write(&ctl->card->controls_rwsem);
|
|
|
|
err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE);
|
|
|
|
up_write(&ctl->card->controls_rwsem);
|
|
|
|
return err;
|
2006-07-05 22:34:51 +07:00
|
|
|
case SNDRV_CTL_IOCTL_TLV_COMMAND:
|
2017-08-03 18:20:41 +07:00
|
|
|
down_write(&ctl->card->controls_rwsem);
|
|
|
|
err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD);
|
|
|
|
up_write(&ctl->card->controls_rwsem);
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
case SNDRV_CTL_IOCTL_POWER:
|
2005-11-17 21:55:49 +07:00
|
|
|
return -ENOPROTOOPT;
|
2005-04-17 05:20:36 +07:00
|
|
|
case SNDRV_CTL_IOCTL_POWER_STATE:
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
return put_user(card->power_state, ip) ? -EFAULT : 0;
|
|
|
|
#else
|
|
|
|
return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
down_read(&snd_ioctl_rwsem);
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(p, &snd_control_ioctls, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
err = p->fioctl(card, ctl, cmd, arg);
|
|
|
|
if (err != -ENOIOCTLCMD) {
|
|
|
|
up_read(&snd_ioctl_rwsem);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
up_read(&snd_ioctl_rwsem);
|
2014-02-05 00:18:16 +07:00
|
|
|
dev_dbg(card->dev, "unknown ioctl = 0x%x\n", cmd);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOTTY;
|
|
|
|
}
|
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
static ssize_t snd_ctl_read(struct file *file, char __user *buffer,
|
|
|
|
size_t count, loff_t * offset)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_file *ctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
int err = 0;
|
|
|
|
ssize_t result = 0;
|
|
|
|
|
|
|
|
ctl = file->private_data;
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!ctl || !ctl->card))
|
|
|
|
return -ENXIO;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!ctl->subscribed)
|
|
|
|
return -EBADFD;
|
2005-11-17 19:53:23 +07:00
|
|
|
if (count < sizeof(struct snd_ctl_event))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EINVAL;
|
|
|
|
spin_lock_irq(&ctl->read_lock);
|
2005-11-17 19:53:23 +07:00
|
|
|
while (count >= sizeof(struct snd_ctl_event)) {
|
|
|
|
struct snd_ctl_event ev;
|
|
|
|
struct snd_kctl_event *kev;
|
2005-04-17 05:20:36 +07:00
|
|
|
while (list_empty(&ctl->events)) {
|
2017-06-20 17:06:13 +07:00
|
|
|
wait_queue_entry_t wait;
|
2005-04-17 05:20:36 +07:00
|
|
|
if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) {
|
|
|
|
err = -EAGAIN;
|
|
|
|
goto __end_lock;
|
|
|
|
}
|
|
|
|
init_waitqueue_entry(&wait, current);
|
|
|
|
add_wait_queue(&ctl->change_sleep, &wait);
|
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
spin_unlock_irq(&ctl->read_lock);
|
|
|
|
schedule();
|
|
|
|
remove_wait_queue(&ctl->change_sleep, &wait);
|
2012-10-16 21:43:39 +07:00
|
|
|
if (ctl->card->shutdown)
|
|
|
|
return -ENODEV;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (signal_pending(current))
|
2006-11-07 19:42:54 +07:00
|
|
|
return -ERESTARTSYS;
|
2005-04-17 05:20:36 +07:00
|
|
|
spin_lock_irq(&ctl->read_lock);
|
|
|
|
}
|
|
|
|
kev = snd_kctl_event(ctl->events.next);
|
|
|
|
ev.type = SNDRV_CTL_EVENT_ELEM;
|
|
|
|
ev.data.elem.mask = kev->mask;
|
|
|
|
ev.data.elem.id = kev->id;
|
|
|
|
list_del(&kev->list);
|
|
|
|
spin_unlock_irq(&ctl->read_lock);
|
|
|
|
kfree(kev);
|
2005-11-17 19:53:23 +07:00
|
|
|
if (copy_to_user(buffer, &ev, sizeof(struct snd_ctl_event))) {
|
2005-04-17 05:20:36 +07:00
|
|
|
err = -EFAULT;
|
|
|
|
goto __end;
|
|
|
|
}
|
|
|
|
spin_lock_irq(&ctl->read_lock);
|
2005-11-17 19:53:23 +07:00
|
|
|
buffer += sizeof(struct snd_ctl_event);
|
|
|
|
count -= sizeof(struct snd_ctl_event);
|
|
|
|
result += sizeof(struct snd_ctl_event);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
__end_lock:
|
|
|
|
spin_unlock_irq(&ctl->read_lock);
|
|
|
|
__end:
|
|
|
|
return result > 0 ? result : err;
|
|
|
|
}
|
|
|
|
|
2017-07-03 10:27:36 +07:00
|
|
|
static __poll_t snd_ctl_poll(struct file *file, poll_table * wait)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2017-07-03 10:27:36 +07:00
|
|
|
__poll_t mask;
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_file *ctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
ctl = file->private_data;
|
|
|
|
if (!ctl->subscribed)
|
|
|
|
return 0;
|
|
|
|
poll_wait(file, &ctl->change_sleep, wait);
|
|
|
|
|
|
|
|
mask = 0;
|
|
|
|
if (!list_empty(&ctl->events))
|
2018-02-12 05:34:03 +07:00
|
|
|
mask |= EPOLLIN | EPOLLRDNORM;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* register the device-specific control-ioctls.
|
|
|
|
* called from each device manager like pcm.c, hwdep.c, etc.
|
|
|
|
*/
|
|
|
|
static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists)
|
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kctl_ioctl *pn;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-11-17 19:53:23 +07:00
|
|
|
pn = kzalloc(sizeof(struct snd_kctl_ioctl), GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (pn == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
pn->fioctl = fcn;
|
|
|
|
down_write(&snd_ioctl_rwsem);
|
|
|
|
list_add_tail(&pn->list, lists);
|
|
|
|
up_write(&snd_ioctl_rwsem);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-30 19:44:34 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_register_ioctl - register the device-specific control-ioctls
|
|
|
|
* @fcn: ioctl callback function
|
|
|
|
*
|
|
|
|
* called from each device manager like pcm.c, hwdep.c, etc.
|
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn)
|
|
|
|
{
|
|
|
|
return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls);
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_register_ioctl);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
#ifdef CONFIG_COMPAT
|
2014-10-30 19:44:34 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_register_ioctl_compat - register the device-specific 32bit compat
|
|
|
|
* control-ioctls
|
|
|
|
* @fcn: ioctl callback function
|
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn)
|
|
|
|
{
|
|
|
|
return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls);
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_register_ioctl_compat);
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* de-register the device-specific control-ioctls.
|
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn,
|
|
|
|
struct list_head *lists)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_kctl_ioctl *p;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!fcn))
|
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
down_write(&snd_ioctl_rwsem);
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(p, lists, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (p->fioctl == fcn) {
|
|
|
|
list_del(&p->list);
|
|
|
|
up_write(&snd_ioctl_rwsem);
|
|
|
|
kfree(p);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
up_write(&snd_ioctl_rwsem);
|
|
|
|
snd_BUG();
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2014-10-30 19:44:34 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_unregister_ioctl - de-register the device-specific control-ioctls
|
|
|
|
* @fcn: ioctl callback function to unregister
|
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn)
|
|
|
|
{
|
|
|
|
return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls);
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_unregister_ioctl);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
#ifdef CONFIG_COMPAT
|
2014-10-30 19:44:34 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_unregister_ioctl - de-register the device-specific compat 32bit
|
|
|
|
* control-ioctls
|
|
|
|
* @fcn: ioctl callback function to unregister
|
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn)
|
|
|
|
{
|
|
|
|
return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls);
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat);
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
static int snd_ctl_fasync(int fd, struct file * file, int on)
|
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_ctl_file *ctl;
|
2009-02-02 04:52:56 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
ctl = file->private_data;
|
2009-02-02 04:52:56 +07:00
|
|
|
return fasync_helper(fd, file, on, &ctl->fasync);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-02-19 20:30:29 +07:00
|
|
|
/* return the preferred subdevice number if already assigned;
|
|
|
|
* otherwise return -1
|
|
|
|
*/
|
|
|
|
int snd_ctl_get_preferred_subdevice(struct snd_card *card, int type)
|
|
|
|
{
|
|
|
|
struct snd_ctl_file *kctl;
|
|
|
|
int subdevice = -1;
|
|
|
|
|
|
|
|
read_lock(&card->ctl_files_rwlock);
|
|
|
|
list_for_each_entry(kctl, &card->ctl_files, list) {
|
|
|
|
if (kctl->pid == task_pid(current)) {
|
|
|
|
subdevice = kctl->preferred_subdevice[type];
|
|
|
|
if (subdevice != -1)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
read_unlock(&card->ctl_files_rwlock);
|
|
|
|
return subdevice;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_ctl_get_preferred_subdevice);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* ioctl32 compat
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
#include "control_compat.c"
|
|
|
|
#else
|
|
|
|
#define snd_ctl_ioctl_compat NULL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* INIT PART
|
|
|
|
*/
|
|
|
|
|
2007-02-12 15:55:37 +07:00
|
|
|
static const struct file_operations snd_ctl_f_ops =
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.read = snd_ctl_read,
|
|
|
|
.open = snd_ctl_open,
|
|
|
|
.release = snd_ctl_release,
|
2010-04-13 16:49:04 +07:00
|
|
|
.llseek = no_llseek,
|
2005-04-17 05:20:36 +07:00
|
|
|
.poll = snd_ctl_poll,
|
|
|
|
.unlocked_ioctl = snd_ctl_ioctl,
|
|
|
|
.compat_ioctl = snd_ctl_ioctl_compat,
|
|
|
|
.fasync = snd_ctl_fasync,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* registration of the control device
|
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_dev_register(struct snd_device *device)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card = device->device_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2015-01-30 14:34:58 +07:00
|
|
|
return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
|
|
|
|
&snd_ctl_f_ops, card, &card->ctl_dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* disconnection of the control device
|
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_dev_disconnect(struct snd_device *device)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card = device->device_data;
|
|
|
|
struct snd_ctl_file *ctl;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-09-07 17:51:13 +07:00
|
|
|
read_lock(&card->ctl_files_rwlock);
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(ctl, &card->ctl_files, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
wake_up(&ctl->change_sleep);
|
|
|
|
kill_fasync(&ctl->fasync, SIGIO, POLL_ERR);
|
|
|
|
}
|
2008-09-07 17:51:13 +07:00
|
|
|
read_unlock(&card->ctl_files_rwlock);
|
2006-06-23 19:38:23 +07:00
|
|
|
|
2015-01-30 14:34:58 +07:00
|
|
|
return snd_unregister_device(&card->ctl_dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* free all controls
|
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
static int snd_ctl_dev_free(struct snd_device *device)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:53:23 +07:00
|
|
|
struct snd_card *card = device->device_data;
|
|
|
|
struct snd_kcontrol *control;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
while (!list_empty(&card->controls)) {
|
|
|
|
control = snd_kcontrol(card->controls.next);
|
|
|
|
snd_ctl_remove(card, control);
|
|
|
|
}
|
|
|
|
up_write(&card->controls_rwsem);
|
2015-01-29 22:41:27 +07:00
|
|
|
put_device(&card->ctl_dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* create control core:
|
|
|
|
* called from init.c
|
|
|
|
*/
|
2005-11-17 19:53:23 +07:00
|
|
|
int snd_ctl_create(struct snd_card *card)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2020-01-03 15:16:20 +07:00
|
|
|
static const struct snd_device_ops ops = {
|
2005-04-17 05:20:36 +07:00
|
|
|
.dev_free = snd_ctl_dev_free,
|
|
|
|
.dev_register = snd_ctl_dev_register,
|
|
|
|
.dev_disconnect = snd_ctl_dev_disconnect,
|
|
|
|
};
|
2015-01-29 22:41:27 +07:00
|
|
|
int err;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card))
|
|
|
|
return -ENXIO;
|
2015-01-29 22:41:27 +07:00
|
|
|
if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS))
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
snd_device_initialize(&card->ctl_dev, card);
|
|
|
|
dev_set_name(&card->ctl_dev, "controlC%d", card->number);
|
|
|
|
|
|
|
|
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
|
|
|
|
if (err < 0)
|
|
|
|
put_device(&card->ctl_dev);
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2007-07-23 20:41:34 +07:00
|
|
|
|
|
|
|
/*
|
2011-01-10 22:25:44 +07:00
|
|
|
* Frequently used control callbacks/helpers
|
2007-07-23 20:41:34 +07:00
|
|
|
*/
|
2014-10-30 19:44:34 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_ctl_boolean_mono_info - Helper function for a standard boolean info
|
|
|
|
* callback with a mono channel
|
|
|
|
* @kcontrol: the kcontrol instance
|
|
|
|
* @uinfo: info to store
|
|
|
|
*
|
|
|
|
* This is a function that can be used as info callback for a standard
|
|
|
|
* boolean control with a single mono channel.
|
|
|
|
*/
|
2007-07-23 20:41:34 +07:00
|
|
|
int snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.integer.min = 0;
|
|
|
|
uinfo->value.integer.max = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(snd_ctl_boolean_mono_info);
|
|
|
|
|
2014-10-30 19:44:34 +07:00
|
|
|
/**
|
|
|
|
* snd_ctl_boolean_stereo_info - Helper function for a standard boolean info
|
|
|
|
* callback with stereo two channels
|
|
|
|
* @kcontrol: the kcontrol instance
|
|
|
|
* @uinfo: info to store
|
|
|
|
*
|
|
|
|
* This is a function that can be used as info callback for a standard
|
|
|
|
* boolean control with stereo two channels.
|
|
|
|
*/
|
2007-07-23 20:41:34 +07:00
|
|
|
int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
|
|
|
uinfo->count = 2;
|
|
|
|
uinfo->value.integer.min = 0;
|
|
|
|
uinfo->value.integer.max = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(snd_ctl_boolean_stereo_info);
|
2011-01-10 22:25:44 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_ctl_enum_info - fills the info structure for an enumerated control
|
|
|
|
* @info: the structure to be filled
|
|
|
|
* @channels: the number of the control's channels; often one
|
|
|
|
* @items: the number of control values; also the size of @names
|
|
|
|
* @names: an array containing the names of all control values
|
|
|
|
*
|
|
|
|
* Sets all required fields in @info to their appropriate values.
|
|
|
|
* If the control's accessibility is not the default (readable and writable),
|
|
|
|
* the caller has to fill @info->access.
|
2013-03-12 04:05:14 +07:00
|
|
|
*
|
|
|
|
* Return: Zero.
|
2011-01-10 22:25:44 +07:00
|
|
|
*/
|
|
|
|
int snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels,
|
|
|
|
unsigned int items, const char *const names[])
|
|
|
|
{
|
|
|
|
info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
|
|
info->count = channels;
|
|
|
|
info->value.enumerated.items = items;
|
2014-10-20 23:08:50 +07:00
|
|
|
if (!items)
|
|
|
|
return 0;
|
2011-01-10 22:25:44 +07:00
|
|
|
if (info->value.enumerated.item >= items)
|
|
|
|
info->value.enumerated.item = items - 1;
|
2014-10-20 23:07:21 +07:00
|
|
|
WARN(strlen(names[info->value.enumerated.item]) >= sizeof(info->value.enumerated.name),
|
|
|
|
"ALSA: too long item name '%s'\n",
|
|
|
|
names[info->value.enumerated.item]);
|
2011-01-10 22:25:44 +07:00
|
|
|
strlcpy(info->value.enumerated.name,
|
|
|
|
names[info->value.enumerated.item],
|
|
|
|
sizeof(info->value.enumerated.name));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(snd_ctl_enum_info);
|