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
* - 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}
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;
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;
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;
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;
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;
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
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:
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 */
*/
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;
{
struct ucm_value *val;
struct list_head *pos;
+ int err;
if (!value_list)
return -ENOENT;
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;
}
/**
+ * \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
}
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;
*/
#include "ucm_local.h"
+#include <stdbool.h>
#include <dirent.h>
#include <limits.h>
}
/*
+ * 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,
file);
goto _err;
}
+ continue;
}
/* device remove */
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;
}
}
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);
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;
/* 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
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);
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,
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)
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;
+}