ucm: add LibraryConfig support

This commit allows to define private alsa-lib's configuration. When
the configuration is present, the device values ("PlaybackCTL",
"CaptureCTL", "PlaybackMixer", "CaptureMixer", "CapturePCM")
are prefixed with '_ucmHEXA.' string where HEXA is replaced by the
unique hexadecimal number identifying the opened ucm manager handle.

    Syntax 4

    LibraryConfig.a_label.SubstiConfig {
            # substituted library configuration like:
            usr_share_dir "${ConfLibDir}"
    }

    LibraryConfig.b_label.Config {
            # non-substituted library configuration like:
            usr_share_dir "/usr/share/alsa"
    }

    The File counterparts:

    LibraryConfig.c_label.SubstiFile "/some/path"
    LibraryConfig.d_label.File "/some/path"

Note that for files the contents is substituted on the request,
but the file name is always substituted (useful for ${ConfDir} etc.).

The private configuration is not saved or preserved. It's life time
belongs to the opened ucm manager handle.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Jaroslav Kysela 2021-04-12 18:09:21 +02:00
parent 3e0140088c
commit 8f5779eb3f
11 changed files with 400 additions and 36 deletions

View file

@ -374,4 +374,11 @@ int _snd_config_load_with_include(snd_config_t *config, snd_input_t *in,
void *INTERNAL(snd_dlopen)(const char *name, int mode, char *errbuf, size_t errbuflen);
#endif
const char *uc_mgr_alibcfg_by_device(snd_config_t **config, const char *name);
static inline int _snd_is_ucm_device(const char *name)
{
return name && name[0] == '_' && name[1] == 'u' && name[2] == 'c' && name[3] == 'm';
}
#endif

View file

@ -257,6 +257,7 @@ int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr,
* - NULL - return current card
* - _verb - return current verb
* - _file - return configuration file loaded for current card
* - _alibcfg - return private alsa-lib's configuration for current card
*
* - [=]{NAME}[/[{modifier}|{/device}][/{verb}]]
* - value identifier {NAME}

View file

@ -1520,9 +1520,15 @@ int snd_ctl_open(snd_ctl_t **ctlp, const char *name, int mode)
int err;
assert(ctlp && name);
err = snd_config_update_ref(&top);
if (err < 0)
return err;
if (_snd_is_ucm_device(name)) {
name = uc_mgr_alibcfg_by_device(&top, name);
if (name == NULL)
return -ENODEV;
} else {
err = snd_config_update_ref(&top);
if (err < 0)
return err;
}
err = snd_ctl_open_noupdate(ctlp, top, name, mode, 0);
snd_config_unref(top);
return err;

View file

@ -2686,9 +2686,15 @@ int snd_pcm_open(snd_pcm_t **pcmp, const char *name,
int err;
assert(pcmp && name);
err = snd_config_update_ref(&top);
if (err < 0)
return err;
if (_snd_is_ucm_device(name)) {
name = uc_mgr_alibcfg_by_device(&top, name);
if (name == NULL)
return -ENODEV;
} else {
err = snd_config_update_ref(&top);
if (err < 0)
return err;
}
err = snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);
snd_config_unref(top);
return err;

View file

@ -304,9 +304,15 @@ int snd_rawmidi_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
int err;
assert((inputp || outputp) && name);
err = snd_config_update_ref(&top);
if (err < 0)
return err;
if (_snd_is_ucm_device(name)) {
name = uc_mgr_alibcfg_by_device(&top, name);
if (name == NULL)
return -ENODEV;
} else {
err = snd_config_update_ref(&top);
if (err < 0)
return err;
}
err = snd_rawmidi_open_noupdate(inputp, outputp, top, name, mode);
snd_config_unref(top);
return err;

View file

@ -978,9 +978,15 @@ int snd_seq_open(snd_seq_t **seqp, const char *name,
int err;
assert(seqp && name);
err = snd_config_update_ref(&top);
if (err < 0)
return err;
if (_snd_is_ucm_device(name)) {
name = uc_mgr_alibcfg_by_device(&top, name);
if (name == NULL)
return -ENODEV;
} else {
err = snd_config_update_ref(&top);
if (err < 0)
return err;
}
err = snd_seq_open_noupdate(seqp, top, name, streams, mode, 0);
snd_config_unref(top);
return err;

View file

@ -205,9 +205,15 @@ int snd_timer_open(snd_timer_t **timer, const char *name, int mode)
int err;
assert(timer && name);
err = snd_config_update_ref(&top);
if (err < 0)
return err;
if (_snd_is_ucm_device(name)) {
name = uc_mgr_alibcfg_by_device(&top, name);
if (name == NULL)
return -ENODEV;
} else {
err = snd_config_update_ref(&top);
if (err < 0)
return err;
}
err = snd_timer_open_noupdate(timer, top, name, mode);
snd_config_unref(top);
return err;

View file

@ -570,6 +570,38 @@ static int execute_sysw(const char *sysw)
return 0;
}
static int rewrite_device_value(snd_use_case_mgr_t *uc_mgr, const char *name, char **value)
{
char *sval;
size_t l;
static const char **s, *_prefix[] = {
"PlaybackCTL",
"CaptureCTL",
"PlaybackMixer",
"CaptureMixer",
"PlaybackPCM",
"CapturePCM",
NULL
};
for (s = _prefix; *s && *value; s++) {
if (strcmp(*s, name) != 0)
continue;
l = strlen(*value) + 9 + 1;
sval = malloc(l);
if (sval == NULL) {
free(*value);
*value = NULL;
return -ENOMEM;
}
snprintf(sval, l, "_ucm%04X.%s", uc_mgr->ucm_card_number, *value);
free(*value);
*value = sval;
break;
}
return 0;
}
/**
* \brief Execute the sequence
* \param uc_mgr Use case manager
@ -596,6 +628,8 @@ static int execute_sequence(snd_use_case_mgr_t *uc_mgr,
cdev = strdup(s->data.cdev);
if (cdev == NULL)
goto __fail_nomem;
if (rewrite_device_value(uc_mgr, "PlaybackCTL", &cdev))
goto __fail_nomem;
break;
case SEQUENCE_ELEMENT_TYPE_CSET:
case SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE:
@ -1259,10 +1293,18 @@ int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr,
INIT_LIST_HEAD(&mgr->variable_list);
pthread_mutex_init(&mgr->mutex, NULL);
err = uc_mgr_card_open(mgr);
if (err < 0)
goto _err;
err = snd_config_top(&mgr->local_config);
if (err < 0)
goto _err;
mgr->card_name = strdup(card_name);
if (mgr->card_name == NULL) {
free(mgr);
return -ENOMEM;
err = -ENOMEM;
goto _err;
}
/* get info on use_cases and verify against card */
@ -1321,6 +1363,7 @@ int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr)
*/
int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr)
{
uc_mgr_card_close(uc_mgr);
uc_mgr_free(uc_mgr);
return 0;
@ -1868,6 +1911,7 @@ static int get_value1(snd_use_case_mgr_t *uc_mgr, char **value,
{
struct ucm_value *val;
struct list_head *pos;
int err;
if (!value_list)
return -ENOENT;
@ -1881,7 +1925,10 @@ static int get_value1(snd_use_case_mgr_t *uc_mgr, char **value,
return -ENOMEM;
return 0;
}
return uc_mgr_get_substituted_value(uc_mgr, value, val->data);
err = uc_mgr_get_substituted_value(uc_mgr, value, val->data);
if (err < 0)
return err;
return rewrite_device_value(uc_mgr, val->name, value);
}
}
return -ENOENT;
@ -1976,6 +2023,31 @@ static int get_value(snd_use_case_mgr_t *uc_mgr,
return -ENOENT;
}
/**
* \brief Get private alsa-lib configuration (ASCII)
* \param uc_mgr Use case manager
* \param str Returned value string
* \return Zero on success (value is filled), otherwise a negative error code
*/
static int get_alibcfg(snd_use_case_mgr_t *uc_mgr, char **str)
{
snd_output_t *out;
size_t size;
int err;
err = snd_output_buffer_open(&out);
if (err < 0)
return err;
err = snd_config_save(uc_mgr->local_config, out);
if (err >= 0) {
size = snd_output_buffer_steal(out, str);
if (*str)
(*str)[size] = '\0';
}
snd_output_close(out);
return 0;
}
/**
* \brief Get current - string
* \param uc_mgr Use case manager
@ -2029,9 +2101,10 @@ int snd_use_case_get(snd_use_case_mgr_t *uc_mgr,
}
err = 0;
} else if (strcmp(identifier, "_alibcfg") == 0) {
err = get_alibcfg(uc_mgr, (char **)value);
} else if (identifier[0] == '_') {
err = -ENOENT;
goto __end;
} else {
if (identifier[0] == '=') {
exact = 1;

View file

@ -31,6 +31,7 @@
*/
#include "ucm_local.h"
#include <stdbool.h>
#include <dirent.h>
#include <limits.h>
@ -420,6 +421,128 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
return 0;
}
/*
* Parse one item for alsa-lib config
*/
static int parse_libconfig1(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *n, *config = NULL;
const char *id, *file = NULL;
bool substfile = false, substconfig = false;
int err;
if (snd_config_get_id(cfg, &id) < 0)
return -EINVAL;
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for %s", id);
return -EINVAL;
}
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
return -EINVAL;
if (strcmp(id, "File") == 0 ||
strcmp(id, "SubstiFile") == 0) {
substfile = id[0] == 'S';
err = snd_config_get_string(n, &file);
if (err < 0)
return err;
continue;
}
if (strcmp(id, "Config") == 0 ||
strcmp(id, "SubstiConfig") == 0) {
substconfig = id[0] == 'S';
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND)
return -EINVAL;
config = n;
continue;
}
uc_error("unknown field %s", id);
return -EINVAL;
}
if (file) {
if (substfile) {
snd_config_t *cfg;
err = uc_mgr_config_load(uc_mgr->conf_format, file, &cfg);
if (err < 0)
return err;
err = uc_mgr_substitute_tree(uc_mgr, cfg);
if (err < 0) {
snd_config_delete(config);
return err;
}
err = snd_config_merge(uc_mgr->local_config, cfg, 1);
if (err < 0) {
snd_config_delete(cfg);
return err;
}
} else {
char filename[PATH_MAX];
ucm_filename(filename, sizeof(filename), uc_mgr->conf_format,
file[0] == '/' ? NULL : uc_mgr->conf_dir_name,
file);
err = uc_mgr_config_load_into(uc_mgr->conf_format, filename, uc_mgr->local_config);
if (err < 0)
return err;
}
}
if (config) {
if (substconfig) {
err = uc_mgr_substitute_tree(uc_mgr, config);
if (err < 0) {
snd_config_delete(config);
return err;
}
}
err = snd_config_merge(uc_mgr->local_config, config, 1);
if (err < 0) {
snd_config_delete(config);
return err;
}
}
return 0;
}
/*
* Parse alsa-lib config
*/
static int parse_libconfig(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id;
int err;
if (snd_config_get_id(cfg, &id) < 0)
return -EINVAL;
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for %s", id);
return -EINVAL;
}
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
err = parse_libconfig1(uc_mgr, n);
if (err < 0)
return err;
}
return 0;
}
/*
* Parse transition
*/
@ -1644,6 +1767,7 @@ static int parse_verb_file(snd_use_case_mgr_t *uc_mgr,
file);
goto _err;
}
continue;
}
/* device remove */
@ -1654,6 +1778,17 @@ static int parse_verb_file(snd_use_case_mgr_t *uc_mgr,
file);
goto _err;
}
continue;
}
/* alsa-lib configuration */
if (uc_mgr->conf_format > 3 && strcmp(id, "LibraryConfig") == 0) {
err = parse_libconfig(uc_mgr, n);
if (err < 0) {
uc_error("error: failed to parse LibConfig");
return err;
}
continue;
}
}
@ -1962,6 +2097,16 @@ static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
continue;
}
/* alsa-lib configuration */
if (uc_mgr->conf_format > 3 && strcmp(id, "LibraryConfig") == 0) {
err = parse_libconfig(uc_mgr, n);
if (err < 0) {
uc_error("error: failed to parse LibraryConfig");
return err;
}
continue;
}
/* error */
if (strcmp(id, "Error") == 0)
return error_node(uc_mgr, n);

View file

@ -222,6 +222,10 @@ struct snd_use_case_mgr {
char *conf_dir_name;
char *comment;
int conf_format;
unsigned int ucm_card_number;
/* UCM cards list */
struct list_head cards_list;
/* use case verb, devices and modifier configs parsed from files */
struct list_head verb_list;
@ -253,6 +257,9 @@ struct snd_use_case_mgr {
/* list of opened control devices */
struct list_head ctl_list;
/* local library configuration */
snd_config_t *local_config;
/* Components don't define cdev, the card device. When executing
* a sequence of a component device, ucm manager enters component
* domain and needs to provide cdev to the component. This cdev
@ -275,6 +282,7 @@ void uc_mgr_stdout(const char *fmt, ...);
const char *uc_mgr_sysfs_root(void);
const char *uc_mgr_config_dir(int format);
int uc_mgr_config_load_into(int format, const char *file, snd_config_t *cfg);
int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg);
int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr, const char *file, snd_config_t **cfg);
int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr);
@ -291,6 +299,14 @@ void uc_mgr_free_transition_element(struct transition_sequence *seq);
void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr);
void uc_mgr_free(snd_use_case_mgr_t *uc_mgr);
static inline int uc_mgr_has_local_config(snd_use_case_mgr_t *uc_mgr)
{
return uc_mgr && snd_config_iterator_first(uc_mgr->local_config);
}
int uc_mgr_card_open(snd_use_case_mgr_t *uc_mgr);
void uc_mgr_card_close(snd_use_case_mgr_t *uc_mgr);
int uc_mgr_open_ctl(snd_use_case_mgr_t *uc_mgr,
struct ctl_list **ctl_list,
const char *device,

View file

@ -341,49 +341,54 @@ const char *uc_mgr_config_dir(int format)
return path;
}
int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg)
int uc_mgr_config_load_into(int format, const char *file, snd_config_t *top)
{
FILE *fp;
snd_input_t *in;
snd_config_t *top;
const char *default_paths[2];
int err;
fp = fopen(file, "r");
if (!fp) {
err = -errno;
__err0:
__err_open:
uc_error("could not open configuration file %s", file);
return err;
}
err = snd_input_stdio_attach(&in, fp, 1);
if (err < 0)
goto __err0;
err = snd_config_top(&top);
if (err < 0)
goto __err1;
goto __err_open;
default_paths[0] = uc_mgr_config_dir(format);
default_paths[1] = NULL;
err = _snd_config_load_with_include(top, in, 0, default_paths);
if (err < 0) {
uc_error("could not load configuration file %s", file);
goto __err2;
if (in)
snd_input_close(in);
return err;
}
err = snd_input_close(in);
if (err < 0)
return err;
return 0;
}
int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg)
{
snd_config_t *top;
int err;
err = snd_config_top(&top);
if (err < 0)
return err;
err = uc_mgr_config_load_into(format, file, top);
if (err < 0) {
in = NULL;
goto __err2;
snd_config_delete(top);
return err;
}
*cfg = top;
return 0;
__err2:
snd_config_delete(top);
__err1:
if (in)
snd_input_close(in);
return err;
}
void uc_mgr_free_value(struct list_head *base)
@ -725,8 +730,95 @@ void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr)
void uc_mgr_free(snd_use_case_mgr_t *uc_mgr)
{
snd_config_delete(uc_mgr->local_config);
uc_mgr_free_verb(uc_mgr);
uc_mgr_free_ctl_list(uc_mgr);
free(uc_mgr->card_name);
free(uc_mgr);
}
/*
* UCM card list stuff
*/
static pthread_mutex_t ucm_cards_mutex = PTHREAD_MUTEX_INITIALIZER;
static LIST_HEAD(ucm_cards);
static unsigned int ucm_card_assign;
static snd_use_case_mgr_t *uc_mgr_card_find(unsigned int card_number)
{
struct list_head *pos;
snd_use_case_mgr_t *uc_mgr;
list_for_each(pos, &ucm_cards) {
uc_mgr = list_entry(pos, snd_use_case_mgr_t, cards_list);
if (uc_mgr->ucm_card_number == card_number)
return uc_mgr;
}
return NULL;
}
int uc_mgr_card_open(snd_use_case_mgr_t *uc_mgr)
{
unsigned int prev;
pthread_mutex_lock(&ucm_cards_mutex);
prev = ucm_card_assign++;
while (uc_mgr_card_find(ucm_card_assign)) {
ucm_card_assign++;
ucm_card_assign &= 0xffff;
if (ucm_card_assign == prev) {
pthread_mutex_unlock(&ucm_cards_mutex);
return -ENOMEM;
}
}
uc_mgr->ucm_card_number = ucm_card_assign;
list_add(&uc_mgr->cards_list, &ucm_cards);
pthread_mutex_unlock(&ucm_cards_mutex);
return 0;
}
void uc_mgr_card_close(snd_use_case_mgr_t *uc_mgr)
{
pthread_mutex_lock(&ucm_cards_mutex);
list_del(&uc_mgr->cards_list);
pthread_mutex_unlock(&ucm_cards_mutex);
}
/**
* \brief Get library configuration based on the private ALSA device name
* \param name[in] ALSA device name
* \retval config A configuration tree or NULL
*
* The returned configuration (non-NULL) should be unreferenced using
* snd_config_unref() call.
*/
const char *uc_mgr_alibcfg_by_device(snd_config_t **top, const char *name)
{
char buf[5];
long card_num;
snd_config_t *config;
snd_use_case_mgr_t *uc_mgr;
int err;
if (strncmp(name, "_ucm", 4) || strlen(name) < 12 || name[8] != '.')
return NULL;
strncpy(buf, name + 4, 4);
buf[4] = '\0';
err = safe_strtol(buf, &card_num);
if (err < 0 || card_num < 0 || card_num > 0xffff)
return NULL;
config = NULL;
pthread_mutex_lock(&ucm_cards_mutex);
uc_mgr = uc_mgr_card_find(card_num);
/* non-empty configs are accepted only */
if (uc_mgr_has_local_config(uc_mgr)) {
config = uc_mgr->local_config;
snd_config_ref(config);
}
pthread_mutex_unlock(&ucm_cards_mutex);
if (!config)
return NULL;
*top = config;
return name + 9;
}