ucm: add sys-card substitution

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>
This commit is contained in:
Jaroslav Kysela 2025-03-13 17:46:58 +01:00
parent 0c7086777a
commit 7fbd47ce79
2 changed files with 153 additions and 7 deletions

View 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

View 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);