alsa-lib/src/ucm/parser.c
2024-04-24 15:19:24 +02:00

3067 lines
68 KiB
C

/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Support for the verb/device/modifier core logic and API,
* command line tool and file parser was kindly sponsored by
* Texas Instruments Inc.
* Support for multiple active modifiers and devices,
* transition sequences, multiple client access and user defined use
* cases was kindly sponsored by Wolfson Microelectronics PLC.
*
* Copyright (C) 2008-2010 SlimLogic Ltd
* Copyright (C) 2010 Wolfson Microelectronics PLC
* Copyright (C) 2010 Texas Instruments Inc.
* Copyright (C) 2010 Red Hat Inc.
* Authors: Liam Girdwood <lrg@slimlogic.co.uk>
* Stefan Schmidt <stefan@slimlogic.co.uk>
* Justin Xu <justinx@slimlogic.co.uk>
* Jaroslav Kysela <perex@perex.cz>
*/
#include "ucm_local.h"
#include <sys/stat.h>
#include <stdbool.h>
#include <dirent.h>
#include <limits.h>
static int filename_filter(const struct dirent64 *dirent);
static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
struct list_head *base,
snd_config_t *cfg);
/*
* compose the absolute ucm filename
*/
static void ucm_filename(char *fn, size_t fn_len, long version,
const char *dir, const char *file)
{
const char *env = getenv(version > 1 ? ALSA_CONFIG_UCM2_VAR : ALSA_CONFIG_UCM_VAR);
if (file[0] == '/')
file++;
if (env == NULL)
snprintf(fn, fn_len, "%s/%s/%s%s%s",
snd_config_topdir(), version > 1 ? "ucm2" : "ucm",
dir ?: "", dir ? "/" : "", file);
else
snprintf(fn, fn_len, "%s/%s%s%s",
env, dir ?: "", dir ? "/" : "", file);
}
/*
*
*/
int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr,
const char *file, snd_config_t **cfg)
{
char filename[PATH_MAX];
int err;
ucm_filename(filename, sizeof(filename), uc_mgr->conf_format,
file[0] == '/' ? NULL : uc_mgr->conf_dir_name,
file);
err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg);
if (err < 0) {
uc_error("error: failed to open file %s: %d", filename, err);
return err;
}
return 0;
}
/*
* Replace mallocated string
*/
static char *replace_string(char **dst, const char *value)
{
free(*dst);
*dst = value ? strdup(value) : NULL;
return *dst;
}
/*
* Parse string
*/
static int parse_string(snd_config_t *n, char **res)
{
int err;
err = snd_config_get_string(n, (const char **)res);
if (err < 0)
return err;
*res = strdup(*res);
if (*res == NULL)
return -ENOMEM;
return 0;
}
/*
* Parse string and substitute
*/
static int parse_string_substitute(snd_use_case_mgr_t *uc_mgr,
snd_config_t *n, char **res)
{
const char *str;
char *s;
int err;
err = snd_config_get_string(n, &str);
if (err < 0)
return err;
err = uc_mgr_get_substituted_value(uc_mgr, &s, str);
if (err >= 0)
*res = s;
return err;
}
/*
* Parse string and substitute
*/
static int parse_string_substitute3(snd_use_case_mgr_t *uc_mgr,
snd_config_t *n, char **res)
{
if (uc_mgr->conf_format < 3)
return parse_string(n, res);
return parse_string_substitute(uc_mgr, n, res);
}
/*
* Parse integer with substitution
*/
static int parse_integer_substitute(snd_use_case_mgr_t *uc_mgr,
snd_config_t *n, long *res)
{
char *s1, *s2;
int err;
err = snd_config_get_ascii(n, &s1);
if (err < 0)
return err;
err = uc_mgr_get_substituted_value(uc_mgr, &s2, s1);
if (err >= 0)
err = safe_strtol(s2, res);
free(s2);
free(s1);
return err;
}
/*
* Parse integer with substitution
*/
static int parse_integer_substitute3(snd_use_case_mgr_t *uc_mgr,
snd_config_t *n, long *res)
{
char *s1, *s2;
int err;
err = snd_config_get_ascii(n, &s1);
if (err < 0)
return err;
if (uc_mgr->conf_format < 3)
s2 = s1;
else
err = uc_mgr_get_substituted_value(uc_mgr, &s2, s1);
if (err >= 0)
err = safe_strtol(s2, res);
if (s1 != s2)
free(s2);
free(s1);
return err;
}
/*
* Parse safe ID
*/
static int parse_is_name_safe(const char *name)
{
if (strchr(name, '.')) {
uc_error("char '.' not allowed in '%s'", name);
return 0;
}
return 1;
}
static int get_string3(snd_use_case_mgr_t *uc_mgr, const char *s1, char **s)
{
if (uc_mgr->conf_format < 3) {
*s = strdup(s1);
if (*s == NULL)
return -ENOMEM;
return 0;
}
return uc_mgr_get_substituted_value(uc_mgr, s, s1);
}
static int parse_get_safe_name(snd_use_case_mgr_t *uc_mgr, snd_config_t *n,
const char *alt, char **name)
{
const char *id;
int err;
if (alt) {
id = alt;
} else {
err = snd_config_get_id(n, &id);
if (err < 0)
return err;
}
err = get_string3(uc_mgr, id, name);
if (err < 0)
return err;
if (!parse_is_name_safe(*name)) {
free(*name);
return -EINVAL;
}
return 0;
}
/*
* Handle 'Error' configuration node.
*/
static int error_node(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
int err;
char *s;
err = parse_string_substitute3(uc_mgr, cfg, &s);
if (err < 0) {
uc_error("error: failed to get Error string");
return err;
}
if (!uc_mgr->suppress_nodev_errors)
uc_error("%s", s);
free(s);
return -ENXIO;
}
/*
*
*/
static int parse_syntax_field(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg, const char *filename)
{
snd_config_t *n;
long l;
int err;
err = snd_config_search(cfg, "Syntax", &n);
if (err < 0) {
uc_error("Syntax field not found in %s", filename);
return -EINVAL;
}
err = snd_config_get_integer(n, &l);
if (err < 0) {
uc_error("Syntax field is invalid in %s", filename);
return err;
}
if (l < 2 || l > SYNTAX_VERSION_MAX) {
uc_error("Incompatible syntax %ld in %s", l, filename);
return -EINVAL;
}
/* delete this field to optimize strcmp() call in the parsing loop */
snd_config_delete(n);
uc_mgr->conf_format = l;
return l;
}
/*
* Evaluate variable regex definitions (in-place delete)
*/
static int evaluate_regex(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *d, *n;
const char *id;
int err;
err = snd_config_search(cfg, "DefineRegex", &d);
if (err == -ENOENT)
return 1;
if (err < 0)
return err;
if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for DefineRegex");
return -EINVAL;
}
if (uc_mgr->conf_format < 3) {
uc_error("DefineRegex is supported in v3+ syntax");
return -EINVAL;
}
snd_config_for_each(i, next, d) {
n = snd_config_iterator_entry(i);
err = snd_config_get_id(n, &id);
if (err < 0)
return err;
if (id[0] == '@') {
uc_error("error: value names starting with '@' are reserved for application variables");
return -EINVAL;
}
err = uc_mgr_define_regex(uc_mgr, id, n);
if (err < 0)
return err;
}
snd_config_delete(d);
return 0;
}
/*
* Evaluate variable definitions (in-place delete)
*/
static int evaluate_define(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *d, *n;
const char *id;
char *var, *s;
int err;
err = snd_config_search(cfg, "Define", &d);
if (err == -ENOENT)
return evaluate_regex(uc_mgr, cfg);
if (err < 0)
return err;
if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for Define");
return -EINVAL;
}
if (uc_mgr->conf_format < 3) {
uc_error("Define is supported in v3+ syntax");
return -EINVAL;
}
snd_config_for_each(i, next, d) {
n = snd_config_iterator_entry(i);
err = snd_config_get_id(n, &id);
if (err < 0)
return err;
err = snd_config_get_ascii(n, &var);
if (err < 0)
return err;
err = uc_mgr_get_substituted_value(uc_mgr, &s, var);
free(var);
if (err < 0)
return err;
if (id[0] == '@') {
free(s);
uc_error("error: value names starting with '@' are reserved for application variables");
return -EINVAL;
}
err = uc_mgr_set_variable(uc_mgr, id, s);
free(s);
if (err < 0)
return err;
}
snd_config_delete(d);
return evaluate_regex(uc_mgr, cfg);
}
/*
* Evaluate macro definitions (in-place delete)
*/
static int evaluate_define_macro(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg)
{
snd_config_t *d;
int err;
err = snd_config_search(cfg, "DefineMacro", &d);
if (err == -ENOENT)
return 1;
if (err < 0)
return err;
if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for DefineMacro");
return -EINVAL;
}
if (uc_mgr->conf_format < 6) {
uc_error("DefineMacro is supported in v6+ syntax");
return -EINVAL;
}
err = snd_config_merge(uc_mgr->macros, d, 0);
if (err < 0)
return err;
return 0;
}
static int evaluate_macro1(snd_use_case_mgr_t *uc_mgr,
snd_config_t *dst,
snd_config_t *args)
{
snd_config_iterator_t i, next;
snd_config_t *m, *mc, *a, *n;
const char *mid, *id;
char name[128], *var, *var2;
const char *s;
int err;
err = snd_config_get_id(args, &mid);
if (err < 0)
return err;
err = snd_config_search(uc_mgr->macros, mid, &m);
if (err < 0) {
uc_error("Macro '%s' is not defined", mid);
return err;
}
a = args;
if (snd_config_get_type(args) == SND_CONFIG_TYPE_STRING) {
err = snd_config_get_string(args, &s);
if (err < 0)
return err;
err = snd_config_load_string(&a, s, 0);
if (err < 0)
return err;
} else if (snd_config_get_type(args) != SND_CONFIG_TYPE_COMPOUND) {
return -EINVAL;
}
/* set arguments */
snd_config_for_each(i, next, a) {
n = snd_config_iterator_entry(i);
err = snd_config_get_id(n, &id);
if (err < 0)
goto __err_path;
snprintf(name, sizeof(name), "__%s", id);
if (uc_mgr_get_variable(uc_mgr, name)) {
uc_error("Macro argument '%s' is already defined", name);
goto __err_path;
}
err = snd_config_get_ascii(n, &var);
if (err < 0)
goto __err_path;
err = uc_mgr_get_substituted_value(uc_mgr, &var2, var);
free(var);
if (err >= 0) {
err = uc_mgr_set_variable(uc_mgr, name, var2);
free(var2);
}
if (err < 0)
goto __err_path;
}
/* merge + substitute variables */
err = snd_config_copy(&mc, m);
if (err < 0)
goto __err_path;
err = uc_mgr_evaluate_inplace(uc_mgr, mc);
if (err < 0) {
snd_config_delete(mc);
goto __err_path;
}
err = uc_mgr_config_tree_merge(uc_mgr, dst, mc, NULL, NULL);
snd_config_delete(mc);
/* delete arguments */
snd_config_for_each(i, next, a) {
n = snd_config_iterator_entry(i);
err = snd_config_get_id(n, &id);
if (err < 0)
goto __err_path;
snprintf(name, sizeof(name), "__%s", id);
err = uc_mgr_delete_variable(uc_mgr, name);
if (err < 0)
goto __err_path;
}
__err_path:
if (a != args)
snd_config_delete(a);
return err;
}
/*
* Evaluate macro definitions and instances (in-place delete)
*/
static int evaluate_macro(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg)
{
snd_config_iterator_t i, i2, next, next2;
snd_config_t *d, *n, *n2;
int err, ret;
ret = evaluate_define_macro(uc_mgr, cfg);
if (ret < 0)
return ret;
err = snd_config_search(cfg, "Macro", &d);
if (err == -ENOENT)
return ret;
if (err < 0)
return err;
if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for DefineMacro");
return -EINVAL;
}
if (uc_mgr->conf_format < 6) {
uc_error("Macro is supported in v6+ syntax");
return -EINVAL;
}
snd_config_for_each(i, next, d) {
n = snd_config_iterator_entry(i);
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
const char *id;
if (snd_config_get_id(n, &id))
id = "";
uc_error("compound type expected for Macro.%s", id);
return -EINVAL;
}
snd_config_for_each(i2, next2, n) {
n2 = snd_config_iterator_entry(i2);
err = evaluate_macro1(uc_mgr, cfg, n2);
if (err < 0)
return err;
}
}
snd_config_delete(d);
return 0;
}
/*
* Evaluate include (in-place)
*/
static int evaluate_include(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg)
{
snd_config_t *n;
int err;
err = snd_config_search(cfg, "Include", &n);
if (err == -ENOENT)
return 1;
if (err < 0)
return err;
err = uc_mgr_evaluate_include(uc_mgr, cfg, n);
snd_config_delete(n);
return err;
}
/*
* Evaluate condition (in-place)
*/
static int evaluate_condition(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
snd_config_t *n;
int err;
err = snd_config_search(cfg, "If", &n);
if (err == -ENOENT)
return 1;
if (err < 0)
return err;
err = uc_mgr_evaluate_condition(uc_mgr, cfg, n);
snd_config_delete(n);
return err;
}
/*
* Evaluate variant (in-place)
*/
static int evaluate_variant(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *n, *c;
const char *id;
int err;
err = snd_config_search(cfg, "Variant", &c);
if (err == -ENOENT)
return 1;
if (err < 0)
return err;
if (uc_mgr->conf_format < 6) {
uc_error("Variant is supported in v6+ syntax");
return -EINVAL;
}
if (uc_mgr->parse_master_section)
return 1;
if (uc_mgr->parse_variant == NULL)
goto __ret;
snd_config_for_each(i, next, c) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
return -EINVAL;
if (strcmp(id, uc_mgr->parse_variant))
continue;
err = uc_mgr_evaluate_inplace(uc_mgr, n);
if (err < 0)
return err;
err = uc_mgr_config_tree_merge(uc_mgr, cfg, n, NULL, NULL);
if (err < 0)
return err;
snd_config_delete(c);
return 0;
}
__ret:
snd_config_delete(c);
return 1;
}
/*
* In-place evaluate
*/
int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg)
{
long iterations = 10000;
int err1 = 0, err2 = 0, err3 = 0, err4 = 0, err5 = 0;
while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0 || err5 == 0) {
if (iterations == 0) {
uc_error("Maximal inplace evaluation iterations number reached (recursive references?)");
return -EINVAL;
}
iterations--;
/* variables at first */
err1 = evaluate_define(uc_mgr, cfg);
if (err1 < 0)
return err1;
/* include at second */
err2 = evaluate_include(uc_mgr, cfg);
if (err2 < 0)
return err2;
/* include or macro may define another variables */
/* conditions may depend on them */
if (err2 == 0)
continue;
err3 = evaluate_variant(uc_mgr, cfg);
if (err3 < 0)
return err3;
if (err3 == 0)
continue;
uc_mgr->macro_hops++;
if (uc_mgr->macro_hops > 100) {
uc_error("Maximal macro hops reached!");
return -EINVAL;
}
err4 = evaluate_macro(uc_mgr, cfg);
uc_mgr->macro_hops--;
if (err4 < 0)
return err4;
if (err4 == 0)
continue;
err5 = evaluate_condition(uc_mgr, cfg);
if (err5 < 0)
return err5;
}
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(cfg);
return err;
}
err = snd_config_merge(uc_mgr->local_config, cfg, 0);
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)
return err;
}
err = snd_config_merge(uc_mgr->local_config, config, 0);
if (err < 0)
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,
struct list_head *tlist,
snd_config_t *cfg)
{
struct transition_sequence *tseq;
const char *id;
snd_config_iterator_t i, next;
snd_config_t *n;
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;
tseq = calloc(1, sizeof(*tseq));
if (tseq == NULL)
return -ENOMEM;
INIT_LIST_HEAD(&tseq->transition_list);
err = get_string3(uc_mgr, id, &tseq->name);
if (err < 0) {
free(tseq);
return err;
}
err = parse_sequence(uc_mgr, &tseq->transition_list, n);
if (err < 0) {
uc_mgr_free_transition_element(tseq);
return err;
}
list_add(&tseq->list, tlist);
}
return 0;
}
/*
* Parse compound
*/
static int parse_compound(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *),
void *data1, void *data2)
{
const char *id;
snd_config_iterator_t i, next;
snd_config_t *n;
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;
}
/* parse compound */
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for %s, is %d", id, snd_config_get_type(cfg));
return -EINVAL;
}
err = fcn(uc_mgr, n, data1, data2);
if (err < 0)
return err;
}
return 0;
}
static int strip_legacy_dev_index(char *name)
{
char *dot = strchr(name, '.');
if (!dot)
return 0;
if (dot[1] != '0' || dot[2] != '\0') {
uc_error("device name %s contains a '.',"
" and is not legacy foo.0 format", name);
return -EINVAL;
}
*dot = '\0';
return 0;
}
/*
* Parse device list
*/
static int parse_device_list(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
struct dev_list *dev_list,
enum dev_list_type type,
snd_config_t *cfg)
{
struct dev_list_node *sdev;
const char *id;
snd_config_iterator_t i, next;
snd_config_t *n;
int err;
if (dev_list->type != DEVLIST_NONE) {
uc_error("error: multiple supported or"
" conflicting device lists");
return -EEXIST;
}
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;
sdev = calloc(1, sizeof(struct dev_list_node));
if (sdev == NULL)
return -ENOMEM;
err = parse_string_substitute3(uc_mgr, n, &sdev->name);
if (err < 0) {
free(sdev);
return err;
}
err = strip_legacy_dev_index(sdev->name);
if (err < 0) {
free(sdev->name);
free(sdev);
return err;
}
list_add(&sdev->list, &dev_list->list);
}
dev_list->type = type;
return 0;
}
/* Find a component device by its name, and remove it from machine device
* list.
*
* Component devices are defined by machine components (usually off-soc
* codes or DSP embeded in SoC). Since alsaconf imports their configuration
* files automatically, we don't know which devices are component devices
* until they are referenced by a machine device sequence. So here when we
* find a referenced device, we move it from the machine device list to the
* component device list. Component devices will not be exposed to applications
* by the original API to list devices for backward compatibility. So sound
* servers can only see the machine devices.
*/
struct use_case_device *find_component_dev(snd_use_case_mgr_t *uc_mgr,
const char *name)
{
struct list_head *pos, *posdev, *_posdev;
struct use_case_verb *verb;
struct use_case_device *dev;
list_for_each(pos, &uc_mgr->verb_list) {
verb = list_entry(pos, struct use_case_verb, list);
/* search in the component device list */
list_for_each(posdev, &verb->cmpt_device_list) {
dev = list_entry(posdev, struct use_case_device, list);
if (!strcmp(dev->name, name))
return dev;
}
/* search the machine device list */
list_for_each_safe(posdev, _posdev, &verb->device_list) {
dev = list_entry(posdev, struct use_case_device, list);
if (!strcmp(dev->name, name)) {
/* find the component device, move it from the
* machine device list to the component device
* list.
*/
list_del(&dev->list);
list_add_tail(&dev->list,
&verb->cmpt_device_list);
return dev;
}
}
}
return NULL;
}
/* parse sequence of a component device
*
* This function will find the component device and mark if its enable or
* disable sequence is needed by its parenet device.
*/
static int parse_component_seq(snd_use_case_mgr_t *uc_mgr,
snd_config_t *n, int enable,
struct component_sequence *cmpt_seq)
{
char *val;
int err;
err = parse_string_substitute3(uc_mgr, n, &val);
if (err < 0)
return err;
cmpt_seq->device = find_component_dev(uc_mgr, val);
if (!cmpt_seq->device) {
uc_error("error: Cannot find component device %s", val);
free(val);
return -EINVAL;
}
free(val);
/* Parent needs its enable or disable sequence */
cmpt_seq->enable = enable;
return 0;
}
/*
* Parse sequences.
*
* Sequence controls elements are in the following form:-
*
* cdev "hw:0"
* cset "element_id_syntax value_syntax"
* usleep time
* exec "any unix command with arguments"
* enadev "component device name"
* disdev "component device name"
*
* e.g.
* cset "name='Master Playback Switch' 0,0"
* cset "iface=PCM,name='Disable HDMI',index=1 0"
* enadev "rt286:Headphones"
* disdev "rt286:Speaker"
*/
static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
struct list_head *base,
snd_config_t *cfg)
{
struct sequence_element *curr;
snd_config_iterator_t i, next;
snd_config_t *n;
int err, idx = 0;
const char *cmd = NULL;
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("error: compound is expected for sequence definition");
return -EINVAL;
}
snd_config_for_each(i, next, cfg) {
const char *id;
idx ^= 1;
n = snd_config_iterator_entry(i);
err = snd_config_get_id(n, &id);
if (err < 0)
continue;
if (idx == 1) {
if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
uc_error("error: string type is expected for sequence command");
return -EINVAL;
}
snd_config_get_string(n, &cmd);
continue;
}
/* alloc new sequence element */
curr = calloc(1, sizeof(struct sequence_element));
if (curr == NULL)
return -ENOMEM;
list_add_tail(&curr->list, base);
if (strcmp(cmd, "cdev") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_CDEV;
err = parse_string_substitute3(uc_mgr, n, &curr->data.cdev);
if (err < 0) {
uc_error("error: cdev requires a string!");
return err;
}
continue;
}
if (strcmp(cmd, "cset") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_CSET;
cset:
err = parse_string_substitute3(uc_mgr, n, &curr->data.cset);
if (err < 0) {
uc_error("error: %s requires a string!", cmd);
return err;
}
continue;
}
if (strcmp(cmd, "enadev") == 0 ||
strcmp(cmd, "disdev") == 0) {
/* need to enable or disable a component device */
curr->type = SEQUENCE_ELEMENT_TYPE_CMPT_SEQ;
err = parse_component_seq(uc_mgr, n,
strcmp(cmd, "enadev") == 0,
&curr->data.cmpt_seq);
if (err < 0) {
uc_error("error: %s requires a valid device!", cmd);
return err;
}
continue;
}
if (strcmp(cmd, "enadev2") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_DEV_ENABLE_SEQ;
goto device;
}
if (strcmp(cmd, "disdev2") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_SEQ;
device:
err = parse_string_substitute3(uc_mgr, n, &curr->data.device);
if (err < 0) {
uc_error("error: %s requires a valid device!", cmd);
return err;
}
continue;
}
if (strcmp(cmd, "disdevall") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_ALL;
continue;
}
if (strcmp(cmd, "cset-bin-file") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE;
goto cset;
}
if (strcmp(cmd, "cset-tlv") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_CSET_TLV;
goto cset;
}
if (strcmp(cmd, "cset-new") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_CSET_NEW;
goto cset;
}
if (strcmp(cmd, "ctl-remove") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_CTL_REMOVE;
goto cset;
}
if (strcmp(cmd, "sysw") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_SYSSET;
err = parse_string_substitute3(uc_mgr, n, &curr->data.sysw);
if (err < 0) {
uc_error("error: sysw requires a string!");
return err;
}
continue;
}
if (strcmp(cmd, "usleep") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
err = parse_integer_substitute3(uc_mgr, n, &curr->data.sleep);
if (err < 0) {
uc_error("error: usleep requires integer!");
return err;
}
continue;
}
if (strcmp(cmd, "msleep") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
err = parse_integer_substitute3(uc_mgr, n, &curr->data.sleep);
if (err < 0) {
uc_error("error: msleep requires integer!");
return err;
}
curr->data.sleep *= 1000L;
continue;
}
if (strcmp(cmd, "exec") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_EXEC;
exec:
err = parse_string_substitute3(uc_mgr, n, &curr->data.exec);
if (err < 0) {
uc_error("error: exec requires a string!");
return err;
}
continue;
}
if (strcmp(cmd, "shell") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_SHELL;
goto exec;
}
if (strcmp(cmd, "cfg-save") == 0) {
curr->type = SEQUENCE_ELEMENT_TYPE_CFGSAVE;
err = parse_string_substitute3(uc_mgr, n, &curr->data.cfgsave);
if (err < 0) {
uc_error("error: sysw requires a string!");
return err;
}
continue;
}
if (strcmp(cmd, "comment") == 0)
goto skip;
uc_error("error: sequence command '%s' is ignored", cmd);
skip:
list_del(&curr->list);
uc_mgr_free_sequence_element(curr);
}
return 0;
}
/*
*
*/
int uc_mgr_add_value(struct list_head *base, const char *key, char *val)
{
struct ucm_value *curr;
curr = calloc(1, sizeof(struct ucm_value));
if (curr == NULL)
return -ENOMEM;
curr->name = strdup(key);
if (curr->name == NULL) {
free(curr);
return -ENOMEM;
}
list_add_tail(&curr->list, base);
curr->data = val;
return 0;
}
/*
* Parse values.
*
* Parse values describing PCM, control/mixer settings and stream parameters.
*
* Value {
* TQ Voice
* CapturePCM "hw:1"
* PlaybackVolume "name='Master Playback Volume',index=2"
* PlaybackSwitch "name='Master Playback Switch',index=2"
* }
*/
static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
struct list_head *base,
snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *n;
char *s;
snd_config_type_t type;
int err;
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("error: compound is expected for value definition");
return -EINVAL;
}
/* in-place evaluation */
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
if (err < 0)
return err;
snd_config_for_each(i, next, cfg) {
const char *id;
n = snd_config_iterator_entry(i);
err = snd_config_get_id(n, &id);
if (err < 0)
continue;
type = snd_config_get_type(n);
switch (type) {
case SND_CONFIG_TYPE_INTEGER:
case SND_CONFIG_TYPE_INTEGER64:
case SND_CONFIG_TYPE_REAL:
err = snd_config_get_ascii(n, &s);
if (err < 0) {
uc_error("error: unable to parse value for id '%s': %s!", id, snd_strerror(err));
return err;
}
break;
case SND_CONFIG_TYPE_STRING:
err = parse_string_substitute(uc_mgr, n, &s);
if (err < 0) {
uc_error("error: unable to parse a string for id '%s'!", id);
return err;
}
break;
default:
uc_error("error: invalid type %i in Value compound '%s'", type, id);
return -EINVAL;
}
err = uc_mgr_add_value(base, id, s);
if (err < 0) {
free(s);
return err;
}
}
return 0;
}
/*
* Parse Modifier Use cases
*
* # Each modifier is described in new section. N modifiers are allowed
* SectionModifier."Capture Voice" {
*
* Comment "Record voice call"
*
* SupportedDevice [
* "x"
* "y"
* ]
*
* ConflictingDevice [
* "x"
* "y"
* ]
*
* EnableSequence [
* ....
* ]
*
* DisableSequence [
* ...
* ]
*
* TransitionSequence."ToModifierName" [
* ...
* ]
*
* # Optional TQ and ALSA PCMs
* Value {
* TQ Voice
* CapturePCM "hw:1"
* PlaybackVolume "name='Master Playback Volume',index=2"
* PlaybackSwitch "name='Master Playback Switch',index=2"
* }
* }
*
* SupportedDevice and ConflictingDevice cannot be specified together.
* Both are optional.
*/
static int parse_modifier(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg,
void *data1, void *data2)
{
struct use_case_verb *verb = data1;
struct use_case_modifier *modifier;
char *name;
snd_config_iterator_t i, next;
snd_config_t *n;
int err;
if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
return -EINVAL;
/* allocate modifier */
modifier = calloc(1, sizeof(*modifier));
if (modifier == NULL) {
free(name);
return -ENOMEM;
}
INIT_LIST_HEAD(&modifier->enable_list);
INIT_LIST_HEAD(&modifier->disable_list);
INIT_LIST_HEAD(&modifier->transition_list);
INIT_LIST_HEAD(&modifier->dev_list.list);
INIT_LIST_HEAD(&modifier->value_list);
list_add_tail(&modifier->list, &verb->modifier_list);
modifier->name = name;
/* in-place evaluation */
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
if (err < 0)
return err;
snd_config_for_each(i, next, cfg) {
const char *id;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "Comment") == 0) {
err = parse_string_substitute3(uc_mgr, n, &modifier->comment);
if (err < 0) {
uc_error("error: failed to get modifier comment");
return err;
}
continue;
}
if (strcmp(id, "SupportedDevice") == 0) {
err = parse_device_list(uc_mgr, &modifier->dev_list,
DEVLIST_SUPPORTED, n);
if (err < 0) {
uc_error("error: failed to parse supported"
" device list");
return err;
}
}
if (strcmp(id, "ConflictingDevice") == 0) {
err = parse_device_list(uc_mgr, &modifier->dev_list,
DEVLIST_CONFLICTING, n);
if (err < 0) {
uc_error("error: failed to parse conflicting"
" device list");
return err;
}
}
if (strcmp(id, "EnableSequence") == 0) {
err = parse_sequence(uc_mgr, &modifier->enable_list, n);
if (err < 0) {
uc_error("error: failed to parse modifier"
" enable sequence");
return err;
}
continue;
}
if (strcmp(id, "DisableSequence") == 0) {
err = parse_sequence(uc_mgr, &modifier->disable_list, n);
if (err < 0) {
uc_error("error: failed to parse modifier"
" disable sequence");
return err;
}
continue;
}
if (strcmp(id, "TransitionSequence") == 0) {
err = parse_transition(uc_mgr, &modifier->transition_list, n);
if (err < 0) {
uc_error("error: failed to parse transition"
" modifier");
return err;
}
continue;
}
if (strcmp(id, "Value") == 0) {
err = parse_value(uc_mgr, &modifier->value_list, n);
if (err < 0) {
uc_error("error: failed to parse Value");
return err;
}
continue;
}
}
return 0;
}
/*
* Parse Device Use Cases
*
* # Each device is described in new section. N devices are allowed
* SectionDevice."Headphones" {
* Comment "Headphones connected to 3.5mm jack"
*
* SupportedDevice [
* "x"
* "y"
* ]
*
* ConflictingDevice [
* "x"
* "y"
* ]
*
* EnableSequence [
* ....
* ]
*
* DisableSequence [
* ...
* ]
*
* TransitionSequence."ToDevice" [
* ...
* ]
*
* Value {
* PlaybackVolume "name='Master Playback Volume',index=2"
* PlaybackSwitch "name='Master Playback Switch',index=2"
* }
* }
*/
static int parse_device(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg,
void *data1, void *data2)
{
struct use_case_verb *verb = data1;
char *name;
struct use_case_device *device;
snd_config_iterator_t i, next;
snd_config_t *n;
int err;
if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
return -EINVAL;
device = calloc(1, sizeof(*device));
if (device == NULL) {
free(name);
return -ENOMEM;
}
INIT_LIST_HEAD(&device->enable_list);
INIT_LIST_HEAD(&device->disable_list);
INIT_LIST_HEAD(&device->transition_list);
INIT_LIST_HEAD(&device->dev_list.list);
INIT_LIST_HEAD(&device->value_list);
list_add_tail(&device->list, &verb->device_list);
device->name = name;
/* in-place evaluation */
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
if (err < 0)
return err;
snd_config_for_each(i, next, cfg) {
const char *id;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "Comment") == 0) {
err = parse_string_substitute3(uc_mgr, n, &device->comment);
if (err < 0) {
uc_error("error: failed to get device comment");
return err;
}
continue;
}
if (strcmp(id, "SupportedDevice") == 0) {
err = parse_device_list(uc_mgr, &device->dev_list,
DEVLIST_SUPPORTED, n);
if (err < 0) {
uc_error("error: failed to parse supported"
" device list");
return err;
}
}
if (strcmp(id, "ConflictingDevice") == 0) {
err = parse_device_list(uc_mgr, &device->dev_list,
DEVLIST_CONFLICTING, n);
if (err < 0) {
uc_error("error: failed to parse conflicting"
" device list");
return err;
}
}
if (strcmp(id, "EnableSequence") == 0) {
uc_dbg("EnableSequence");
err = parse_sequence(uc_mgr, &device->enable_list, n);
if (err < 0) {
uc_error("error: failed to parse device enable"
" sequence");
return err;
}
continue;
}
if (strcmp(id, "DisableSequence") == 0) {
uc_dbg("DisableSequence");
err = parse_sequence(uc_mgr, &device->disable_list, n);
if (err < 0) {
uc_error("error: failed to parse device disable"
" sequence");
return err;
}
continue;
}
if (strcmp(id, "TransitionSequence") == 0) {
uc_dbg("TransitionSequence");
err = parse_transition(uc_mgr, &device->transition_list, n);
if (err < 0) {
uc_error("error: failed to parse transition"
" device");
return err;
}
continue;
}
if (strcmp(id, "Value") == 0) {
err = parse_value(uc_mgr, &device->value_list, n);
if (err < 0) {
uc_error("error: failed to parse Value");
return err;
}
continue;
}
}
return 0;
}
/*
* Parse Device Rename/Delete Command
*
* # The devices might be renamed to allow the better conditional runtime
* # evaluation. Bellow example renames Speaker1 device to Speaker and
* # removes Speaker2 device.
* RenameDevice."Speaker1" "Speaker"
* RemoveDevice."Speaker2" "Speaker2"
*/
static int parse_dev_name_list(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg,
struct list_head *list)
{
snd_config_t *n;
snd_config_iterator_t i, next;
const char *id, *name1;
char *name1s, *name2;
struct ucm_dev_name *dev;
snd_config_iterator_t pos;
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, &name1) < 0)
return -EINVAL;
err = get_string3(uc_mgr, name1, &name1s);
if (err < 0)
return err;
err = parse_string_substitute3(uc_mgr, n, &name2);
if (err < 0) {
free(name1s);
uc_error("error: failed to get target device name for '%s'", name1);
return err;
}
/* skip duplicates */
list_for_each(pos, list) {
dev = list_entry(pos, struct ucm_dev_name, list);
if (strcmp(dev->name1, name1s) == 0) {
free(name2);
free(name1s);
return 0;
}
}
free(name1s);
dev = calloc(1, sizeof(*dev));
if (dev == NULL) {
free(name2);
return -ENOMEM;
}
dev->name1 = strdup(name1);
if (dev->name1 == NULL) {
free(dev);
free(name2);
return -ENOMEM;
}
dev->name2 = name2;
list_add_tail(&dev->list, list);
}
return 0;
}
static int parse_compound_check_legacy(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg,
int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *),
void *data1)
{
const char *id, *idchild;
int child_ctr = 0, legacy_format = 1;
snd_config_iterator_t i, next;
snd_config_t *child;
int err;
err = snd_config_get_id(cfg, &id);
if (err < 0)
return err;
snd_config_for_each(i, next, cfg) {
child_ctr++;
if (child_ctr > 1) {
break;
}
child = snd_config_iterator_entry(i);
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
legacy_format = 0;
break;
}
if (snd_config_get_id(child, &idchild) < 0)
return -EINVAL;
if (strcmp(idchild, "0")) {
legacy_format = 0;
break;
}
}
if (child_ctr != 1) {
legacy_format = 0;
}
if (legacy_format)
return parse_compound(uc_mgr, cfg, fcn, data1, (void *)id);
else
return fcn(uc_mgr, cfg, data1, NULL);
}
static int parse_device_name(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg,
void *data1,
void *data2 ATTRIBUTE_UNUSED)
{
return parse_compound_check_legacy(uc_mgr, cfg, parse_device, data1);
}
static int parse_modifier_name(snd_use_case_mgr_t *uc_mgr,
snd_config_t *cfg,
void *data1,
void *data2 ATTRIBUTE_UNUSED)
{
return parse_compound_check_legacy(uc_mgr, cfg, parse_modifier, data1);
}
static int verb_dev_list_add(struct use_case_verb *verb,
enum dev_list_type dst_type,
const char *dst,
const char *src)
{
struct use_case_device *device;
struct list_head *pos;
list_for_each(pos, &verb->device_list) {
device = list_entry(pos, struct use_case_device, list);
if (strcmp(device->name, dst) != 0)
continue;
if (device->dev_list.type != dst_type) {
if (list_empty(&device->dev_list.list)) {
device->dev_list.type = dst_type;
} else {
uc_error("error: incompatible device list type ('%s', '%s')",
device->name, src);
return -EINVAL;
}
}
return uc_mgr_put_to_dev_list(&device->dev_list, src);
}
uc_error("error: unable to find device '%s'", dst);
return -ENOENT;
}
static int verb_dev_list_check(struct use_case_verb *verb)
{
struct list_head *pos, *pos2;
struct use_case_device *device;
struct dev_list_node *dlist;
int err;
list_for_each(pos, &verb->device_list) {
device = list_entry(pos, struct use_case_device, list);
list_for_each(pos2, &device->dev_list.list) {
dlist = list_entry(pos2, struct dev_list_node, list);
err = verb_dev_list_add(verb, device->dev_list.type,
dlist->name, device->name);
if (err < 0)
return err;
}
}
return 0;
}
static int verb_device_management(struct use_case_verb *verb)
{
struct list_head *pos;
struct ucm_dev_name *dev;
int err;
/* rename devices */
list_for_each(pos, &verb->rename_list) {
dev = list_entry(pos, struct ucm_dev_name, list);
err = uc_mgr_rename_device(verb, dev->name1, dev->name2);
if (err < 0) {
uc_error("error: cannot rename device '%s' to '%s'", dev->name1, dev->name2);
return err;
}
}
/* remove devices */
list_for_each(pos, &verb->remove_list) {
dev = list_entry(pos, struct ucm_dev_name, list);
err = uc_mgr_remove_device(verb, dev->name2);
if (err < 0) {
uc_error("error: cannot remove device '%s'", dev->name2);
return err;
}
}
/* those lists are no longer used */
uc_mgr_free_dev_name_list(&verb->rename_list);
uc_mgr_free_dev_name_list(&verb->remove_list);
/* handle conflicting/supported lists */
return verb_dev_list_check(verb);
}
/*
* Parse Verb Section
*
* # Example Use case verb section for Voice call blah
* # By Joe Blogs <joe@blogs.com>
*
* SectionVerb {
* # enable and disable sequences are compulsory
* EnableSequence [
* cset "name='Master Playback Switch',index=2 0,0"
* cset "name='Master Playback Volume',index=2 25,25"
* msleep 50
* cset "name='Master Playback Switch',index=2 1,1"
* cset "name='Master Playback Volume',index=2 50,50"
* ]
*
* DisableSequence [
* cset "name='Master Playback Switch',index=2 0,0"
* cset "name='Master Playback Volume',index=2 25,25"
* msleep 50
* cset "name='Master Playback Switch',index=2 1,1"
* cset "name='Master Playback Volume',index=2 50,50"
* ]
*
* # Optional transition verb
* TransitionSequence."ToCaseName" [
* msleep 1
* ]
*
* # Optional TQ and ALSA PCMs
* Value {
* TQ HiFi
* CapturePCM "hw:0"
* PlaybackPCM "hw:0"
* }
* }
*/
static int parse_verb(snd_use_case_mgr_t *uc_mgr,
struct use_case_verb *verb,
snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *n;
int err;
/* in-place evaluation */
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
if (err < 0)
return err;
/* parse verb section */
snd_config_for_each(i, next, cfg) {
const char *id;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "EnableSequence") == 0) {
uc_dbg("Parse EnableSequence");
err = parse_sequence(uc_mgr, &verb->enable_list, n);
if (err < 0) {
uc_error("error: failed to parse verb enable sequence");
return err;
}
continue;
}
if (strcmp(id, "DisableSequence") == 0) {
uc_dbg("Parse DisableSequence");
err = parse_sequence(uc_mgr, &verb->disable_list, n);
if (err < 0) {
uc_error("error: failed to parse verb disable sequence");
return err;
}
continue;
}
if (strcmp(id, "TransitionSequence") == 0) {
uc_dbg("Parse TransitionSequence");
err = parse_transition(uc_mgr, &verb->transition_list, n);
if (err < 0) {
uc_error("error: failed to parse transition sequence");
return err;
}
continue;
}
if (strcmp(id, "Value") == 0) {
uc_dbg("Parse Value");
err = parse_value(uc_mgr, &verb->value_list, n);
if (err < 0)
return err;
continue;
}
}
return 0;
}
/*
* Parse a Use case verb file.
*
* This file contains the following :-
* o Verb enable and disable sequences.
* o Supported Device enable and disable sequences for verb.
* o Supported Modifier enable and disable sequences for verb
* o Optional QoS for the verb and modifiers.
* o Optional PCM device ID for verb and modifiers
* o Alias kcontrols IDs for master and volumes and mutes.
*/
static int parse_verb_file(snd_use_case_mgr_t *uc_mgr,
const char *use_case_name,
const char *comment,
const char *file)
{
snd_config_iterator_t i, next;
snd_config_t *n;
struct use_case_verb *verb;
snd_config_t *cfg;
int err;
/* allocate verb */
verb = calloc(1, sizeof(struct use_case_verb));
if (verb == NULL)
return -ENOMEM;
INIT_LIST_HEAD(&verb->enable_list);
INIT_LIST_HEAD(&verb->disable_list);
INIT_LIST_HEAD(&verb->transition_list);
INIT_LIST_HEAD(&verb->device_list);
INIT_LIST_HEAD(&verb->cmpt_device_list);
INIT_LIST_HEAD(&verb->modifier_list);
INIT_LIST_HEAD(&verb->value_list);
INIT_LIST_HEAD(&verb->rename_list);
INIT_LIST_HEAD(&verb->remove_list);
list_add_tail(&verb->list, &uc_mgr->verb_list);
if (use_case_name == NULL)
return -EINVAL;
verb->name = strdup(use_case_name);
if (verb->name == NULL)
return -ENOMEM;
if (comment != NULL) {
verb->comment = strdup(comment);
if (verb->comment == NULL)
return -ENOMEM;
}
/* open Verb file for reading */
err = uc_mgr_config_load_file(uc_mgr, file, &cfg);
if (err < 0)
return err;
/* in-place evaluation */
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
if (err < 0)
goto _err;
/* parse master config sections */
snd_config_for_each(i, next, cfg) {
const char *id;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* find verb section and parse it */
if (strcmp(id, "SectionVerb") == 0) {
err = parse_verb(uc_mgr, verb, n);
if (err < 0) {
uc_error("error: %s failed to parse verb",
file);
goto _err;
}
continue;
}
/* find device sections and parse them */
if (strcmp(id, "SectionDevice") == 0) {
err = parse_compound(uc_mgr, n,
parse_device_name, verb, NULL);
if (err < 0) {
uc_error("error: %s failed to parse device",
file);
goto _err;
}
continue;
}
/* find modifier sections and parse them */
if (strcmp(id, "SectionModifier") == 0) {
err = parse_compound(uc_mgr, n,
parse_modifier_name, verb, NULL);
if (err < 0) {
uc_error("error: %s failed to parse modifier",
file);
goto _err;
}
continue;
}
/* device renames */
if (strcmp(id, "RenameDevice") == 0) {
err = parse_dev_name_list(uc_mgr, n, &verb->rename_list);
if (err < 0) {
uc_error("error: %s failed to parse device rename",
file);
goto _err;
}
continue;
}
/* device remove */
if (strcmp(id, "RemoveDevice") == 0) {
err = parse_dev_name_list(uc_mgr, n, &verb->remove_list);
if (err < 0) {
uc_error("error: %s failed to parse 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");
goto _err;
}
continue;
}
}
snd_config_delete(cfg);
/* use case verb must have at least 1 device */
if (list_empty(&verb->device_list)) {
uc_error("error: no use case device defined", file);
return -EINVAL;
}
/* do device rename and delete */
err = verb_device_management(verb);
if (err < 0) {
uc_error("error: device management error in verb '%s'", verb->name);
return err;
}
return 0;
_err:
snd_config_delete(cfg);
return err;
}
/*
* Parse variant information
*/
static int parse_variant(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
char **_vfile, char **_vcomment)
{
snd_config_iterator_t i, next;
snd_config_t *n;
char *file = NULL, *comment = NULL;
int err;
/* parse master config sections */
snd_config_for_each(i, next, cfg) {
const char *id;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* get use case verb file name */
if (strcmp(id, "File") == 0) {
if (_vfile) {
err = parse_string_substitute3(uc_mgr, n, &file);
if (err < 0) {
uc_error("failed to get File");
goto __error;
}
}
continue;
}
/* get optional use case comment */
if (strncmp(id, "Comment", 7) == 0) {
if (_vcomment) {
err = parse_string_substitute3(uc_mgr, n, &comment);
if (err < 0) {
uc_error("error: failed to get Comment");
goto __error;
}
}
continue;
}
uc_error("unknown field '%s' in Variant section", id);
err = -EINVAL;
goto __error;
}
if (_vfile)
*_vfile = file;
if (_vcomment)
*_vcomment = comment;
return 0;
__error:
free(file);
free(comment);
return err;
}
/*
* Parse master section for "Use Case" and "File" tags.
*/
static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
void *data1 ATTRIBUTE_UNUSED,
void *data2 ATTRIBUTE_UNUSED)
{
snd_config_iterator_t i, next;
snd_config_t *n, *variant = NULL;
char *use_case_name, *file = NULL, *comment = NULL;
bool variant_ok = false;
int err;
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for use case section");
return -EINVAL;
}
err = parse_get_safe_name(uc_mgr, cfg, NULL, &use_case_name);
if (err < 0) {
uc_error("unable to get name for use case section");
return err;
}
/* in-place evaluation */
uc_mgr->parse_master_section = 1;
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
uc_mgr->parse_master_section = 0;
if (err < 0)
goto __error;
/* parse master config sections */
snd_config_for_each(i, next, cfg) {
const char *id;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
/* get use case verb file name */
if (strcmp(id, "File") == 0) {
err = parse_string_substitute3(uc_mgr, n, &file);
if (err < 0) {
uc_error("failed to get File");
goto __error;
}
continue;
}
/* get optional use case comment */
if (strncmp(id, "Comment", 7) == 0) {
err = parse_string_substitute3(uc_mgr, n, &comment);
if (err < 0) {
uc_error("error: failed to get Comment");
goto __error;
}
continue;
}
if (uc_mgr->conf_format >= 6 && strcmp(id, "Variant") == 0) {
snd_config_iterator_t i2, next2;
variant = n;
snd_config_for_each(i2, next2, n) {
const char *id2;
snd_config_t *n2;
n2 = snd_config_iterator_entry(i2);
if (snd_config_get_id(n2, &id2) < 0)
continue;
err = uc_mgr_evaluate_inplace(uc_mgr, n2);
if (err < 0)
goto __error;
if (strcmp(use_case_name, id2) == 0)
variant_ok = true;
}
continue;
}
uc_error("unknown field '%s' in SectionUseCase", id);
}
if (variant && !variant_ok) {
uc_error("error: undefined variant '%s'", use_case_name);
err = -EINVAL;
goto __error;
}
if (!variant) {
uc_dbg("use_case_name %s file '%s'", use_case_name, file);
/* do we have both use case name and file ? */
if (!file) {
uc_error("error: use case missing file");
err = -EINVAL;
goto __error;
}
/* parse verb file */
err = parse_verb_file(uc_mgr, use_case_name, comment, file);
} else {
/* parse variants */
snd_config_for_each(i, next, variant) {
char *vfile, *vcomment;
const char *id;
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
if (!parse_is_name_safe(id)) {
err = -EINVAL;
goto __error;
}
err = parse_variant(uc_mgr, n, &vfile, &vcomment);
if (err < 0)
break;
uc_mgr->parse_variant = id;
err = parse_verb_file(uc_mgr, id,
vcomment ? vcomment : comment,
vfile ? vfile : file);
uc_mgr->parse_variant = NULL;
free(vfile);
free(vcomment);
}
}
__error:
free(use_case_name);
free(file);
free(comment);
return err;
}
/*
* parse controls which should be run only at initial boot (forcefully)
*/
static int parse_controls_fixedboot(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
int err;
if (!list_empty(&uc_mgr->fixedboot_list)) {
uc_error("FixedBoot list is not empty");
return -EINVAL;
}
err = parse_sequence(uc_mgr, &uc_mgr->fixedboot_list, cfg);
if (err < 0) {
uc_error("Unable to parse FixedBootSequence");
return err;
}
return 0;
}
/*
* parse controls which should be run only at initial boot
*/
static int parse_controls_boot(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
int err;
if (!list_empty(&uc_mgr->boot_list)) {
uc_error("Boot list is not empty");
return -EINVAL;
}
err = parse_sequence(uc_mgr, &uc_mgr->boot_list, cfg);
if (err < 0) {
uc_error("Unable to parse BootSequence");
return err;
}
return 0;
}
/*
* parse controls
*/
static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
int err;
if (!list_empty(&uc_mgr->default_list)) {
uc_error("Default list is not empty");
return -EINVAL;
}
err = parse_sequence(uc_mgr, &uc_mgr->default_list, cfg);
if (err < 0) {
uc_error("Unable to parse SectionDefaults");
return err;
}
return 0;
}
/*
* Each sound card has a master sound card file that lists all the supported
* use case verbs for that sound card. i.e.
*
* #Example master file for blah sound card
* #By Joe Blogs <joe@bloggs.org>
*
* Comment "Nice Abstracted Soundcard"
*
* # The file is divided into Use case sections. One section per use case verb.
*
* SectionUseCase."Voice Call" {
* File "voice_call_blah"
* Comment "Make a voice phone call."
* }
*
* SectionUseCase."HiFi" {
* File "hifi_blah"
* Comment "Play and record HiFi quality Music."
* }
*
* # Define Value defaults
*
* ValueDefaults {
* PlaybackCTL "hw:CARD=0"
* CaptureCTL "hw:CARD=0"
* }
*
* # The initial boot (run once) configuration.
*
* BootSequence [
* cset "name='Master Playback Switch',index=2 1,1"
* cset "name='Master Playback Volume',index=2 25,25"
* ]
*
* # This file also stores the default sound card state.
*
* SectionDefaults [
* cset "name='Master Mono Playback',index=1 0"
* cset "name='Master Mono Playback Volume',index=1 0"
* cset "name='PCM Switch',index=2 1,1"
* exec "some binary here"
* msleep 50
* ........
* ]
*
* # End of example file.
*/
static int parse_master_file(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_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for master file");
return -EINVAL;
}
if (uc_mgr->conf_format >= 2) {
err = parse_syntax_field(uc_mgr, cfg, uc_mgr->conf_file_name);
if (err < 0)
return err;
}
/* in-place evaluation */
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
if (err < 0)
return err;
/* parse master config sections */
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "Comment") == 0) {
err = parse_string_substitute3(uc_mgr, n, &uc_mgr->comment);
if (err < 0) {
uc_error("error: failed to get master comment");
return err;
}
continue;
}
/* find use case section and parse it */
if (strcmp(id, "SectionUseCase") == 0) {
err = parse_compound(uc_mgr, n,
parse_master_section,
NULL, NULL);
if (err < 0)
return err;
continue;
}
/* find default control values section (force boot sequence only) */
if (strcmp(id, "FixedBootSequence") == 0) {
err = parse_controls_fixedboot(uc_mgr, n);
if (err < 0)
return err;
continue;
}
/* find default control values section (first boot only) */
if (strcmp(id, "BootSequence") == 0) {
err = parse_controls_boot(uc_mgr, n);
if (err < 0)
return err;
continue;
}
/* find default control values section and parse it */
if (strcmp(id, "SectionDefaults") == 0) {
err = parse_controls(uc_mgr, n);
if (err < 0)
return err;
continue;
}
/* get the default values */
if (strcmp(id, "ValueDefaults") == 0) {
err = parse_value(uc_mgr, &uc_mgr->value_list, n);
if (err < 0) {
uc_error("error: failed to parse ValueDefaults");
return 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 LibraryConfig");
return err;
}
continue;
}
/* error */
if (strcmp(id, "Error") == 0)
return error_node(uc_mgr, n);
/* skip further Syntax value updates (Include) */
if (strcmp(id, "Syntax") == 0)
continue;
uc_error("unknown master file field %s", id);
}
return 0;
}
/* get the card info */
static int get_card_info(snd_use_case_mgr_t *mgr,
const char *ctl_name,
snd_ctl_card_info_t **info)
{
struct ctl_list *ctl_list;
int err;
err = uc_mgr_open_ctl(mgr, &ctl_list, ctl_name, 0);
if (err < 0)
return err;
if (info)
*info = ctl_list->ctl_info;
return err;
}
/* find the card in the local machine */
static int get_by_card_name(snd_use_case_mgr_t *mgr, const char *card_name)
{
int card, err;
snd_ctl_card_info_t *info;
const char *_driver, *_name, *_long_name;
snd_ctl_card_info_alloca(&info);
card = -1;
if (snd_card_next(&card) < 0 || card < 0) {
uc_error("no soundcards found...");
return -1;
}
while (card >= 0) {
char name[32];
/* clear the list, keep the only one CTL device */
uc_mgr_free_ctl_list(mgr);
sprintf(name, "hw:%d", card);
err = get_card_info(mgr, name, &info);
if (err == 0) {
_driver = snd_ctl_card_info_get_driver(info);
_name = snd_ctl_card_info_get_name(info);
_long_name = snd_ctl_card_info_get_longname(info);
if (!strcmp(card_name, _driver) ||
!strcmp(card_name, _name) ||
!strcmp(card_name, _long_name))
return 0;
}
if (snd_card_next(&card) < 0) {
uc_error("snd_card_next");
break;
}
}
uc_mgr_free_ctl_list(mgr);
return -1;
}
/* set the driver name and long name by the card ctl name */
static inline int get_by_card(snd_use_case_mgr_t *mgr, const char *ctl_name)
{
return get_card_info(mgr, ctl_name, NULL);
}
static int parse_toplevel_path(snd_use_case_mgr_t *uc_mgr,
char *filename,
snd_config_t *cfg)
{
snd_config_iterator_t i, next, i2, next2;
snd_config_t *n, *n2;
const char *id;
char *dir = NULL, *file = NULL, fn[PATH_MAX];
struct stat64 st;
long version;
int err;
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for UseCasePath node");
return -EINVAL;
}
/* parse use case path config sections */
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for UseCasePath.something node");
return -EINVAL;
}
if (snd_config_get_id(n, &id) < 0)
continue;
version = 2;
/* parse use case path config sections */
snd_config_for_each(i2, next2, n) {
n2 = snd_config_iterator_entry(i2);
if (snd_config_get_id(n2, &id) < 0)
continue;
if (strcmp(id, "Version") == 0) {
err = parse_integer_substitute(uc_mgr, n2, &version);
if (err < 0) {
uc_error("unable to parse UcmDirectory");
goto __error;
}
if (version < 1 || version > 2) {
uc_error("Version must be 1 or 2");
err = -EINVAL;
goto __error;
}
continue;
}
if (strcmp(id, "Directory") == 0) {
err = parse_string_substitute(uc_mgr, n2, &dir);
if (err < 0) {
uc_error("unable to parse Directory");
goto __error;
}
continue;
}
if (strcmp(id, "File") == 0) {
err = parse_string_substitute(uc_mgr, n2, &file);
if (err < 0) {
uc_error("unable to parse File");
goto __error;
}
continue;
}
uc_error("unknown UseCasePath field %s", id);
}
if (dir == NULL) {
uc_error("Directory is not defined in %s!", filename);
goto __next;
}
if (file == NULL) {
uc_error("File is not defined in %s!", filename);
goto __next;
}
ucm_filename(fn, sizeof(fn), version, dir, file);
if (access(fn, R_OK) == 0 && lstat64(fn, &st) == 0) {
if (S_ISLNK(st.st_mode)) {
ssize_t r;
char *link, *dir2, *p;
link = malloc(PATH_MAX);
if (link == NULL)
goto __enomem;
r = readlink(fn, link, PATH_MAX - 1);
if (r <= 0) {
free(link);
goto __next;
}
link[r] = '\0';
p = strrchr(link, '/');
if (p) {
*p = '\0';
dir2 = malloc(PATH_MAX);
if (dir2 == NULL) {
free(link);
goto __enomem;
}
strncpy(dir2, dir, PATH_MAX - 1);
strncat(dir2, "/", PATH_MAX - 1);
strncat(dir2, link, PATH_MAX - 1);
fn[PATH_MAX - 1] = '\0';
free(dir);
dir = dir2;
}
free(link);
}
if (replace_string(&uc_mgr->conf_dir_name, dir) == NULL)
goto __enomem;
if (replace_string(&uc_mgr->conf_file_name, file) == NULL)
goto __enomem;
strncpy(filename, fn, PATH_MAX);
filename[PATH_MAX - 1] = '\0';
uc_mgr->conf_format = version;
goto __ok;
}
__next:
free(file);
if (dir != fn)
free(dir);
dir = NULL;
file = NULL;
}
err = -ENOENT;
goto __error;
__enomem:
err = -ENOMEM;
goto __error;
__ok:
err = 0;
__error:
free(file);
if (dir != fn)
free(dir);
return err;
}
static int parse_toplevel_config(snd_use_case_mgr_t *uc_mgr,
char *filename,
snd_config_t *cfg)
{
snd_config_iterator_t i, next;
snd_config_t *n;
const char *id;
int err;
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
uc_error("compound type expected for toplevel file");
return -EINVAL;
}
err = parse_syntax_field(uc_mgr, cfg, filename);
if (err < 0)
return err;
/* in-place evaluation */
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
if (err < 0)
return err;
/* parse toplevel config sections */
snd_config_for_each(i, next, cfg) {
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "UseCasePath") == 0) {
err = parse_toplevel_path(uc_mgr, filename, n);
if (err == 0)
return 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;
}
/* skip further Syntax value updates (Include) */
if (strcmp(id, "Syntax") == 0)
continue;
uc_error("unknown toplevel field %s", id);
}
return -ENOENT;
}
static int load_toplevel_config(snd_use_case_mgr_t *uc_mgr,
snd_config_t **cfg)
{
char filename[PATH_MAX];
snd_config_t *tcfg;
int err;
ucm_filename(filename, sizeof(filename), 2, NULL, "ucm.conf");
if (access(filename, R_OK) != 0) {
uc_error("Unable to find the top-level configuration file '%s'.", filename);
return -ENOENT;
}
err = uc_mgr_config_load(2, filename, &tcfg);
if (err < 0)
goto __error;
/* filename is shared for function input and output! */
err = parse_toplevel_config(uc_mgr, filename, tcfg);
snd_config_delete(tcfg);
if (err < 0)
goto __error;
err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg);
if (err < 0) {
uc_error("error: could not parse configuration for card %s",
uc_mgr->card_name);
goto __error;
}
return 0;
__error:
return err;
}
/* load master use case file for sound card based on rules in ucm2/ucm.conf
*/
int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr)
{
snd_config_t *cfg;
const char *name;
int err;
err = snd_config_top(&uc_mgr->local_config);
if (err < 0)
return err;
err = snd_config_top(&uc_mgr->macros);
if (err < 0)
return err;
name = uc_mgr->card_name;
if (strncmp(name, "hw:", 3) == 0) {
err = get_by_card(uc_mgr, name);
if (err < 0) {
uc_error("card '%s' is not valid", name);
goto __error;
}
} else if (strncmp(name, "strict:", 7)) {
/* do not handle the error here */
/* we can refer the virtual UCM config */
get_by_card_name(uc_mgr, name);
}
err = load_toplevel_config(uc_mgr, &cfg);
if (err < 0)
goto __error;
err = parse_master_file(uc_mgr, cfg);
if (uc_mgr->macros) {
snd_config_delete(uc_mgr->macros);
uc_mgr->macros = NULL;
}
snd_config_delete(cfg);
if (err < 0) {
uc_mgr_free_ctl_list(uc_mgr);
uc_mgr_free_verb(uc_mgr);
}
return err;
__error:
uc_mgr_free_ctl_list(uc_mgr);
replace_string(&uc_mgr->conf_dir_name, NULL);
return err;
}
static int filename_filter(const struct dirent64 *dirent)
{
if (dirent == NULL)
return 0;
if (dirent->d_type == DT_DIR) {
if (dirent->d_name[0] == '.') {
if (dirent->d_name[1] == '\0')
return 0;
if (dirent->d_name[1] == '.' &&
dirent->d_name[2] == '\0')
return 0;
}
return 1;
}
return 0;
}
/* scan all cards and comments
*
* Cards are defined by machines. Each card/machine installs its UCM
* configuration files in a subdirectory with the same name as the sound
* card under /usr/share/alsa/ucm2. This function will scan all the card
* directories and skip the component directories defined in the array
* component_dir.
*/
int uc_mgr_scan_master_configs(const char **_list[])
{
char filename[PATH_MAX], dfl[PATH_MAX], fn[FILENAME_MAX];
char *env = getenv(ALSA_CONFIG_UCM2_VAR);
snd_use_case_mgr_t *uc_mgr;
const char **list, *d_name;
char *s;
snd_config_t *cfg, *c;
int i, j, cnt, err, cards;
long l;
ssize_t ss;
struct dirent64 **namelist;
i = -1;
cards = 0;
while (1) {
err = snd_card_next(&i);
if (err < 0)
return err;
if (i < 0)
break;
cards++;
}
cards += 4; /* plug-and-play */
if (env)
snprintf(filename, sizeof(filename), "%s/conf.virt.d", env);
else
snprintf(filename, sizeof(filename), "%s/ucm2/conf.virt.d",
snd_config_topdir());
#if defined(_GNU_SOURCE) && !defined(__NetBSD__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__sun) && !defined(__ANDROID__)
#define SORTFUNC versionsort64
#else
#define SORTFUNC alphasort64
#endif
err = scandir64(filename, &namelist, filename_filter, SORTFUNC);
if (err < 0) {
err = -errno;
uc_error("error: could not scan directory %s: %s",
filename, strerror(-err));
return err;
}
cnt = err;
dfl[0] = '\0';
if (strlen(filename) + 8 < sizeof(filename)) {
strcat(filename, "/default");
ss = readlink(filename, dfl, sizeof(dfl)-1);
if (ss >= 0) {
dfl[ss] = '\0';
dfl[sizeof(dfl)-1] = '\0';
if (dfl[0] && dfl[strlen(dfl)-1] == '/')
dfl[strlen(dfl)-1] = '\0';
} else {
dfl[0] = '\0';
}
}
j = 0;
list = calloc(1, (cards + cnt) * 2 * sizeof(char *));
if (list == NULL) {
err = -ENOMEM;
goto __err;
}
i = -1;
while (j / 2 < cards) {
err = snd_card_next(&i);
if (err < 0)
goto __err;
if (i < 0)
break;
snprintf(fn, sizeof(fn), "-hw:%d", i);
err = snd_use_case_mgr_open(&uc_mgr, fn);
if (err == -ENOENT || err == -ENXIO)
continue;
if (err < 0) {
uc_error("Unable to open '%s': %s", fn, snd_strerror(err));
goto __err;
}
err = snd_use_case_get(uc_mgr, "comment", (const char **)&s);
if (err < 0) {
err = snd_card_get_longname(i, &s);
if (err < 0)
goto __err;
}
snd_use_case_mgr_close(uc_mgr);
list[j] = strdup(fn + 1);
if (list[j] == NULL) {
free(s);
err = -ENOMEM;
goto __err;
}
list[j + 1] = s;
j += 2;
}
for (i = 0; i < cnt; i++) {
d_name = namelist[i]->d_name;
snprintf(fn, sizeof(fn), "%s.conf", d_name);
ucm_filename(filename, sizeof(filename), 2, d_name, fn);
#ifdef HAVE_EACCESS
if (eaccess(filename, R_OK))
#else
if (access(filename, R_OK))
#endif
continue;
err = uc_mgr_config_load(2, filename, &cfg);
if (err < 0)
goto __err;
err = snd_config_search(cfg, "Syntax", &c);
if (err < 0) {
uc_error("Syntax field not found in %s", d_name);
snd_config_delete(cfg);
continue;
}
err = snd_config_get_integer(c, &l);
if (err < 0) {
uc_error("Syntax field is invalid in %s", d_name);
snd_config_delete(cfg);
goto __err;
}
if (l < 2 || l > SYNTAX_VERSION_MAX) {
uc_error("Incompatible syntax %d in %s", l, d_name);
snd_config_delete(cfg);
goto __err;
}
err = snd_config_search(cfg, "Comment", &c);
if (err >= 0) {
err = parse_string(c, (char **)&list[j+1]);
if (err < 0) {
snd_config_delete(cfg);
goto __err;
}
}
snd_config_delete(cfg);
list[j] = strdup(d_name);
if (list[j] == NULL) {
err = -ENOMEM;
goto __err;
}
if (strcmp(dfl, list[j]) == 0) {
/* default to top */
const char *save1 = list[j];
const char *save2 = list[j + 1];
memmove(list + 2, list, j * sizeof(char *));
list[0] = save1;
list[1] = save2;
}
j += 2;
}
err = 0;
__err:
for (i = 0; i < cnt; i++)
free(namelist[i]);
free(namelist);
if (err < 0) {
for (i = 0; i < j; i++) {
free((void *)list[i * 2]);
free((void *)list[i * 2 + 1]);
}
free(list);
return err;
}
*_list = list;
return j;
}