summaryrefslogtreecommitdiff
path: root/sound/core
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core')
-rw-r--r--sound/core/Kconfig6
-rw-r--r--sound/core/Makefile2
-rw-r--r--sound/core/control.c272
-rw-r--r--sound/core/control_led.c777
-rw-r--r--sound/core/init.c2
-rw-r--r--sound/core/oss/pcm_oss.c8
-rw-r--r--sound/core/pcm_memory.c12
-rw-r--r--sound/core/pcm_native.c4
-rw-r--r--sound/core/seq_device.c15
9 files changed, 1034 insertions, 64 deletions
diff --git a/sound/core/Kconfig b/sound/core/Kconfig
index a4050f87f230..db2e3c63ff41 100644
--- a/sound/core/Kconfig
+++ b/sound/core/Kconfig
@@ -203,4 +203,10 @@ config SND_DMA_SGBUF
def_bool y
depends on X86
+config SND_CTL_LED
+ tristate
+ select NEW_LEDS if SND_CTL_LED
+ select LEDS_TRIGGERS if SND_CTL_LED
+ select LEDS_TRIGGER_AUDIO if SND_CTL_LED
+
source "sound/core/seq/Kconfig"
diff --git a/sound/core/Makefile b/sound/core/Makefile
index ee4a4a6b99ba..d774792850f3 100644
--- a/sound/core/Makefile
+++ b/sound/core/Makefile
@@ -27,6 +27,7 @@ CFLAGS_pcm_native.o := -I$(src)
snd-pcm-dmaengine-objs := pcm_dmaengine.o
+snd-ctl-led-objs := control_led.o
snd-rawmidi-objs := rawmidi.o
snd-timer-objs := timer.o
snd-hrtimer-objs := hrtimer.o
@@ -37,6 +38,7 @@ snd-seq-device-objs := seq_device.o
snd-compress-objs := compress_offload.o
obj-$(CONFIG_SND) += snd.o
+obj-$(CONFIG_SND_CTL_LED) += snd-ctl-led.o
obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
obj-$(CONFIG_SND_TIMER) += snd-timer.o
obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o
diff --git a/sound/core/control.c b/sound/core/control.c
index 5165741a8400..498e3701514a 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -7,6 +7,7 @@
#include <linux/threads.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/time.h>
@@ -18,8 +19,11 @@
#include <sound/info.h>
#include <sound/control.h>
-/* max number of user-defined controls */
-#define MAX_USER_CONTROLS 32
+// Max allocation size for user controls.
+static int max_user_ctl_alloc_size = 8 * 1024 * 1024;
+module_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444);
+MODULE_PARM_DESC(max_user_ctl_alloc_size, "Max allocation size for user controls");
+
#define MAX_CONTROL_COUNT 1028
struct snd_kctl_ioctl {
@@ -28,10 +32,12 @@ struct snd_kctl_ioctl {
};
static DECLARE_RWSEM(snd_ioctl_rwsem);
+static DECLARE_RWSEM(snd_ctl_layer_rwsem);
static LIST_HEAD(snd_control_ioctls);
#ifdef CONFIG_COMPAT
static LIST_HEAD(snd_control_compat_ioctls);
#endif
+static struct snd_ctl_layer_ops *snd_ctl_layer;
static int snd_ctl_open(struct inode *inode, struct file *file)
{
@@ -182,6 +188,32 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask,
EXPORT_SYMBOL(snd_ctl_notify);
/**
+ * snd_ctl_notify_one - Send notification to user-space for a control change
+ * @card: the card to send notification
+ * @mask: the event mask, SNDRV_CTL_EVENT_*
+ * @kctl: the pointer with the control instance
+ * @ioff: the additional offset to the control index
+ *
+ * This function calls snd_ctl_notify() and does additional jobs
+ * like LED state changes.
+ */
+void snd_ctl_notify_one(struct snd_card *card, unsigned int mask,
+ struct snd_kcontrol *kctl, unsigned int ioff)
+{
+ struct snd_ctl_elem_id id = kctl->id;
+ struct snd_ctl_layer_ops *lops;
+
+ id.index += ioff;
+ id.numid += ioff;
+ snd_ctl_notify(card, mask, &id);
+ down_read(&snd_ctl_layer_rwsem);
+ for (lops = snd_ctl_layer; lops; lops = lops->next)
+ lops->lnotify(card, mask, kctl, ioff);
+ up_read(&snd_ctl_layer_rwsem);
+}
+EXPORT_SYMBOL(snd_ctl_notify_one);
+
+/**
* 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
@@ -250,6 +282,7 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
+ SNDRV_CTL_ELEM_ACCESS_LED_MASK |
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);
err = snd_ctl_new(&kctl, count, access, NULL);
@@ -342,7 +375,6 @@ static int __snd_ctl_add_replace(struct snd_card *card,
{
struct snd_ctl_elem_id id;
unsigned int idx;
- unsigned int count;
struct snd_kcontrol *old;
int err;
@@ -376,10 +408,8 @@ static int __snd_ctl_add_replace(struct snd_card *card,
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);
+ for (idx = 0; idx < kcontrol->count; idx++)
+ snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
return 0;
}
@@ -462,16 +492,14 @@ EXPORT_SYMBOL(snd_ctl_replace);
*/
int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
- struct snd_ctl_elem_id id;
unsigned int idx;
if (snd_BUG_ON(!card || !kcontrol))
return -EINVAL;
list_del(&kcontrol->list);
card->controls_count -= kcontrol->count;
- id = kcontrol->id;
- for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id);
+ for (idx = 0; idx < kcontrol->count; idx++)
+ snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx);
snd_ctl_free_one(kcontrol);
return 0;
}
@@ -537,9 +565,6 @@ static int snd_ctl_remove_user_ctl(struct snd_ctl_file * file,
goto error;
}
ret = snd_ctl_remove(card, kctl);
- if (ret < 0)
- goto error;
- card->user_ctl_count--;
error:
up_write(&card->controls_rwsem);
return ret;
@@ -584,11 +609,13 @@ int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,
vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
}
snd_ctl_build_ioff(id, kctl, index_offset);
- ret = 1;
+ downgrade_write(&card->controls_rwsem);
+ snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, index_offset);
+ up_read(&card->controls_rwsem);
+ return 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);
@@ -1022,7 +1049,8 @@ static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl,
if (result < 0)
return result;
/* drop internal access flags */
- info.access &= ~SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK;
+ info.access &= ~(SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK|
+ SNDRV_CTL_ELEM_ACCESS_LED_MASK);
if (copy_to_user(_info, &info, sizeof(info)))
return -EFAULT;
return result;
@@ -1110,25 +1138,34 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
unsigned int index_offset;
int result;
+ down_write(&card->controls_rwsem);
kctl = snd_ctl_find_id(card, &control->id);
- if (kctl == NULL)
+ if (kctl == NULL) {
+ up_write(&card->controls_rwsem);
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)) {
+ up_write(&card->controls_rwsem);
return -EPERM;
}
snd_ctl_build_ioff(&control->id, kctl, index_offset);
result = kctl->put(kctl, control);
- if (result < 0)
+ if (result < 0) {
+ up_write(&card->controls_rwsem);
return result;
+ }
if (result > 0) {
- struct snd_ctl_elem_id id = control->id;
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id);
+ downgrade_write(&card->controls_rwsem);
+ snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, index_offset);
+ up_read(&card->controls_rwsem);
+ } else {
+ up_write(&card->controls_rwsem);
}
return 0;
@@ -1150,9 +1187,7 @@ static int snd_ctl_elem_write_user(struct snd_ctl_file *file,
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;
@@ -1231,6 +1266,12 @@ struct user_element {
void *priv_data; /* private data (like strings for enumerated type) */
};
+// check whether the addition (in bytes) of user ctl element may overflow the limit.
+static bool check_user_elem_overflow(struct snd_card *card, ssize_t add)
+{
+ return (ssize_t)card->user_ctl_alloc_size + add > max_user_ctl_alloc_size;
+}
+
static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
@@ -1296,12 +1337,12 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol,
return change;
}
+/* called in controls_rwsem write lock */
static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf,
unsigned int size)
{
struct user_element *ue = kctl->private_data;
unsigned int *container;
- struct snd_ctl_elem_id id;
unsigned int mask = 0;
int i;
int change;
@@ -1309,6 +1350,10 @@ static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf,
if (size > 1024 * 128) /* sane value */
return -EINVAL;
+ // does the TLV size change cause overflow?
+ if (check_user_elem_overflow(ue->card, (ssize_t)(size - ue->tlv_data_size)))
+ return -ENOMEM;
+
container = vmemdup_user(buf, size);
if (IS_ERR(container))
return PTR_ERR(container);
@@ -1326,17 +1371,20 @@ static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf,
for (i = 0; i < kctl->count; ++i)
kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
mask = SNDRV_CTL_EVENT_MASK_INFO;
+ } else {
+ ue->card->user_ctl_alloc_size -= ue->tlv_data_size;
+ ue->tlv_data_size = 0;
+ kvfree(ue->tlv_data);
}
- kvfree(ue->tlv_data);
ue->tlv_data = container;
ue->tlv_data_size = size;
+ // decremented at private_free.
+ ue->card->user_ctl_alloc_size += size;
mask |= SNDRV_CTL_EVENT_MASK_TLV;
- for (i = 0; i < kctl->count; ++i) {
- snd_ctl_build_ioff(&id, kctl, i);
- snd_ctl_notify(ue->card, mask, &id);
- }
+ for (i = 0; i < kctl->count; ++i)
+ snd_ctl_notify_one(ue->card, mask, kctl, i);
return change;
}
@@ -1367,6 +1415,7 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kctl, int op_flag,
return read_user_tlv(kctl, buf, size);
}
+/* called in controls_rwsem write lock */
static int snd_ctl_elem_init_enum_names(struct user_element *ue)
{
char *names, *p;
@@ -1374,16 +1423,17 @@ static int snd_ctl_elem_init_enum_names(struct user_element *ue)
unsigned int i;
const uintptr_t user_ptrval = ue->info.value.enumerated.names_ptr;
- if (ue->info.value.enumerated.names_length > 64 * 1024)
+ buf_len = ue->info.value.enumerated.names_length;
+ if (buf_len > 64 * 1024)
return -EINVAL;
- names = vmemdup_user((const void __user *)user_ptrval,
- ue->info.value.enumerated.names_length);
+ if (check_user_elem_overflow(ue->card, buf_len))
+ return -ENOMEM;
+ names = vmemdup_user((const void __user *)user_ptrval, buf_len);
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);
@@ -1397,14 +1447,27 @@ static int snd_ctl_elem_init_enum_names(struct user_element *ue)
ue->priv_data = names;
ue->info.value.enumerated.names_ptr = 0;
+ // increment the allocation size; decremented again at private_free.
+ ue->card->user_ctl_alloc_size += ue->info.value.enumerated.names_length;
return 0;
}
+static size_t compute_user_elem_size(size_t size, unsigned int count)
+{
+ return sizeof(struct user_element) + size * count;
+}
+
static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
{
struct user_element *ue = kcontrol->private_data;
+ // decrement the allocation size.
+ ue->card->user_ctl_alloc_size -= compute_user_elem_size(ue->elem_data_size, kcontrol->count);
+ ue->card->user_ctl_alloc_size -= ue->tlv_data_size;
+ if (ue->priv_data)
+ ue->card->user_ctl_alloc_size -= ue->info.value.enumerated.names_length;
+
kvfree(ue->tlv_data);
kvfree(ue->priv_data);
kfree(ue);
@@ -1418,6 +1481,7 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
unsigned int count;
unsigned int access;
long private_size;
+ size_t alloc_size;
struct user_element *ue;
unsigned int offset;
int err;
@@ -1435,13 +1499,6 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
return err;
}
- /*
- * The number of userspace controls are counted control by control,
- * not element by element.
- */
- if (card->user_ctl_count + 1 > MAX_USER_CONTROLS)
- return -ENOMEM;
-
/* Check the number of elements for this userspace control. */
count = info->owner;
if (count == 0)
@@ -1472,6 +1529,13 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
if (info->count < 1)
return -EINVAL;
private_size = value_sizes[info->type] * info->count;
+ alloc_size = compute_user_elem_size(private_size, count);
+
+ down_write(&card->controls_rwsem);
+ if (check_user_elem_overflow(card, alloc_size)) {
+ err = -ENOMEM;
+ goto unlock;
+ }
/*
* Keep memory object for this userspace control. After passing this
@@ -1481,18 +1545,21 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
*/
err = snd_ctl_new(&kctl, count, access, file);
if (err < 0)
- return err;
+ goto unlock;
memcpy(&kctl->id, &info->id, sizeof(kctl->id));
- kctl->private_data = kzalloc(sizeof(struct user_element) + private_size * count,
- GFP_KERNEL);
- if (kctl->private_data == NULL) {
+ ue = kzalloc(alloc_size, GFP_KERNEL);
+ if (!ue) {
kfree(kctl);
- return -ENOMEM;
+ err = -ENOMEM;
+ goto unlock;
}
+ kctl->private_data = ue;
kctl->private_free = snd_ctl_elem_user_free;
+ // increment the allocated size; decremented again at private_free.
+ card->user_ctl_alloc_size += alloc_size;
+
/* Set private data for this userspace control. */
- ue = (struct user_element *)kctl->private_data;
ue->card = card;
ue->info = *info;
ue->info.access = 0;
@@ -1502,7 +1569,7 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
err = snd_ctl_elem_init_enum_names(ue);
if (err < 0) {
snd_ctl_free_one(kctl);
- return err;
+ goto unlock;
}
}
@@ -1519,7 +1586,6 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
kctl->tlv.c = snd_ctl_elem_user_tlv;
/* This function manage to free the instance on failure. */
- down_write(&card->controls_rwsem);
err = __snd_ctl_add_replace(card, kctl, CTL_ADD_EXCLUSIVE);
if (err < 0) {
snd_ctl_free_one(kctl);
@@ -1534,9 +1600,6 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
* applications because the field originally means PID of a process
* which locks the element.
*/
-
- card->user_ctl_count++;
-
unlock:
up_write(&card->controls_rwsem);
return err;
@@ -1976,6 +2039,88 @@ EXPORT_SYMBOL_GPL(snd_ctl_get_preferred_subdevice);
#endif
/*
+ * control layers (audio LED etc.)
+ */
+
+/**
+ * snd_ctl_request_layer - request to use the layer
+ * @module_name: Name of the kernel module (NULL == build-in)
+ *
+ * Return an error code when the module cannot be loaded.
+ */
+int snd_ctl_request_layer(const char *module_name)
+{
+ struct snd_ctl_layer_ops *lops;
+
+ if (module_name == NULL)
+ return 0;
+ down_read(&snd_ctl_layer_rwsem);
+ for (lops = snd_ctl_layer; lops; lops = lops->next)
+ if (strcmp(lops->module_name, module_name) == 0)
+ break;
+ up_read(&snd_ctl_layer_rwsem);
+ if (lops)
+ return 0;
+ return request_module(module_name);
+}
+EXPORT_SYMBOL_GPL(snd_ctl_request_layer);
+
+/**
+ * snd_ctl_register_layer - register new control layer
+ * @lops: operation structure
+ *
+ * The new layer can track all control elements and do additional
+ * operations on top (like audio LED handling).
+ */
+void snd_ctl_register_layer(struct snd_ctl_layer_ops *lops)
+{
+ struct snd_card *card;
+ int card_number;
+
+ down_write(&snd_ctl_layer_rwsem);
+ lops->next = snd_ctl_layer;
+ snd_ctl_layer = lops;
+ up_write(&snd_ctl_layer_rwsem);
+ for (card_number = 0; card_number < SNDRV_CARDS; card_number++) {
+ card = snd_card_ref(card_number);
+ if (card) {
+ down_read(&card->controls_rwsem);
+ lops->lregister(card);
+ up_read(&card->controls_rwsem);
+ snd_card_unref(card);
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(snd_ctl_register_layer);
+
+/**
+ * snd_ctl_disconnect_layer - disconnect control layer
+ * @lops: operation structure
+ *
+ * It is expected that the information about tracked cards
+ * is freed before this call (the disconnect callback is
+ * not called here).
+ */
+void snd_ctl_disconnect_layer(struct snd_ctl_layer_ops *lops)
+{
+ struct snd_ctl_layer_ops *lops2, *prev_lops2;
+
+ down_write(&snd_ctl_layer_rwsem);
+ for (lops2 = snd_ctl_layer, prev_lops2 = NULL; lops2; lops2 = lops2->next) {
+ if (lops2 == lops) {
+ if (!prev_lops2)
+ snd_ctl_layer = lops->next;
+ else
+ prev_lops2->next = lops->next;
+ break;
+ }
+ prev_lops2 = lops2;
+ }
+ up_write(&snd_ctl_layer_rwsem);
+}
+EXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer);
+
+/*
* INIT PART
*/
@@ -1998,9 +2143,20 @@ static const struct file_operations snd_ctl_f_ops =
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
+ struct snd_ctl_layer_ops *lops;
+ int err;
- return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
- &snd_ctl_f_ops, card, &card->ctl_dev);
+ err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
+ &snd_ctl_f_ops, card, &card->ctl_dev);
+ if (err < 0)
+ return err;
+ down_read(&card->controls_rwsem);
+ down_read(&snd_ctl_layer_rwsem);
+ for (lops = snd_ctl_layer; lops; lops = lops->next)
+ lops->lregister(card);
+ up_read(&snd_ctl_layer_rwsem);
+ up_read(&card->controls_rwsem);
+ return 0;
}
/*
@@ -2010,6 +2166,7 @@ static int snd_ctl_dev_disconnect(struct snd_device *device)
{
struct snd_card *card = device->device_data;
struct snd_ctl_file *ctl;
+ struct snd_ctl_layer_ops *lops;
unsigned long flags;
read_lock_irqsave(&card->ctl_files_rwlock, flags);
@@ -2019,6 +2176,13 @@ static int snd_ctl_dev_disconnect(struct snd_device *device)
}
read_unlock_irqrestore(&card->ctl_files_rwlock, flags);
+ down_read(&card->controls_rwsem);
+ down_read(&snd_ctl_layer_rwsem);
+ for (lops = snd_ctl_layer; lops; lops = lops->next)
+ lops->ldisconnect(card);
+ up_read(&snd_ctl_layer_rwsem);
+ up_read(&card->controls_rwsem);
+
return snd_unregister_device(&card->ctl_dev);
}
diff --git a/sound/core/control_led.c b/sound/core/control_led.c
new file mode 100644
index 000000000000..25f57c14f294
--- /dev/null
+++ b/sound/core/control_led.c
@@ -0,0 +1,777 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LED state routines for driver control interface
+ * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ALSA control interface to LED trigger code.");
+MODULE_LICENSE("GPL");
+
+#define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \
+ >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1)
+
+enum snd_ctl_led_mode {
+ MODE_FOLLOW_MUTE = 0,
+ MODE_FOLLOW_ROUTE,
+ MODE_OFF,
+ MODE_ON,
+};
+
+struct snd_ctl_led_card {
+ struct device dev;
+ int number;
+ struct snd_ctl_led *led;
+};
+
+struct snd_ctl_led {
+ struct device dev;
+ struct list_head controls;
+ const char *name;
+ unsigned int group;
+ enum led_audio trigger_type;
+ enum snd_ctl_led_mode mode;
+ struct snd_ctl_led_card *cards[SNDRV_CARDS];
+};
+
+struct snd_ctl_led_ctl {
+ struct list_head list;
+ struct snd_card *card;
+ unsigned int access;
+ struct snd_kcontrol *kctl;
+ unsigned int index_offset;
+};
+
+static DEFINE_MUTEX(snd_ctl_led_mutex);
+static bool snd_ctl_led_card_valid[SNDRV_CARDS];
+static struct snd_ctl_led snd_ctl_leds[MAX_LED] = {
+ {
+ .name = "speaker",
+ .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
+ .trigger_type = LED_AUDIO_MUTE,
+ .mode = MODE_FOLLOW_MUTE,
+ },
+ {
+ .name = "mic",
+ .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
+ .trigger_type = LED_AUDIO_MICMUTE,
+ .mode = MODE_FOLLOW_MUTE,
+ },
+};
+
+static void snd_ctl_led_sysfs_add(struct snd_card *card);
+static void snd_ctl_led_sysfs_remove(struct snd_card *card);
+
+#define UPDATE_ROUTE(route, cb) \
+ do { \
+ int route2 = (cb); \
+ if (route2 >= 0) \
+ route = route < 0 ? route2 : (route | route2); \
+ } while (0)
+
+static inline unsigned int access_to_group(unsigned int access)
+{
+ return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >>
+ SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1;
+}
+
+static inline unsigned int group_to_access(unsigned int group)
+{
+ return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
+}
+
+static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access)
+{
+ unsigned int group = access_to_group(access);
+ if (group >= MAX_LED)
+ return NULL;
+ return &snd_ctl_leds[group];
+}
+
+/*
+ * A note for callers:
+ * The two static variables info and value are protected using snd_ctl_led_mutex.
+ */
+static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl)
+{
+ static struct snd_ctl_elem_info info;
+ static struct snd_ctl_elem_value value;
+ struct snd_kcontrol *kctl = lctl->kctl;
+ unsigned int i;
+ int result;
+
+ memset(&info, 0, sizeof(info));
+ info.id = kctl->id;
+ info.id.index += lctl->index_offset;
+ info.id.numid += lctl->index_offset;
+ result = kctl->info(kctl, &info);
+ if (result < 0)
+ return -1;
+ memset(&value, 0, sizeof(value));
+ value.id = info.id;
+ result = kctl->get(kctl, &value);
+ if (result < 0)
+ return -1;
+ if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
+ info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
+ for (i = 0; i < info.count; i++)
+ if (value.value.integer.value[i] != info.value.integer.min)
+ return 1;
+ } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) {
+ for (i = 0; i < info.count; i++)
+ if (value.value.integer64.value[i] != info.value.integer64.min)
+ return 1;
+ }
+ return 0;
+}
+
+static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
+ struct snd_kcontrol *kctl, unsigned int ioff)
+{
+ struct snd_ctl_led *led;
+ struct snd_ctl_led_ctl *lctl;
+ int route;
+ bool found;
+
+ led = snd_ctl_led_get_by_access(access);
+ if (!led)
+ return;
+ route = -1;
+ found = false;
+ mutex_lock(&snd_ctl_led_mutex);
+ /* the card may not be registered (active) at this point */
+ if (card && !snd_ctl_led_card_valid[card->number]) {
+ mutex_unlock(&snd_ctl_led_mutex);
+ return;
+ }
+ list_for_each_entry(lctl, &led->controls, list) {
+ if (lctl->kctl == kctl && lctl->index_offset == ioff)
+ found = true;
+ UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
+ }
+ if (!found && kctl && card) {
+ lctl = kzalloc(sizeof(*lctl), GFP_KERNEL);
+ if (lctl) {
+ lctl->card = card;
+ lctl->access = access;
+ lctl->kctl = kctl;
+ lctl->index_offset = ioff;
+ list_add(&lctl->list, &led->controls);
+ UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
+ }
+ }
+ mutex_unlock(&snd_ctl_led_mutex);
+ switch (led->mode) {
+ case MODE_OFF: route = 1; break;
+ case MODE_ON: route = 0; break;
+ case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break;
+ case MODE_FOLLOW_MUTE: /* noop */ break;
+ }
+ if (route >= 0)
+ ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON);
+}
+
+static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)
+{
+ struct list_head *controls;
+ struct snd_ctl_led_ctl *lctl;
+ unsigned int group;
+
+ for (group = 0; group < MAX_LED; group++) {
+ controls = &snd_ctl_leds[group].controls;
+ list_for_each_entry(lctl, controls, list)
+ if (lctl->kctl == kctl && lctl->index_offset == ioff)
+ return lctl;
+ }
+ return NULL;
+}
+
+static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,
+ unsigned int access)
+{
+ struct snd_ctl_led_ctl *lctl;
+ unsigned int ret = 0;
+
+ mutex_lock(&snd_ctl_led_mutex);
+ lctl = snd_ctl_led_find(kctl, ioff);
+ if (lctl && (access == 0 || access != lctl->access)) {
+ ret = lctl->access;
+ list_del(&lctl->list);
+ kfree(lctl);
+ }
+ mutex_unlock(&snd_ctl_led_mutex);
+ return ret;
+}
+
+static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,
+ struct snd_kcontrol *kctl, unsigned int ioff)
+{
+ struct snd_kcontrol_volatile *vd;
+ unsigned int access, access2;
+
+ if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) {
+ access = snd_ctl_led_remove(kctl, ioff, 0);
+ if (access)
+ snd_ctl_led_set_state(card, access, NULL, 0);
+ } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) {
+ vd = &kctl->vd[ioff];
+ access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
+ access2 = snd_ctl_led_remove(kctl, ioff, access);
+ if (access2)
+ snd_ctl_led_set_state(card, access2, NULL, 0);
+ if (access)
+ snd_ctl_led_set_state(card, access, kctl, ioff);
+ } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD |
+ SNDRV_CTL_EVENT_MASK_VALUE)) != 0) {
+ vd = &kctl->vd[ioff];
+ access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
+ if (access)
+ snd_ctl_led_set_state(card, access, kctl, ioff);
+ }
+}
+
+static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id,
+ unsigned int group, bool set)
+{
+ struct snd_card *card;
+ struct snd_kcontrol *kctl;
+ struct snd_kcontrol_volatile *vd;
+ unsigned int ioff, access, new_access;
+ int err = 0;
+
+ card = snd_card_ref(card_number);
+ if (card) {
+ down_write(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, id);
+ if (kctl) {
+ ioff = snd_ctl_get_ioff(kctl, id);
+ vd = &kctl->vd[ioff];
+ access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
+ if (access != 0 && access != group_to_access(group)) {
+ err = -EXDEV;
+ goto unlock;
+ }
+ new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK;
+ if (set)
+ new_access |= group_to_access(group);
+ if (new_access != vd->access) {
+ vd->access = new_access;
+ snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff);
+ }
+ } else {
+ err = -ENOENT;
+ }
+unlock:
+ up_write(&card->controls_rwsem);
+ snd_card_unref(card);
+ } else {
+ err = -ENXIO;
+ }
+ return err;
+}
+
+static void snd_ctl_led_refresh(void)
+{
+ unsigned int group;
+
+ for (group = 0; group < MAX_LED; group++)
+ snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
+}
+
+static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl)
+{
+ list_del(&lctl->list);
+ kfree(lctl);
+}
+
+static void snd_ctl_led_clean(struct snd_card *card)
+{
+ unsigned int group;
+ struct snd_ctl_led *led;
+ struct snd_ctl_led_ctl *lctl;
+
+ for (group = 0; group < MAX_LED; group++) {
+ led = &snd_ctl_leds[group];
+repeat:
+ list_for_each_entry(lctl, &led->controls, list)
+ if (!card || lctl->card == card) {
+ snd_ctl_led_ctl_destroy(lctl);
+ goto repeat;
+ }
+ }
+}
+
+static int snd_ctl_led_reset(int card_number, unsigned int group)
+{
+ struct snd_card *card;
+ struct snd_ctl_led *led;
+ struct snd_ctl_led_ctl *lctl;
+ struct snd_kcontrol_volatile *vd;
+ bool change = false;
+
+ card = snd_card_ref(card_number);
+ if (!card)
+ return -ENXIO;
+
+ mutex_lock(&snd_ctl_led_mutex);
+ if (!snd_ctl_led_card_valid[card_number]) {
+ mutex_unlock(&snd_ctl_led_mutex);
+ snd_card_unref(card);
+ return -ENXIO;
+ }
+ led = &snd_ctl_leds[group];
+repeat:
+ list_for_each_entry(lctl, &led->controls, list)
+ if (lctl->card == card) {
+ vd = &lctl->kctl->vd[lctl->index_offset];
+ vd->access &= ~group_to_access(group);
+ snd_ctl_led_ctl_destroy(lctl);
+ change = true;
+ goto repeat;
+ }
+ mutex_unlock(&snd_ctl_led_mutex);
+ if (change)
+ snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
+ snd_card_unref(card);
+ return 0;
+}
+
+static void snd_ctl_led_register(struct snd_card *card)
+{
+ struct snd_kcontrol *kctl;
+ unsigned int ioff;
+
+ if (snd_BUG_ON(card->number < 0 ||
+ card->number >= ARRAY_SIZE(snd_ctl_led_card_valid)))
+ return;
+ mutex_lock(&snd_ctl_led_mutex);
+ snd_ctl_led_card_valid[card->number] = true;
+ mutex_unlock(&snd_ctl_led_mutex);
+ /* the register callback is already called with held card->controls_rwsem */
+ list_for_each_entry(kctl, &card->controls, list)
+ for (ioff = 0; ioff < kctl->count; ioff++)
+ snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff);
+ snd_ctl_led_refresh();
+ snd_ctl_led_sysfs_add(card);
+}
+
+static void snd_ctl_led_disconnect(struct snd_card *card)
+{
+ snd_ctl_led_sysfs_remove(card);
+ mutex_lock(&snd_ctl_led_mutex);
+ snd_ctl_led_card_valid[card->number] = false;
+ snd_ctl_led_clean(card);
+ mutex_unlock(&snd_ctl_led_mutex);
+ snd_ctl_led_refresh();
+}
+
+/*
+ * sysfs
+ */
+
+static ssize_t show_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
+ const char *str;
+
+ switch (led->mode) {
+ case MODE_FOLLOW_MUTE: str = "follow-mute"; break;
+ case MODE_FOLLOW_ROUTE: str = "follow-route"; break;
+ case MODE_ON: str = "on"; break;
+ case MODE_OFF: str = "off"; break;
+ }
+ return sprintf(buf, "%s\n", str);
+}
+
+static ssize_t store_mode(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
+ char _buf[16];
+ size_t l = min(count, sizeof(_buf) - 1);
+ enum snd_ctl_led_mode mode;
+
+ memcpy(_buf, buf, l);
+ _buf[l] = '\0';
+ if (strstr(_buf, "mute"))
+ mode = MODE_FOLLOW_MUTE;
+ else if (strstr(_buf, "route"))
+ mode = MODE_FOLLOW_ROUTE;
+ else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0)
+ mode = MODE_OFF;
+ else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0)
+ mode = MODE_ON;
+ else
+ return count;
+
+ mutex_lock(&snd_ctl_led_mutex);
+ led->mode = mode;
+ mutex_unlock(&snd_ctl_led_mutex);
+
+ snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0);
+ return count;
+}
+
+static ssize_t show_brightness(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
+
+ return sprintf(buf, "%u\n", ledtrig_audio_get(led->trigger_type));
+}
+
+static DEVICE_ATTR(mode, 0644, show_mode, store_mode);
+static DEVICE_ATTR(brightness, 0444, show_brightness, NULL);
+
+static struct attribute *snd_ctl_led_dev_attrs[] = {
+ &dev_attr_mode.attr,
+ &dev_attr_brightness.attr,
+ NULL,
+};
+
+static const struct attribute_group snd_ctl_led_dev_attr_group = {
+ .attrs = snd_ctl_led_dev_attrs,
+};
+
+static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = {
+ &snd_ctl_led_dev_attr_group,
+ NULL,
+};
+
+static char *find_eos(char *s)
+{
+ while (*s && *s != ',')
+ s++;
+ if (*s)
+ s++;
+ return s;
+}
+
+static char *parse_uint(char *s, unsigned int *val)
+{
+ unsigned long long res;
+ if (kstrtoull(s, 10, &res))
+ res = 0;
+ *val = res;
+ return find_eos(s);
+}
+
+static char *parse_string(char *s, char *val, size_t val_size)
+{
+ if (*s == '"' || *s == '\'') {
+ char c = *s;
+ s++;
+ while (*s && *s != c) {
+ if (val_size > 1) {
+ *val++ = *s;
+ val_size--;
+ }
+ s++;
+ }
+ } else {
+ while (*s && *s != ',') {
+ if (val_size > 1) {
+ *val++ = *s;
+ val_size--;
+ }
+ s++;
+ }
+ }
+ *val = '\0';
+ if (*s)
+ s++;
+ return s;
+}
+
+static char *parse_iface(char *s, unsigned int *val)
+{
+ if (!strncasecmp(s, "card", 4))
+ *val = SNDRV_CTL_ELEM_IFACE_CARD;
+ else if (!strncasecmp(s, "mixer", 5))
+ *val = SNDRV_CTL_ELEM_IFACE_MIXER;
+ return find_eos(s);
+}
+
+/*
+ * These types of input strings are accepted:
+ *
+ * unsigned integer - numid (equivaled to numid=UINT)
+ * string - basic mixer name (equivalent to iface=MIXER,name=STR)
+ * numid=UINT
+ * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT]
+ */
+static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count,
+ bool attach)
+{
+ char buf2[256], *s, *os;
+ size_t len = max(sizeof(s) - 1, count);
+ struct snd_ctl_elem_id id;
+ int err;
+
+ strncpy(buf2, buf, len);
+ buf2[len] = '\0';
+ memset(&id, 0, sizeof(id));
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ s = buf2;
+ while (*s) {
+ os = s;
+ if (!strncasecmp(s, "numid=", 6)) {
+ s = parse_uint(s + 6, &id.numid);
+ } else if (!strncasecmp(s, "iface=", 6)) {
+ s = parse_iface(s + 6, &id.iface);
+ } else if (!strncasecmp(s, "device=", 7)) {
+ s = parse_uint(s + 7, &id.device);
+ } else if (!strncasecmp(s, "subdevice=", 10)) {
+ s = parse_uint(s + 10, &id.subdevice);
+ } else if (!strncasecmp(s, "name=", 5)) {
+ s = parse_string(s + 5, id.name, sizeof(id.name));
+ } else if (!strncasecmp(s, "index=", 6)) {
+ s = parse_uint(s + 6, &id.index);
+ } else if (s == buf2) {
+ while (*s) {
+ if (*s < '0' || *s > '9')
+ break;
+ s++;
+ }
+ if (*s == '\0')
+ parse_uint(buf2, &id.numid);
+ else {
+ for (; *s >= ' '; s++);
+ *s = '\0';
+ strlcpy(id.name, buf2, sizeof(id.name));
+ }
+ break;
+ }
+ if (*s == ',')
+ s++;
+ if (s == os)
+ break;
+ }
+
+ err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach);
+ if (err < 0)
+ return err;
+
+ return count;
+}
+
+static ssize_t parse_attach(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
+ return set_led_id(led_card, buf, count, true);
+}
+
+static ssize_t parse_detach(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
+ return set_led_id(led_card, buf, count, false);
+}
+
+static ssize_t ctl_reset(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
+ int err;
+
+ if (count > 0 && buf[0] == '1') {
+ err = snd_ctl_led_reset(led_card->number, led_card->led->group);
+ if (err < 0)
+ return err;
+ }
+ return count;
+}
+
+static ssize_t ctl_list(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
+ struct snd_card *card;
+ struct snd_ctl_led_ctl *lctl;
+ char *buf2 = buf;
+ size_t l;
+
+ card = snd_card_ref(led_card->number);
+ if (!card)
+ return -ENXIO;
+ down_read(&card->controls_rwsem);
+ mutex_lock(&snd_ctl_led_mutex);
+ if (snd_ctl_led_card_valid[led_card->number]) {
+ list_for_each_entry(lctl, &led_card->led->controls, list)
+ if (lctl->card == card) {
+ if (buf2 - buf > PAGE_SIZE - 16)
+ break;
+ if (buf2 != buf)
+ *buf2++ = ' ';
+ l = scnprintf(buf2, 15, "%u",
+ lctl->kctl->id.numid +
+ lctl->index_offset);
+ buf2[l] = '\0';
+ buf2 += l + 1;
+ }
+ }
+ mutex_unlock(&snd_ctl_led_mutex);
+ up_read(&card->controls_rwsem);
+ snd_card_unref(card);
+ return buf2 - buf;
+}
+
+static DEVICE_ATTR(attach, 0200, NULL, parse_attach);
+static DEVICE_ATTR(detach, 0200, NULL, parse_detach);
+static DEVICE_ATTR(reset, 0200, NULL, ctl_reset);
+static DEVICE_ATTR(list, 0444, ctl_list, NULL);
+
+static struct attribute *snd_ctl_led_card_attrs[] = {
+ &dev_attr_attach.attr,
+ &dev_attr_detach.attr,
+ &dev_attr_reset.attr,
+ &dev_attr_list.attr,
+ NULL,
+};
+
+static const struct attribute_group snd_ctl_led_card_attr_group = {
+ .attrs = snd_ctl_led_card_attrs,
+};
+
+static const struct attribute_group *snd_ctl_led_card_attr_groups[] = {
+ &snd_ctl_led_card_attr_group,
+ NULL,
+};
+
+static struct device snd_ctl_led_dev;
+
+static void snd_ctl_led_sysfs_add(struct snd_card *card)
+{
+ unsigned int group;
+ struct snd_ctl_led_card *led_card;
+ struct snd_ctl_led *led;
+ char link_name[32];
+
+ for (group = 0; group < MAX_LED; group++) {
+ led = &snd_ctl_leds[group];
+ led_card = kzalloc(sizeof(*led_card), GFP_KERNEL);
+ if (!led_card)
+ goto cerr2;
+ led_card->number = card->number;
+ led_card->led = led;
+ device_initialize(&led_card->dev);
+ if (dev_set_name(&led_card->dev, "card%d", card->number) < 0)
+ goto cerr;
+ led_card->dev.parent = &led->dev;
+ led_card->dev.groups = snd_ctl_led_card_attr_groups;
+ if (device_add(&led_card->dev))
+ goto cerr;
+ led->cards[card->number] = led_card;
+ snprintf(link_name, sizeof(link_name), "led-%s", led->name);
+ WARN(sysfs_create_link(&card->ctl_dev.kobj, &led_card->dev.kobj, link_name),
+ "can't create symlink to controlC%i device\n", card->number);
+ WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"),
+ "can't create symlink to card%i\n", card->number);
+
+ continue;
+cerr:
+ put_device(&led_card->dev);
+cerr2:
+ printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number);
+ kfree(led_card);
+ }
+}
+
+static void snd_ctl_led_sysfs_remove(struct snd_card *card)
+{
+ unsigned int group;
+ struct snd_ctl_led_card *led_card;
+ struct snd_ctl_led *led;
+ char link_name[32];
+
+ for (group = 0; group < MAX_LED; group++) {
+ led = &snd_ctl_leds[group];
+ led_card = led->cards[card->number];
+ if (!led_card)
+ continue;
+ snprintf(link_name, sizeof(link_name), "led-%s", led->name);
+ sysfs_remove_link(&card->ctl_dev.kobj, link_name);
+ sysfs_remove_link(&led_card->dev.kobj, "card");
+ device_del(&led_card->dev);
+ kfree(led_card);
+ led->cards[card->number] = NULL;
+ }
+}
+
+/*
+ * Control layer registration
+ */
+static struct snd_ctl_layer_ops snd_ctl_led_lops = {
+ .module_name = SND_CTL_LAYER_MODULE_LED,
+ .lregister = snd_ctl_led_register,
+ .ldisconnect = snd_ctl_led_disconnect,
+ .lnotify = snd_ctl_led_notify,
+};
+
+static int __init snd_ctl_led_init(void)
+{
+ struct snd_ctl_led *led;
+ unsigned int group;
+
+ device_initialize(&snd_ctl_led_dev);
+ snd_ctl_led_dev.class = sound_class;
+ dev_set_name(&snd_ctl_led_dev, "ctl-led");
+ if (device_add(&snd_ctl_led_dev)) {
+ put_device(&snd_ctl_led_dev);
+ return -ENOMEM;
+ }
+ for (group = 0; group < MAX_LED; group++) {
+ led = &snd_ctl_leds[group];
+ INIT_LIST_HEAD(&led->controls);
+ device_initialize(&led->dev);
+ led->dev.parent = &snd_ctl_led_dev;
+ led->dev.groups = snd_ctl_led_dev_attr_groups;
+ dev_set_name(&led->dev, led->name);
+ if (device_add(&led->dev)) {
+ put_device(&led->dev);
+ for (; group > 0; group--) {
+ led = &snd_ctl_leds[group - 1];
+ device_del(&led->dev);
+ }
+ device_del(&snd_ctl_led_dev);
+ return -ENOMEM;
+ }
+ }
+ snd_ctl_register_layer(&snd_ctl_led_lops);
+ return 0;
+}
+
+static void __exit snd_ctl_led_exit(void)
+{
+ struct snd_ctl_led *led;
+ struct snd_card *card;
+ unsigned int group, card_number;
+
+ snd_ctl_disconnect_layer(&snd_ctl_led_lops);
+ for (card_number = 0; card_number < SNDRV_CARDS; card_number++) {
+ if (!snd_ctl_led_card_valid[card_number])
+ continue;
+ card = snd_card_ref(card_number);
+ if (card) {
+ snd_ctl_led_sysfs_remove(card);
+ snd_card_unref(card);
+ }
+ }
+ for (group = 0; group < MAX_LED; group++) {
+ led = &snd_ctl_leds[group];
+ device_del(&led->dev);
+ }
+ device_del(&snd_ctl_led_dev);
+ snd_ctl_led_clean(NULL);
+}
+
+module_init(snd_ctl_led_init)
+module_exit(snd_ctl_led_exit)
diff --git a/sound/core/init.c b/sound/core/init.c
index 45f4b01de23f..ef41f5b3a240 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -398,10 +398,8 @@ int snd_card_disconnect(struct snd_card *card)
return 0;
}
card->shutdown = 1;
- spin_unlock(&card->files_lock);
/* replace file->f_op with special dummy operations */
- spin_lock(&card->files_lock);
list_for_each_entry(mfile, &card->files_list, list) {
/* it's critical part, use endless loop */
/* we have no room to fail */
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
index 142fc751a847..86c39ee01aaa 100644
--- a/sound/core/oss/pcm_oss.c
+++ b/sound/core/oss/pcm_oss.c
@@ -3069,8 +3069,12 @@ static void snd_pcm_oss_proc_done(struct snd_pcm *pcm)
}
}
#else /* !CONFIG_SND_VERBOSE_PROCFS */
-#define snd_pcm_oss_proc_init(pcm)
-#define snd_pcm_oss_proc_done(pcm)
+static inline void snd_pcm_oss_proc_init(struct snd_pcm *pcm)
+{
+}
+static inline void snd_pcm_oss_proc_done(struct snd_pcm *pcm)
+{
+}
#endif /* CONFIG_SND_VERBOSE_PROCFS */
/*
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
index 289dd1fd8fe7..542a75babdee 100644
--- a/sound/core/pcm_memory.c
+++ b/sound/core/pcm_memory.c
@@ -176,6 +176,10 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
substream->dma_buffer.dev.dev,
size, &new_dmab) < 0) {
buffer->error = -ENOMEM;
+ pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n",
+ substream->pcm->card->number, substream->pcm->device,
+ substream->stream ? 'c' : 'p', substream->number,
+ substream->pcm->name, size);
return;
}
substream->buffer_bytes_max = size;
@@ -210,7 +214,9 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream)
}
#else /* !CONFIG_SND_VERBOSE_PROCFS */
-#define preallocate_info_init(s)
+static inline void preallocate_info_init(struct snd_pcm_substream *substream)
+{
+}
#endif /* CONFIG_SND_VERBOSE_PROCFS */
/*
@@ -400,6 +406,10 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
substream->dma_buffer.dev.dev,
size, dmab) < 0) {
kfree(dmab);
+ pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n",
+ substream->pcm->card->number, substream->pcm->device,
+ substream->stream ? 'c' : 'p', substream->number,
+ substream->pcm->name, size);
return -ENOMEM;
}
}
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 17a85f4815d5..8dbe86cf2e4f 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -1425,7 +1425,7 @@ static int snd_pcm_do_stop(struct snd_pcm_substream *substream,
substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
substream->runtime->stop_operating = true;
}
- return 0; /* unconditonally stop all substreams */
+ return 0; /* unconditionally stop all substreams */
}
static void snd_pcm_post_stop(struct snd_pcm_substream *substream,
@@ -1469,7 +1469,7 @@ EXPORT_SYMBOL(snd_pcm_stop);
* After stopping, the state is changed to SETUP.
* Unlike snd_pcm_stop(), this affects only the given stream.
*
- * Return: Zero if succesful, or a negative error code.
+ * Return: Zero if successful, or a negative error code.
*/
int snd_pcm_drain_done(struct snd_pcm_substream *substream)
{
diff --git a/sound/core/seq_device.c b/sound/core/seq_device.c
index 7ed13cb32ef8..382275c5b193 100644
--- a/sound/core/seq_device.c
+++ b/sound/core/seq_device.c
@@ -133,10 +133,19 @@ void snd_seq_device_load_drivers(void)
flush_work(&autoload_work);
}
EXPORT_SYMBOL(snd_seq_device_load_drivers);
-#define cancel_autoload_drivers() cancel_work_sync(&autoload_work)
+
+static inline void cancel_autoload_drivers(void)
+{
+ cancel_work_sync(&autoload_work);
+}
#else
-#define queue_autoload_drivers() /* NOP */
-#define cancel_autoload_drivers() /* NOP */
+static inline void queue_autoload_drivers(void)
+{
+}
+
+static inline void cancel_autoload_drivers(void)
+{
+}
#endif
/*