OSDN Git Service

ucm: add LibraryConfig support
authorJaroslav Kysela <perex@perex.cz>
Mon, 12 Apr 2021 16:09:21 +0000 (18:09 +0200)
committerJaroslav Kysela <perex@perex.cz>
Tue, 13 Apr 2021 15:14:28 +0000 (17:14 +0200)
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>
include/local.h
include/use-case.h
src/control/control.c
src/pcm/pcm.c
src/rawmidi/rawmidi.c
src/seq/seq.c
src/timer/timer.c
src/ucm/main.c
src/ucm/parser.c
src/ucm/ucm_local.h
src/ucm/utils.c

index ed6ba93..4e7d88a 100644 (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
index 13e5e3a..ec1a97b 100644 (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}
index 602735d..ed986e5 100644 (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;
index a57ce5d..09df0f1 100644 (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;
index 1b5f852..55f4482 100644 (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;
index afc8842..f051426 100644 (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;
index 670becd..6fc710b 100644 (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;
index 231ef4a..385ee5e 100644 (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;
@@ -1977,6 +2024,31 @@ static int get_value(snd_use_case_mgr_t *uc_mgr,
 }
 
 /**
+ * \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
  * \param identifier 
@@ -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;
index 169dbef..6b824d1 100644 (file)
@@ -31,6 +31,7 @@
  */
 
 #include "ucm_local.h"
+#include <stdbool.h>
 #include <dirent.h>
 #include <limits.h>
 
@@ -421,6 +422,128 @@ int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
 }
 
 /*
+ * 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
  */
 static int parse_transition(snd_use_case_mgr_t *uc_mgr,
@@ -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);
index 1ae143b..0c3f21e 100644 (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,
index b331fb7..b212422 100644 (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;
+}