]> git.alsa-project.org Git - alsa-lib.git/commitdiff
ucm: add sys-card substitution
authorJaroslav Kysela <perex@perex.cz>
Thu, 13 Mar 2025 16:46:58 +0000 (17:46 +0100)
committerJaroslav Kysela <perex@perex.cz>
Thu, 13 Mar 2025 17:28:05 +0000 (18:28 +0100)
It may be useful to check additional sysfs parameters like USB descriptors
to determine the exact hardware capabilities.

Introduce 'sys-card' substitution and 'sys' substitution to allow data
fetching from given range. Also, add conversion to hexadecimal format
when the source file has binary contents.

Example - fetch bytes from positions 0x10..0x15 (6 bytes):

  Define.Bytes1 "${sys-card:[type=hex,pos=0x10,size=6]device/../descriptors}"

Example - fetch one byte from position 0x22:

  Define.Bytes2 "${sys-card:[type=hex,pos=0x22]device/../descriptors}"

Replace type=hex or omit this variable settings to work with ASCII
characters.

Link: https://github.com/alsa-project/alsa-ucm-conf/issues/444
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
src/ucm/ucm_local.h
src/ucm/ucm_subs.c

index 464d11543a77de68800c28db04c7a30b9ee89c49..b274277d63a0016b446ebb576785616eb1c5ef3a 100644 (file)
@@ -40,7 +40,7 @@
 #include <pthread.h>
 #include "use-case.h"
 
-#define SYNTAX_VERSION_MAX     7
+#define SYNTAX_VERSION_MAX     8
 
 #define MAX_CARD_SHORT_NAME    32
 #define MAX_CARD_LONG_NAME     80
index 2b01033863b493ffd19c185666de327bfb2b9d2b..d1dc581930c7b1ac7ff5d58c631c621101cbba73 100644 (file)
@@ -30,6 +30,8 @@
 #include <limits.h>
 #include <regex.h>
 
+static unsigned char _hex_table[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
 static char *rval_open_name(snd_use_case_mgr_t *uc_mgr)
 {
        const char *name;
@@ -504,20 +506,117 @@ static char *rval_env(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char *i
        return NULL;
 }
 
-static char *rval_sysfs(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char *id)
+#define RANGE_TYPE_ASCII 0
+#define RANGE_TYPE_HEX   1
+
+static int parse_position(snd_config_t *config, const char *name, ssize_t *pos, bool optional)
+{
+       snd_config_t *d;
+       const char *s;
+       long v;
+
+       if (snd_config_search(config, name, &d)) {
+               if (optional) {
+                       *pos = -1;
+                       return 0;
+               }
+               uc_error("Unable to find field '%s'", name);
+               return -1;
+       }
+       if (!snd_config_get_integer(d, &v))
+               goto fin;
+       if (snd_config_get_string(d, &s))
+               return -1;
+       if (safe_strtol(s, &v)) {
+               uc_error("Unable to parse position '%s'", s);
+               return -1;
+       }
+fin:
+       *pos = v;
+       return 0;
+}
+
+static int parse_range(const char *cfg, int *type, ssize_t *pos, ssize_t *size)
+{
+       snd_config_t *config, *d;
+       int err, retval = 0;
+       const char *s;
+
+       err = snd_config_load_string(&config, cfg, 0);
+       if (err < 0) {
+               uc_error("The range arguments '%s' are invalid", cfg);
+               return -1;
+       }
+       if (snd_config_search(config, "type", &d)) {
+               *type = RANGE_TYPE_ASCII;
+       } else {
+               if (snd_config_get_string(d, &s))
+                       goto null;
+               if (strcasecmp(s, "ascii") == 0) {
+                       *type = RANGE_TYPE_ASCII;
+               } else if (strcasecmp(s, "hex") == 0) {
+                       *type = RANGE_TYPE_HEX;
+               } else {
+                       uc_error("Unknown range type '%s'", s);
+               }
+       }
+       *pos = 0;
+       *size = -1;
+       if (parse_position(config, "pos", pos, false) ||
+           parse_position(config, "size", size, true)) {
+               retval = -1;
+               goto null;
+       }
+
+       if (*size <= 0)
+               *size = 1;
+       if (*pos < 0) {
+               uc_error("Invalid start position");
+               retval = -1;
+               goto null;
+       }
+
+null:
+       snd_config_delete(config);
+       return retval;
+}
+
+static char *rval_sysfs_main(snd_use_case_mgr_t *uc_mgr, const char *top_path, const char *id)
 {
        char path[PATH_MAX], link[PATH_MAX + 1];
        struct stat64 sb;
-       ssize_t len;
-       const char *e;
-       int fd;
+       ssize_t len, range_start = -1, range_size = -1;
+       const char *e, *s;
+       int fd, type = RANGE_TYPE_ASCII;
 
        e = uc_mgr_sysfs_root();
        if (e == NULL)
                return NULL;
+       if (id[0] == '[') {
+               if (uc_mgr->conf_format < 8) {
+                       uc_error("Sysfs ranges are supported in v8+ syntax");
+                       return NULL;
+               }
+               s = strchr(id, ']');
+               if (s == NULL)
+                       return NULL;
+               len = s - id - 1;
+               if ((size_t)(len - 1) > sizeof(link) - 1)
+                       return NULL;
+               strncpy(link, id + 1, len);
+               link[len] = '\0';
+               if (parse_range(link, &type, &range_start, &range_size)) {
+                       uc_error("sysfs: cannot parse hex range '%s'", link);
+                       return NULL;
+               }
+               id = s + 1;
+       }
        if (id[0] == '/')
                id++;
-       snprintf(path, sizeof(path), "%s/%s", e, id);
+       if (top_path)
+               snprintf(path, sizeof(path), "%s/%s/%s", e, top_path, id);
+       else
+               snprintf(path, sizeof(path), "%s/%s", e, id);
        if (lstat64(path, &sb) != 0)
                return NULL;
        if (S_ISLNK(sb.st_mode)) {
@@ -542,18 +641,64 @@ static char *rval_sysfs(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED, const char
                uc_error("sysfs open failed for '%s' (%d)", path, errno);
                return NULL;
        }
-       len = read(fd, path, sizeof(path)-1);
+       len = sizeof(path) - 1;
+       if (range_start > 0 && lseek(fd, range_start, SEEK_SET) != range_start) {
+               uc_error("sysfs seek failed (%d)", errno);
+               close(fd);
+               return NULL;
+       }
+       if (range_size > 0) {
+               if (range_size > len) {
+                       uc_error("sysfs EOB for '%s'", path);
+                       close(fd);
+                       return NULL;
+               } else {
+                       len = range_size;
+               }
+       }
+       len = read(fd, path, len);
        close(fd);
        if (len < 0) {
                uc_error("sysfs unable to read value '%s' (%d)", path, errno);
                return NULL;
        }
+       if (type == RANGE_TYPE_HEX && range_start >= 0) {
+               char *m = malloc(len * 2 + 1);
+               ssize_t idx;
+               if (m == NULL)
+                       return NULL;
+               for (idx = 0; idx < len; idx++) {
+                       m[(idx * 2) + 0] = _hex_table[((unsigned char)path[idx]) >> 4];
+                       m[(idx * 2) + 1] = _hex_table[((unsigned char)path[idx]) & 0x0f];
+               }
+               m[len * 2] = '\0';
+               return m;
+       }
        while (len > 0 && path[len-1] == '\n')
                len--;
        path[len] = '\0';
        return strdup(path);
 }
 
+static char *rval_sysfs(snd_use_case_mgr_t *uc_mgr, const char *id)
+{
+       return rval_sysfs_main(uc_mgr, NULL, id);
+}
+
+static char *rval_sysfs_card(snd_use_case_mgr_t *uc_mgr, const char *id)
+{
+       char top_path[32], *s;
+
+       if (uc_mgr->conf_format < 8) {
+               uc_error("sys-card is supported in v8+ syntax");
+               return NULL;
+       }
+       s = get_card_number(uc_mgr_get_master_ctl(uc_mgr));
+       snprintf(top_path, sizeof(top_path), "class/sound/card%s", s);
+       free(s);
+       return rval_sysfs_main(uc_mgr, top_path, id);
+}
+
 static char *rval_var(snd_use_case_mgr_t *uc_mgr, const char *id)
 {
        const char *v;
@@ -751,6 +896,7 @@ __std:
                MATCH_VARIABLE(value, "${CardComponents}", rval_card_components, true);
                MATCH_VARIABLE2(value, "${env:", rval_env, false);
                MATCH_VARIABLE2(value, "${sys:", rval_sysfs, false);
+               MATCH_VARIABLE2(value, "${sys-card:", rval_sysfs_card, false);
                MATCH_VARIABLE2(value, "${var:", rval_var, true);
                MATCH_VARIABLE2(value, "${eval:", rval_eval, false);
                MATCH_VARIABLE2(value, "${find-card:", rval_card_lookup, false);