All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jaroslav Kysela <perex@perex.cz>
To: ALSA development <alsa-devel@alsa-project.org>
Cc: Takashi Iwai <tiwai@suse.de>, Hans de Goede <hdegoede@redhat.com>
Subject: [PATCH v4 6/6] ALSA: led control - add sysfs kcontrol LED marking layer
Date: Wed, 17 Mar 2021 18:29:45 +0100	[thread overview]
Message-ID: <20210317172945.842280-7-perex@perex.cz> (raw)
In-Reply-To: <20210317172945.842280-1-perex@perex.cz>

We need to manage the kcontrol entries association for the LED trigger
from the user space. This patch adds a layer to the sysfs tree like:

/sys/devices/virtual/sound/ctl-led/mic
   + card0
   |  + attach
   |  + detach
   |  ...
   + card1
      + attach
      ...

Operations:

  attach and detach
    - amixer style ID is accepted and easy strings for numid and
      simple names
  reset
    - reset all associated kcontrol entries
  list
    - list associated kcontrol entries (numid values only)

Additional symlinks:

/sys/devices/virtual/sound/ctl-led/mic/card0/card ->
  /sys/class/sound/card0

/sys/class/sound/card0/controlC0/led-mic ->
  /sys/devices/virtual/sound/ctl-led/mic/card0

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
---
 sound/core/control_led.c | 366 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 362 insertions(+), 4 deletions(-)

diff --git a/sound/core/control_led.c b/sound/core/control_led.c
index dfa51d8461e1..d4fb8b873f34 100644
--- a/sound/core/control_led.c
+++ b/sound/core/control_led.c
@@ -24,6 +24,12 @@ enum snd_ctl_led_mode {
 	 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;
@@ -31,6 +37,7 @@ struct snd_ctl_led {
 	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 {
@@ -58,6 +65,9 @@ static struct snd_ctl_led snd_ctl_leds[MAX_LED] = {
 	},
 };
 
+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); \
@@ -222,6 +232,46 @@ static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,
 	}
 }
 
+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;
@@ -230,6 +280,12 @@ static void snd_ctl_led_refresh(void)
 		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;
@@ -241,13 +297,47 @@ static void snd_ctl_led_clean(struct snd_card *card)
 repeat:
 		list_for_each_entry(lctl, &led->controls, list)
 			if (!card || lctl->card == card) {
-				list_del(&lctl->list);
-				kfree(lctl);
+				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;
@@ -264,10 +354,12 @@ static void snd_ctl_led_register(struct snd_card *card)
 		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);
@@ -349,8 +441,264 @@ static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = {
 	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;
+	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) {
+		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++;
+	}
+
+	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
  */
@@ -397,14 +745,24 @@ static int __init snd_ctl_led_init(void)
 static void __exit snd_ctl_led_exit(void)
 {
 	struct snd_ctl_led *led;
-	unsigned int group;
+	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_disconnect_layer(&snd_ctl_led_lops);
 	snd_ctl_led_clean(NULL);
 }
 
-- 
2.29.2

  parent reply	other threads:[~2021-03-17 17:33 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-17 17:29 [PATCH v4 0/6] ALSA: control - add generic LED API Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 1/6] ALSA: control - introduce snd_ctl_notify_one() helper Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 2/6] ALSA: control - add layer registration routines Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 3/6] ALSA: control - add generic LED trigger module as the new control layer Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 4/6] ALSA: HDA - remove the custom implementation for the audio LED trigger Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 5/6] ALSA: control - add sysfs support to the LED trigger module Jaroslav Kysela
2021-03-17 17:29 ` Jaroslav Kysela [this message]
2021-03-19 16:34   ` [PATCH v4 6/6] ALSA: led control - add sysfs kcontrol LED marking layer Hans de Goede
2021-03-19 17:22     ` Takashi Iwai
2021-03-19 17:58       ` Jaroslav Kysela
2021-03-19 22:08       ` Hans de Goede
2021-03-20  7:41         ` Takashi Iwai
2021-03-20  9:17           ` Hans de Goede
2021-03-20  9:48             ` Takashi Iwai
2021-03-22 14:16               ` Jaroslav Kysela
2021-03-23  9:38                 ` Takashi Iwai
2021-03-23  9:49                   ` Takashi Iwai
2021-03-23 10:31                     ` Jaroslav Kysela
2021-03-23 10:42                       ` Hans de Goede
2021-03-23 11:03                         ` Takashi Iwai
2021-03-23 10:50                       ` Takashi Iwai
2021-03-23 11:13                         ` Jaroslav Kysela
2021-03-23 11:34                           ` Takashi Iwai
2021-03-23 12:22                             ` Jaroslav Kysela
2021-03-23 21:39                 ` Curtis Malainey
2021-03-23 22:49                   ` Dylan Reid
2021-03-26  8:07                     ` Takashi Iwai
2021-03-19 18:11     ` Jaroslav Kysela
2021-03-19 22:13       ` Hans de Goede
2021-03-30 15:49 ` [PATCH v4 0/6] ALSA: control - add generic LED API Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210317172945.842280-7-perex@perex.cz \
    --to=perex@perex.cz \
    --cc=alsa-devel@alsa-project.org \
    --cc=hdegoede@redhat.com \
    --cc=tiwai@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.