diff mbox series

[alsa-utils] amixer: implement handling of TLV bytes kcontrols

Message ID 20250203183514.2645694-1-jan.dakinevich@salutedevices.com
State New
Headers show
Series [alsa-utils] amixer: implement handling of TLV bytes kcontrols | expand

Commit Message

Jan Dakinevich Feb. 3, 2025, 6:35 p.m. UTC
SND_SOC_BYTES_EXT kcontrols are deprecated and SND_SOC_BYTES_TLV should
be used instead. However, there is no support of these kcontrols in
amixer.

This commit introduces a mechanism to show and write the content of
SND_SOC_BYTES_TLV-based kcontrols.

Example:

  $ amixer cset name="Test" 0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0
  $ amixer cget name="Test"
  numid=42,iface=MIXER,name='Test'
    ; type=BYTES,access=-----RW-,values=10
    : values=0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0

Signed-off-by: Jan Dakinevich <jan.dakinevich@salutedevices.com>
---
 amixer/amixer.c | 180 +++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 141 insertions(+), 39 deletions(-)
diff mbox series

Patch

diff --git a/amixer/amixer.c b/amixer/amixer.c
index 07e9819e5cdb..09f774fe2885 100644
--- a/amixer/amixer.c
+++ b/amixer/amixer.c
@@ -609,6 +609,8 @@  static int show_control(const char *space, snd_hctl_elem_t *elem,
 {
 	int err;
 	unsigned int item, idx, count, *tlv;
+	uint8_t *tlv_bytes = NULL;
+	unsigned int tlv_bytes_count = 0;
 	snd_ctl_elem_type_t type;
 	snd_ctl_elem_id_t *id;
 	snd_ctl_elem_info_t *info;
@@ -628,6 +630,18 @@  static int show_control(const char *space, snd_hctl_elem_t *elem,
 	}
 	count = snd_ctl_elem_info_get_count(info);
 	type = snd_ctl_elem_info_get_type(info);
+	if (type == SND_CTL_ELEM_TYPE_BYTES &&
+	    !snd_ctl_elem_info_is_readable(info) &&
+	    !snd_ctl_elem_info_is_writable(info)) {
+		if (count <= 2 * sizeof(unsigned int))
+			return -EINVAL;
+		tlv_bytes_count = count;
+		count -= 2 * sizeof(unsigned int);
+
+		tlv_bytes = malloc(tlv_bytes_count);
+		if (!tlv_bytes)
+			return -ENOMEM;
+	}
 	printf("%s; type=%s,access=%s,values=%u", space, control_type(info), control_access(info), count);
 	switch (type) {
 	case SND_CTL_ELEM_TYPE_INTEGER:
@@ -661,16 +675,30 @@  static int show_control(const char *space, snd_hctl_elem_t *elem,
 		break;
 	}
 	if (level & LEVEL_BASIC) {
-		if (!snd_ctl_elem_info_is_readable(info))
+		if (snd_ctl_elem_info_is_readable(info)) {
+			err = snd_hctl_elem_read(elem, control);
+			if (err < 0) {
+				error("Control %s element read error: %s\n", card, snd_strerror(err));
+				return err;
+			}
+		} else if (tlv_bytes) {
+			err = snd_hctl_elem_tlv_read(elem, (void *)tlv_bytes, tlv_bytes_count);
+			if (err < 0) {
+				error("Control %s element TLV read error: %s\n", card, snd_strerror(err));
+				free(tlv_bytes);
+				return err;
+			}
+		} else
 			goto __skip_read;
-		if ((err = snd_hctl_elem_read(elem, control)) < 0) {
-			error("Control %s element read error: %s\n", card, snd_strerror(err));
-			return err;
-		}
+
 		printf("%s: values=", space);
 		for (idx = 0; idx < count; idx++) {
 			if (idx > 0)
 				printf(",");
+			if (tlv_bytes) {
+				printf("0x%02x", tlv_bytes[idx + 2 * sizeof(unsigned int)]);
+				continue;
+			}
 			switch (type) {
 			case SND_CTL_ELEM_TYPE_BOOLEAN:
 				printf("%s", snd_ctl_elem_value_get_boolean(control, idx) ? "on" : "off");
@@ -699,17 +727,11 @@  static int show_control(const char *space, snd_hctl_elem_t *elem,
 			}
 		}
 		printf("\n");
+		if (tlv_bytes)
+			goto __skip_tlv;
 	      __skip_read:
 		if (!snd_ctl_elem_info_is_tlv_readable(info))
 			goto __skip_tlv;
-		/* skip ASoC ext bytes controls that may have huge binary TLV data */
-		if (type == SND_CTL_ELEM_TYPE_BYTES &&
-				!snd_ctl_elem_info_is_readable(info) &&
-				!snd_ctl_elem_info_is_writable(info)) {
-			printf("%s; ASoC TLV Byte control, skipping bytes dump\n", space);
-			goto __skip_tlv;
-		}
-
 		tlv = malloc(4096);
 		if ((err = snd_hctl_elem_tlv_read(elem, tlv, 4096)) < 0) {
 			error("Control %s element TLV read error: %s\n", card, snd_strerror(err));
@@ -720,6 +742,7 @@  static int show_control(const char *space, snd_hctl_elem_t *elem,
 		free(tlv);
 	}
       __skip_tlv:
+	free(tlv_bytes);
 	return 0;
 }
 
@@ -1133,16 +1156,110 @@  static int parse_simple_id(const char *str, snd_mixer_selem_id_t *sid)
 	return 0;
 }
 
+static int parse_bytes(const char *str, void *ptr, unsigned int len)
+{
+	char *p;
+	unsigned long tmp;
+	unsigned int idx = 0;
+	unsigned char *data = ptr;
+
+	for (;;) {
+		if (!*str)
+			break;
+		if (idx == len)
+			return -EINVAL;
+
+		tmp = strtoul(str, &p, 0);
+		if (p - str < 1)
+			return -EINVAL;
+		if (*p == ',')
+			str = p + 1;
+		else if (*p == '\0')
+			str = p;
+		else
+			return -EINVAL;
+
+		if (tmp > 255)
+			return -EINVAL;
+
+		data[idx] = tmp;
+		idx++;
+	}
+
+	if (!idx)
+		return -EINVAL;
+
+	return idx;
+}
+
+static int cset_tlv(int argc, char *argv[], snd_ctl_t *handle,
+	snd_ctl_elem_info_t *info, snd_ctl_elem_id_t *id)
+{
+	int err;
+	unsigned int count;
+	unsigned int *tlv = NULL;
+
+	count = snd_ctl_elem_info_get_count(info);
+	if (count <= 2 * sizeof(unsigned int)) {
+		err = -EINVAL;
+		goto out;
+	}
+	tlv = malloc(count);
+	if (!tlv) {
+		err = -ENOMEM;
+		goto out;
+	}
+	err = parse_bytes(argv[1], tlv + 2,
+			  count - 2 * sizeof(unsigned int));
+	if (err < 0)
+		goto out;
+	tlv[0] = 0;
+	tlv[1] = err;
+
+	err = snd_ctl_elem_tlv_write(handle, id, tlv);
+
+out:
+	free(tlv);
+	return err;
+}
+
+static int cset_elem(int argc, char *argv[], snd_ctl_t *handle,
+	snd_ctl_elem_info_t *info, snd_ctl_elem_id_t *id)
+{
+	int err;
+	snd_ctl_elem_value_t *control;
+	snd_ctl_elem_value_alloca(&control);
+
+	snd_ctl_elem_value_set_id(control, id);
+	if ((err = snd_ctl_elem_read(handle, control)) < 0) {
+		if (!ignore_error)
+			error("Cannot read the given element from control %s\n", card);
+		return err;
+	}
+	err = snd_ctl_ascii_value_parse(handle, control, info, argv[1]);
+	if (err < 0) {
+		if (!ignore_error)
+			error("Control %s parse error: %s\n", card, snd_strerror(err));
+		return err;
+	}
+	if ((err = snd_ctl_elem_write(handle, control)) < 0) {
+		if (!ignore_error)
+			error("Control %s element write error: %s\n", card, snd_strerror(err));
+		return err;
+	}
+
+	return 0;
+}
+
 static int cset(int argc, char *argv[], int roflag, int keep_handle)
 {
 	int err;
 	static snd_ctl_t *handle = NULL;
 	snd_ctl_elem_info_t *info;
 	snd_ctl_elem_id_t *id;
-	snd_ctl_elem_value_t *control;
+	snd_ctl_elem_type_t type;
 	snd_ctl_elem_info_alloca(&info);
 	snd_ctl_elem_id_alloca(&id);
-	snd_ctl_elem_value_alloca(&control);
 
 	if (argc < 1) {
 		fprintf(stderr, "Specify a full control identifier: [[iface=<iface>,][name='name',][index=<index>,][device=<device>,][subdevice=<subdevice>]]|[numid=<numid>]\n");
@@ -1175,30 +1292,15 @@  static int cset(int argc, char *argv[], int roflag, int keep_handle)
 	}
 	snd_ctl_elem_info_get_id(info, id);     /* FIXME: Remove it when hctl find works ok !!! */
 	if (!roflag) {
-		snd_ctl_elem_value_set_id(control, id);
-		if ((err = snd_ctl_elem_read(handle, control)) < 0) {
-			if (ignore_error)
-				return 0;
-			error("Cannot read the given element from control %s\n", card);
-			if (! keep_handle) {
-				snd_ctl_close(handle);
-				handle = NULL;
-			}
-			return err;
-		}
-		err = snd_ctl_ascii_value_parse(handle, control, info, argv[1]);
-		if (err < 0) {
- 			if (!ignore_error)
-				error("Control %s parse error: %s\n", card, snd_strerror(err));
-			if (!keep_handle) {
-				snd_ctl_close(handle);
-				handle = NULL;
-			}
-			return ignore_error ? 0 : err;
-		}
-		if ((err = snd_ctl_elem_write(handle, control)) < 0) {
-			if (!ignore_error)
-				error("Control %s element write error: %s\n", card, snd_strerror(err));
+		type = snd_ctl_elem_info_get_type(info);
+		if (type == SND_CTL_ELEM_TYPE_BYTES &&
+		    !snd_ctl_elem_info_is_readable(info) &&
+		    !snd_ctl_elem_info_is_writable(info)) {
+			err = cset_tlv(argc, argv, handle, info, id);
+		} else
+			err = cset_elem(argc, argv, handle, info, id);
+
+		if (err) {
 			if (!keep_handle) {
 				snd_ctl_close(handle);
 				handle = NULL;