From 8f5779eb3fe43ba8b61490db5292720413fb2d31 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Mon, 12 Apr 2021 18:09:21 +0200 Subject: [PATCH] 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 --- include/local.h | 7 ++ include/use-case.h | 1 + src/control/control.c | 12 +++- src/pcm/pcm.c | 12 +++- src/rawmidi/rawmidi.c | 12 +++- src/seq/seq.c | 12 +++- src/timer/timer.c | 12 +++- src/ucm/main.c | 81 +++++++++++++++++++++-- src/ucm/parser.c | 145 ++++++++++++++++++++++++++++++++++++++++++ src/ucm/ucm_local.h | 16 +++++ src/ucm/utils.c | 126 +++++++++++++++++++++++++++++++----- 11 files changed, 400 insertions(+), 36 deletions(-) diff --git a/include/local.h b/include/local.h index ed6ba936..4e7d88a0 100644 --- a/include/local.h +++ b/include/local.h @@ -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 diff --git a/include/use-case.h b/include/use-case.h index 13e5e3a3..ec1a97b0 100644 --- a/include/use-case.h +++ b/include/use-case.h @@ -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} diff --git a/src/control/control.c b/src/control/control.c index 602735db..ed986e54 100644 --- a/src/control/control.c +++ b/src/control/control.c @@ -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; diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c index a57ce5d9..09df0f12 100644 --- a/src/pcm/pcm.c +++ b/src/pcm/pcm.c @@ -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; diff --git a/src/rawmidi/rawmidi.c b/src/rawmidi/rawmidi.c index 1b5f8525..55f44821 100644 --- a/src/rawmidi/rawmidi.c +++ b/src/rawmidi/rawmidi.c @@ -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; diff --git a/src/seq/seq.c b/src/seq/seq.c index afc88424..f051426f 100644 --- a/src/seq/seq.c +++ b/src/seq/seq.c @@ -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; diff --git a/src/timer/timer.c b/src/timer/timer.c index 670becd9..6fc710b9 100644 --- a/src/timer/timer.c +++ b/src/timer/timer.c @@ -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; diff --git a/src/ucm/main.c b/src/ucm/main.c index 231ef4a1..385ee5e8 100644 --- a/src/ucm/main.c +++ b/src/ucm/main.c @@ -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; diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 169dbefe..6b824d1c 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -31,6 +31,7 @@ */ #include "ucm_local.h" +#include #include #include @@ -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); diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index 1ae143bc..0c3f21e6 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -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, diff --git a/src/ucm/utils.c b/src/ucm/utils.c index b331fb78..b212422a 100644 --- a/src/ucm/utils.c +++ b/src/ucm/utils.c @@ -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; +} -- 2.47.1