mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 16:56:26 +00:00
b2e538a982
Using WARN() for showing the error of symlink creations don't give
more information than telling that something goes wrong, since the
usual code path is a lregister callback from each control element
creation. More badly, the use of WARN() rather confuses fuzzer as if
it were serious issues.
This patch downgrades the warning messages to use the normal dev_err()
instead of WARN(). For making it clearer, add the function name to
the prefix, too.
Fixes: a135dfb5de
("ALSA: led control - add sysfs kcontrol LED marking layer")
Reported-by: syzbot+4e7919b09c67ffd198ae@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/675664c7.050a0220.a30f1.018c.GAE@google.com
Link: https://patch.msgid.link/20241209095614.4273-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
790 lines
20 KiB
C
790 lines
20 KiB
C
// 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)
|
|
|
|
#define to_led_card_dev(_dev) \
|
|
container_of(_dev, struct snd_ctl_led_card, dev)
|
|
|
|
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 led_trigger *snd_ctl_ledtrig_audio[NUM_AUDIO_LEDS];
|
|
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;
|
|
scoped_guard(mutex, &snd_ctl_led_mutex) {
|
|
/* the card may not be registered (active) at this point */
|
|
if (card && !snd_ctl_led_card_valid[card->number])
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
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) {
|
|
struct led_trigger *trig = snd_ctl_ledtrig_audio[led->trigger_type];
|
|
|
|
led_trigger_event(trig, 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;
|
|
|
|
guard(mutex)(&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);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T))
|
|
|
|
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 __free(snd_card_unref) = NULL;
|
|
struct snd_kcontrol *kctl;
|
|
struct snd_kcontrol_volatile *vd;
|
|
unsigned int ioff, access, new_access;
|
|
|
|
card = snd_card_ref(card_number);
|
|
if (!card)
|
|
return -ENXIO;
|
|
guard(rwsem_write)(&card->controls_rwsem);
|
|
kctl = snd_ctl_find_id(card, id);
|
|
if (!kctl)
|
|
return -ENOENT;
|
|
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))
|
|
return -EXDEV;
|
|
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);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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_ctl *lctl, *_lctl;
|
|
struct snd_ctl_led *led;
|
|
|
|
for (group = 0; group < MAX_LED; group++) {
|
|
led = &snd_ctl_leds[group];
|
|
list_for_each_entry_safe(lctl, _lctl, &led->controls, list)
|
|
if (!card || lctl->card == card)
|
|
snd_ctl_led_ctl_destroy(lctl);
|
|
}
|
|
}
|
|
|
|
static int snd_ctl_led_reset(int card_number, unsigned int group)
|
|
{
|
|
struct snd_card *card __free(snd_card_unref) = NULL;
|
|
struct snd_ctl_led_ctl *lctl, *_lctl;
|
|
struct snd_ctl_led *led;
|
|
struct snd_kcontrol_volatile *vd;
|
|
bool change = false;
|
|
|
|
card = snd_card_ref(card_number);
|
|
if (!card)
|
|
return -ENXIO;
|
|
|
|
scoped_guard(mutex, &snd_ctl_led_mutex) {
|
|
if (!snd_ctl_led_card_valid[card_number])
|
|
return -ENXIO;
|
|
led = &snd_ctl_leds[group];
|
|
list_for_each_entry_safe(lctl, _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;
|
|
}
|
|
}
|
|
if (change)
|
|
snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
|
|
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;
|
|
scoped_guard(mutex, &snd_ctl_led_mutex)
|
|
snd_ctl_led_card_valid[card->number] = true;
|
|
/* 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);
|
|
scoped_guard(mutex, &snd_ctl_led_mutex) {
|
|
snd_ctl_led_card_valid[card->number] = false;
|
|
snd_ctl_led_clean(card);
|
|
}
|
|
snd_ctl_led_refresh();
|
|
}
|
|
|
|
static void snd_ctl_led_card_release(struct device *dev)
|
|
{
|
|
struct snd_ctl_led_card *led_card = to_led_card_dev(dev);
|
|
|
|
kfree(led_card);
|
|
}
|
|
|
|
static void snd_ctl_led_release(struct device *dev)
|
|
{
|
|
}
|
|
|
|
static void snd_ctl_led_dev_release(struct device *dev)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* sysfs
|
|
*/
|
|
|
|
static ssize_t mode_show(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 = NULL;
|
|
|
|
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 sysfs_emit(buf, "%s\n", str);
|
|
}
|
|
|
|
static ssize_t mode_store(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;
|
|
|
|
scoped_guard(mutex, &snd_ctl_led_mutex)
|
|
led->mode = mode;
|
|
|
|
snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t brightness_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
|
|
struct led_trigger *trig = snd_ctl_ledtrig_audio[led->trigger_type];
|
|
|
|
return sysfs_emit(buf, "%u\n", led_trigger_get_brightness(trig));
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(mode);
|
|
static DEVICE_ATTR_RO(brightness);
|
|
|
|
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, snd_ctl_elem_iface_t *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;
|
|
struct snd_ctl_elem_id id;
|
|
int err;
|
|
|
|
if (strscpy(buf2, buf, sizeof(buf2)) < 0)
|
|
return -E2BIG;
|
|
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';
|
|
strscpy(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 attach_store(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 detach_store(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 reset_store(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 list_show(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 __free(snd_card_unref) = NULL;
|
|
struct snd_ctl_led_ctl *lctl;
|
|
size_t l = 0;
|
|
|
|
card = snd_card_ref(led_card->number);
|
|
if (!card)
|
|
return -ENXIO;
|
|
guard(rwsem_read)(&card->controls_rwsem);
|
|
guard(mutex)(&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)
|
|
continue;
|
|
if (l)
|
|
l += sysfs_emit_at(buf, l, " ");
|
|
l += sysfs_emit_at(buf, l, "%u",
|
|
lctl->kctl->id.numid + lctl->index_offset);
|
|
}
|
|
}
|
|
return l;
|
|
}
|
|
|
|
static DEVICE_ATTR_WO(attach);
|
|
static DEVICE_ATTR_WO(detach);
|
|
static DEVICE_ATTR_WO(reset);
|
|
static DEVICE_ATTR_RO(list);
|
|
|
|
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);
|
|
led_card->dev.release = snd_ctl_led_card_release;
|
|
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);
|
|
if (sysfs_create_link(&card->ctl_dev->kobj, &led_card->dev.kobj,
|
|
link_name))
|
|
dev_err(card->dev,
|
|
"%s: can't create symlink to controlC%i device\n",
|
|
__func__, card->number);
|
|
if (sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj,
|
|
"card"))
|
|
dev_err(card->dev,
|
|
"%s: can't create symlink to card%i\n",
|
|
__func__, card->number);
|
|
|
|
continue;
|
|
cerr:
|
|
put_device(&led_card->dev);
|
|
cerr2:
|
|
dev_err(card->dev, "snd_ctl_led: unable to add card%d", card->number);
|
|
}
|
|
}
|
|
|
|
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_unregister(&led_card->dev);
|
|
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;
|
|
|
|
led_trigger_register_simple("audio-mute", &snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]);
|
|
led_trigger_register_simple("audio-micmute", &snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]);
|
|
|
|
device_initialize(&snd_ctl_led_dev);
|
|
snd_ctl_led_dev.class = &sound_class;
|
|
snd_ctl_led_dev.release = snd_ctl_led_dev_release;
|
|
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.release = snd_ctl_led_release;
|
|
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_unregister(&led->dev);
|
|
}
|
|
device_unregister(&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_unregister(&led->dev);
|
|
}
|
|
device_unregister(&snd_ctl_led_dev);
|
|
snd_ctl_led_clean(NULL);
|
|
|
|
led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]);
|
|
led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]);
|
|
}
|
|
|
|
module_init(snd_ctl_led_init)
|
|
module_exit(snd_ctl_led_exit)
|
|
|
|
MODULE_ALIAS("ledtrig:audio-mute");
|
|
MODULE_ALIAS("ledtrig:audio-micmute");
|