2010-09-07 15:35:14 +02:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
2011-03-28 11:04:19 +02:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
* License along with this library; if not, write to the Free Software
|
2017-11-14 14:29:26 +01:00
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
2010-09-07 15:35:14 +02:00
|
|
|
*
|
|
|
|
|
* 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"
|
2021-04-12 18:09:21 +02:00
|
|
|
#include <stdbool.h>
|
2010-10-13 11:48:52 +02:00
|
|
|
#include <dirent.h>
|
2016-12-21 19:46:34 -03:00
|
|
|
#include <limits.h>
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2017-01-18 11:53:35 +08:00
|
|
|
static int filename_filter(const struct dirent *dirent);
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
struct list_head *base,
|
|
|
|
|
snd_config_t *cfg);
|
|
|
|
|
|
2019-11-09 11:53:32 +01:00
|
|
|
/*
|
2020-05-26 18:54:31 +02:00
|
|
|
* compose the absolute ucm filename
|
2019-11-09 11:53:32 +01:00
|
|
|
*/
|
2020-05-26 18:54:31 +02:00
|
|
|
static void ucm_filename(char *fn, size_t fn_len, long version,
|
|
|
|
|
const char *dir, const char *file)
|
2019-11-09 11:53:32 +01:00
|
|
|
{
|
2020-05-26 18:54:31 +02:00
|
|
|
const char *env = getenv(version > 1 ? ALSA_CONFIG_UCM2_VAR : ALSA_CONFIG_UCM_VAR);
|
2019-11-09 11:53:32 +01:00
|
|
|
|
2021-04-13 17:19:54 +02:00
|
|
|
if (file[0] == '/')
|
|
|
|
|
file++;
|
2020-05-26 18:54:31 +02:00
|
|
|
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);
|
2019-11-09 11:53:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/*
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
ucm_filename(filename, sizeof(filename), uc_mgr->conf_format,
|
|
|
|
|
file[0] == '/' ? NULL : uc_mgr->conf_dir_name,
|
|
|
|
|
file);
|
2020-05-16 15:47:19 +02:00
|
|
|
err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg);
|
|
|
|
|
if (err < 0) {
|
2021-02-03 11:55:53 +01:00
|
|
|
uc_error("error: failed to open file %s: %d", filename, err);
|
2020-05-16 15:47:19 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-15 10:31:56 +01:00
|
|
|
/*
|
|
|
|
|
* Replace mallocated string
|
|
|
|
|
*/
|
|
|
|
|
static char *replace_string(char **dst, const char *value)
|
|
|
|
|
{
|
|
|
|
|
free(*dst);
|
2020-05-26 18:54:31 +02:00
|
|
|
*dst = value ? strdup(value) : NULL;
|
2020-01-15 10:31:56 +01:00
|
|
|
return *dst;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/*
|
|
|
|
|
* Parse string
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 11:55:33 +02:00
|
|
|
/*
|
|
|
|
|
* Parse string and substitute
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-27 14:47:08 +02:00
|
|
|
/*
|
|
|
|
|
* Parse string and substitute
|
|
|
|
|
*/
|
|
|
|
|
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);
|
2020-05-27 15:20:03 +02:00
|
|
|
return parse_string_substitute(uc_mgr, n, res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Parse integer with substitution
|
|
|
|
|
*/
|
|
|
|
|
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);
|
2020-05-27 14:47:08 +02:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2020-05-27 15:20:03 +02:00
|
|
|
err = uc_mgr_get_substituted_value(uc_mgr, &s2, s1);
|
2020-05-27 14:47:08 +02:00
|
|
|
if (err >= 0)
|
2020-05-27 15:20:03 +02:00
|
|
|
err = safe_strtol(s2, res);
|
|
|
|
|
free(s2);
|
|
|
|
|
free(s1);
|
2020-05-27 14:47:08 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 11:55:33 +02:00
|
|
|
/*
|
|
|
|
|
* Parse integer with substitution
|
|
|
|
|
*/
|
2020-05-27 15:20:03 +02:00
|
|
|
int parse_integer_substitute3(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *n, long *res)
|
2020-05-20 11:55:33 +02:00
|
|
|
{
|
|
|
|
|
char *s1, *s2;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_get_ascii(n, &s1);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2020-05-27 15:20:03 +02:00
|
|
|
if (uc_mgr->conf_format < 3)
|
|
|
|
|
s2 = s1;
|
|
|
|
|
else
|
|
|
|
|
err = uc_mgr_get_substituted_value(uc_mgr, &s2, s1);
|
2020-05-20 11:55:33 +02:00
|
|
|
if (err >= 0)
|
|
|
|
|
err = safe_strtol(s2, res);
|
2020-05-27 15:20:03 +02:00
|
|
|
if (s1 != s2)
|
|
|
|
|
free(s2);
|
2020-05-20 11:55:33 +02:00
|
|
|
free(s1);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-31 14:24:19 +01:00
|
|
|
/*
|
|
|
|
|
* Parse safe ID
|
|
|
|
|
*/
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
int parse_is_name_safe(const char *name)
|
2011-01-31 14:24:19 +01:00
|
|
|
{
|
|
|
|
|
if (strchr(name, '.')) {
|
2011-06-02 16:44:38 -06:00
|
|
|
uc_error("char '.' not allowed in '%s'", name);
|
2011-01-31 14:24:19 +01:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-27 15:13:55 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-27 13:53:10 +02:00
|
|
|
int parse_get_safe_name(snd_use_case_mgr_t *uc_mgr, snd_config_t *n,
|
|
|
|
|
const char *alt, char **name)
|
2011-01-31 14:24:19 +01:00
|
|
|
{
|
2020-05-27 13:53:10 +02:00
|
|
|
const char *id;
|
2011-01-31 14:24:19 +01:00
|
|
|
int err;
|
|
|
|
|
|
2020-05-27 13:53:10 +02:00
|
|
|
if (alt) {
|
|
|
|
|
id = alt;
|
|
|
|
|
} else {
|
|
|
|
|
err = snd_config_get_id(n, &id);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2020-07-03 14:48:18 +02:00
|
|
|
err = get_string3(uc_mgr, id, name);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
if (!parse_is_name_safe(*name)) {
|
|
|
|
|
free(*name);
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
return -EINVAL;
|
2020-07-03 14:48:18 +02:00
|
|
|
}
|
|
|
|
|
return 0;
|
2011-01-31 14:24:19 +01:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2020-10-06 10:23:25 +02:00
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
uc_error("%s", s);
|
|
|
|
|
free(s);
|
|
|
|
|
return -ENXIO;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 16:48:43 +02:00
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 15:37:42 +02:00
|
|
|
if (uc_mgr->conf_format < 3) {
|
|
|
|
|
uc_error("DefineRegex is supported in v3+ syntax");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 16:48:43 +02:00
|
|
|
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 = uc_mgr_define_regex(uc_mgr, id, n);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snd_config_delete(d);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 19:04:36 +02:00
|
|
|
/*
|
|
|
|
|
* 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)
|
2020-05-19 16:48:43 +02:00
|
|
|
return evaluate_regex(uc_mgr, cfg);
|
2020-05-20 19:04:36 +02:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
uc_error("compound type expected for Define");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 15:37:42 +02:00
|
|
|
if (uc_mgr->conf_format < 3) {
|
|
|
|
|
uc_error("Define is supported in v3+ syntax");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 19:04:36 +02:00
|
|
|
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;
|
|
|
|
|
uc_mgr_set_variable(uc_mgr, id, s);
|
|
|
|
|
free(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snd_config_delete(d);
|
2020-05-19 16:48:43 +02:00
|
|
|
|
|
|
|
|
return evaluate_regex(uc_mgr, cfg);
|
2020-05-20 19:04:36 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-14 15:13:36 +01:00
|
|
|
/*
|
|
|
|
|
* 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)
|
2020-05-16 15:47:19 +02:00
|
|
|
return 1;
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
2019-11-14 16:57:22 +01:00
|
|
|
err = uc_mgr_evaluate_condition(uc_mgr, cfg, n);
|
|
|
|
|
snd_config_delete(n);
|
|
|
|
|
return err;
|
2019-11-14 15:13:36 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/*
|
|
|
|
|
* In-place evaluate
|
|
|
|
|
*/
|
|
|
|
|
int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg)
|
|
|
|
|
{
|
2020-05-20 19:04:36 +02:00
|
|
|
int err1 = 0, err2 = 0, err3 = 0;
|
2020-05-16 15:47:19 +02:00
|
|
|
|
2020-05-20 19:04:36 +02:00
|
|
|
while (err1 == 0 || err2 == 0 || err3 == 0) {
|
|
|
|
|
/* variables at first */
|
|
|
|
|
err1 = evaluate_define(uc_mgr, cfg);
|
2020-05-16 15:47:19 +02:00
|
|
|
if (err1 < 0)
|
|
|
|
|
return err1;
|
2020-05-20 19:04:36 +02:00
|
|
|
/* include at second */
|
|
|
|
|
err2 = evaluate_include(uc_mgr, cfg);
|
2020-05-16 15:47:19 +02:00
|
|
|
if (err2 < 0)
|
|
|
|
|
return err2;
|
2020-05-20 19:34:15 +02:00
|
|
|
/* include may define another variables */
|
|
|
|
|
/* conditions may depend on them */
|
|
|
|
|
if (err2 == 0)
|
|
|
|
|
continue;
|
2020-05-20 19:04:36 +02:00
|
|
|
err3 = evaluate_condition(uc_mgr, cfg);
|
|
|
|
|
if (err3 < 0)
|
|
|
|
|
return err3;
|
2020-05-16 15:47:19 +02:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 18:09:21 +02:00
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/*
|
|
|
|
|
* 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);
|
2010-10-26 14:26:46 +02:00
|
|
|
return -EINVAL;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
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);
|
|
|
|
|
|
2020-05-27 15:13:55 +02:00
|
|
|
err = get_string3(uc_mgr, id, &tseq->name);
|
2020-05-27 15:04:31 +02:00
|
|
|
if (err < 0) {
|
2010-10-26 14:26:46 +02:00
|
|
|
free(tseq);
|
2020-05-27 15:04:31 +02:00
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
err = parse_sequence(uc_mgr, &tseq->transition_list, n);
|
2010-10-26 14:26:46 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_mgr_free_transition_element(tseq);
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
2010-10-26 14:26:46 +02:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
list_add(&tseq->list, tlist);
|
|
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
/* parse compound */
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
uc_error("compound type expected for %s, is %d", id, snd_config_get_type(cfg));
|
2010-09-07 15:35:14 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = fcn(uc_mgr, n, data1, data2);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
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;
|
|
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
|
|
|
|
|
/*
|
2011-06-03 14:56:31 -06:00
|
|
|
* Parse device list
|
2010-10-26 14:26:46 +02:00
|
|
|
*/
|
2011-06-03 14:56:31 -06:00
|
|
|
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)
|
2010-10-26 14:26:46 +02:00
|
|
|
{
|
2011-06-03 14:56:31 -06:00
|
|
|
struct dev_list_node *sdev;
|
2010-10-26 14:26:46 +02:00
|
|
|
const char *id;
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *n;
|
|
|
|
|
int err;
|
|
|
|
|
|
2011-06-03 14:56:31 -06:00
|
|
|
if (dev_list->type != DEVLIST_NONE) {
|
|
|
|
|
uc_error("error: multiple supported or"
|
|
|
|
|
" conflicting device lists");
|
|
|
|
|
return -EEXIST;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
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;
|
|
|
|
|
|
2011-06-03 14:56:31 -06:00
|
|
|
sdev = calloc(1, sizeof(struct dev_list_node));
|
2010-10-26 14:26:46 +02:00
|
|
|
if (sdev == NULL)
|
|
|
|
|
return -ENOMEM;
|
2020-05-27 14:47:08 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &sdev->name);
|
2010-10-26 14:26:46 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
free(sdev);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
err = strip_legacy_dev_index(sdev->name);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
free(sdev->name);
|
|
|
|
|
free(sdev);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2011-06-03 14:56:31 -06:00
|
|
|
list_add(&sdev->list, &dev_list->list);
|
2010-10-26 14:26:46 +02:00
|
|
|
}
|
2011-06-03 14:56:31 -06:00
|
|
|
|
|
|
|
|
dev_list->type = type;
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-28 13:33:54 +08:00
|
|
|
/* 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,
|
2020-05-27 14:12:12 +02:00
|
|
|
snd_config_t *n, int enable,
|
|
|
|
|
struct component_sequence *cmpt_seq)
|
2016-11-28 13:33:54 +08:00
|
|
|
{
|
2020-05-27 14:12:12 +02:00
|
|
|
char *val;
|
2016-11-28 13:33:54 +08:00
|
|
|
int err;
|
|
|
|
|
|
2020-05-27 14:47:08 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &val);
|
2016-11-28 13:33:54 +08:00
|
|
|
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);
|
2020-06-08 18:17:16 +02:00
|
|
|
free(val);
|
2016-11-28 13:33:54 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2020-06-08 18:17:16 +02:00
|
|
|
free(val);
|
2016-11-28 13:33:54 +08:00
|
|
|
|
|
|
|
|
/* Parent needs its enable or disable sequence */
|
|
|
|
|
cmpt_seq->enable = enable;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/*
|
|
|
|
|
* Parse sequences.
|
|
|
|
|
*
|
|
|
|
|
* Sequence controls elements are in the following form:-
|
|
|
|
|
*
|
2010-11-29 15:49:13 +01:00
|
|
|
* cdev "hw:0"
|
2010-09-07 15:35:14 +02:00
|
|
|
* cset "element_id_syntax value_syntax"
|
|
|
|
|
* usleep time
|
|
|
|
|
* exec "any unix command with arguments"
|
2016-11-28 13:33:54 +08:00
|
|
|
* enadev "component device name"
|
|
|
|
|
* disdev "component device name"
|
2010-09-07 15:35:14 +02:00
|
|
|
*
|
|
|
|
|
* e.g.
|
|
|
|
|
* cset "name='Master Playback Switch' 0,0"
|
|
|
|
|
* cset "iface=PCM,name='Disable HDMI',index=1 0"
|
2016-11-28 13:33:54 +08:00
|
|
|
* enadev "rt286:Headphones"
|
|
|
|
|
* disdev "rt286:Speaker"
|
2010-09-07 15:35:14 +02:00
|
|
|
*/
|
2016-11-28 13:33:54 +08:00
|
|
|
static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
|
2010-09-07 15:35:14 +02:00
|
|
|
struct list_head *base,
|
|
|
|
|
snd_config_t *cfg)
|
|
|
|
|
{
|
|
|
|
|
struct sequence_element *curr;
|
2010-10-26 14:26:46 +02:00
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *n;
|
2010-11-10 16:06:29 +01:00
|
|
|
int err, idx = 0;
|
|
|
|
|
const char *cmd = NULL;
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
uc_error("error: compound is expected for sequence definition");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-10-26 14:26:46 +02:00
|
|
|
const char *id;
|
2010-11-10 16:06:29 +01:00
|
|
|
idx ^= 1;
|
2010-09-07 15:35:14 +02:00
|
|
|
n = snd_config_iterator_entry(i);
|
2010-10-26 14:26:46 +02:00
|
|
|
err = snd_config_get_id(n, &id);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
continue;
|
2010-11-10 16:06:29 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
/* alloc new sequence element */
|
|
|
|
|
curr = calloc(1, sizeof(struct sequence_element));
|
|
|
|
|
if (curr == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
list_add_tail(&curr->list, base);
|
|
|
|
|
|
2010-11-23 15:58:14 +01:00
|
|
|
if (strcmp(cmd, "cdev") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CDEV;
|
2020-05-27 15:20:03 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &curr->data.cdev);
|
2010-11-23 15:58:14 +01:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: cdev requires a string!");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-10 16:06:29 +01:00
|
|
|
if (strcmp(cmd, "cset") == 0) {
|
2010-10-26 14:26:46 +02:00
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CSET;
|
2021-03-25 20:36:54 +01:00
|
|
|
cset:
|
2020-05-27 15:20:03 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &curr->data.cset);
|
2010-10-26 14:26:46 +02:00
|
|
|
if (err < 0) {
|
2021-03-25 20:36:54 +01:00
|
|
|
uc_error("error: %s requires a string!", cmd);
|
2010-10-26 14:26:46 +02:00
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2016-11-28 13:33:54 +08:00
|
|
|
if (strcmp(cmd, "enadev") == 0) {
|
|
|
|
|
/* need to enable a component device */
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CMPT_SEQ;
|
|
|
|
|
err = parse_component_seq(uc_mgr, n, 1,
|
|
|
|
|
&curr->data.cmpt_seq);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: enadev requires a valid device!");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(cmd, "disdev") == 0) {
|
|
|
|
|
/* need to disable a component device */
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CMPT_SEQ;
|
|
|
|
|
err = parse_component_seq(uc_mgr, n, 0,
|
|
|
|
|
&curr->data.cmpt_seq);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: disdev requires a valid device!");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-22 09:32:47 +08:00
|
|
|
if (strcmp(cmd, "cset-bin-file") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE;
|
2021-03-25 20:36:54 +01:00
|
|
|
goto cset;
|
2015-01-22 09:32:47 +08:00
|
|
|
}
|
|
|
|
|
|
2016-04-13 18:53:09 +08:00
|
|
|
if (strcmp(cmd, "cset-tlv") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CSET_TLV;
|
2021-03-25 20:36:54 +01:00
|
|
|
goto cset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(cmd, "cset-new") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CSET_NEW;
|
|
|
|
|
goto cset;
|
2016-04-13 18:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
2021-03-29 11:12:28 +02:00
|
|
|
if (strcmp(cmd, "ctl-remove") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CTL_REMOVE;
|
|
|
|
|
goto cset;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 11:40:30 +01:00
|
|
|
if (strcmp(cmd, "sysw") == 0) {
|
2021-03-05 19:55:06 +01:00
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_SYSSET;
|
2021-03-23 11:40:30 +01:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &curr->data.sysw);
|
2021-03-05 19:55:06 +01:00
|
|
|
if (err < 0) {
|
2021-03-23 11:40:30 +01:00
|
|
|
uc_error("error: sysw requires a string!");
|
2021-03-05 19:55:06 +01:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-10 16:06:29 +01:00
|
|
|
if (strcmp(cmd, "usleep") == 0) {
|
2010-10-26 14:26:46 +02:00
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
|
2020-05-27 15:20:03 +02:00
|
|
|
err = parse_integer_substitute3(uc_mgr, n, &curr->data.sleep);
|
2010-10-26 14:26:46 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: usleep requires integer!");
|
|
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2011-08-22 13:35:32 +08:00
|
|
|
if (strcmp(cmd, "msleep") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
|
2020-05-27 15:20:03 +02:00
|
|
|
err = parse_integer_substitute3(uc_mgr, n, &curr->data.sleep);
|
2011-08-22 13:35:32 +08:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: msleep requires integer!");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
curr->data.sleep *= 1000L;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-10 16:06:29 +01:00
|
|
|
if (strcmp(cmd, "exec") == 0) {
|
2010-10-26 14:26:46 +02:00
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_EXEC;
|
2021-05-11 14:48:16 +02:00
|
|
|
exec:
|
2020-05-27 15:20:03 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &curr->data.exec);
|
2010-10-26 14:26:46 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: exec requires a string!");
|
|
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
continue;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
2021-03-09 20:02:57 +01:00
|
|
|
|
2021-05-11 14:48:16 +02:00
|
|
|
if (strcmp(cmd, "shell") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_SHELL;
|
|
|
|
|
goto exec;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-14 12:20:18 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-09 20:02:57 +01:00
|
|
|
if (strcmp(cmd, "comment") == 0)
|
|
|
|
|
goto skip;
|
|
|
|
|
|
|
|
|
|
uc_error("error: sequence command '%s' is ignored", cmd);
|
|
|
|
|
|
|
|
|
|
skip:
|
2010-10-26 14:26:46 +02:00
|
|
|
list_del(&curr->list);
|
|
|
|
|
uc_mgr_free_sequence_element(curr);
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-05 14:21:07 +01:00
|
|
|
/*
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-22 14:31:15 +02:00
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_iterator_t i, next;
|
2010-10-26 14:26:46 +02:00
|
|
|
snd_config_t *n;
|
2019-11-05 14:21:07 +01:00
|
|
|
char *s;
|
2010-09-22 14:31:15 +02:00
|
|
|
snd_config_type_t type;
|
|
|
|
|
int err;
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
uc_error("error: compound is expected for value definition");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2019-11-14 15:13:36 +01:00
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-10-26 14:26:46 +02:00
|
|
|
const char *id;
|
2010-09-22 14:31:15 +02:00
|
|
|
n = snd_config_iterator_entry(i);
|
2010-10-26 14:26:46 +02:00
|
|
|
err = snd_config_get_id(n, &id);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
continue;
|
2010-09-22 14:31:15 +02:00
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
type = snd_config_get_type(n);
|
|
|
|
|
switch (type) {
|
|
|
|
|
case SND_CONFIG_TYPE_INTEGER:
|
|
|
|
|
case SND_CONFIG_TYPE_INTEGER64:
|
|
|
|
|
case SND_CONFIG_TYPE_REAL:
|
2019-11-05 14:21:07 +01:00
|
|
|
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;
|
2010-10-26 14:26:46 +02:00
|
|
|
case SND_CONFIG_TYPE_STRING:
|
2020-05-26 15:14:15 +02:00
|
|
|
err = parse_string_substitute(uc_mgr, n, &s);
|
2010-10-26 14:26:46 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: unable to parse a string for id '%s'!", id);
|
|
|
|
|
return err;
|
2010-09-22 14:31:15 +02:00
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
break;
|
|
|
|
|
default:
|
2019-11-05 13:02:41 +01:00
|
|
|
uc_error("error: invalid type %i in Value compound '%s'", type, id);
|
2010-10-26 14:26:46 +02:00
|
|
|
return -EINVAL;
|
2010-09-22 14:31:15 +02:00
|
|
|
}
|
2019-11-05 14:21:07 +01:00
|
|
|
err = uc_mgr_add_value(base, id, s);
|
2019-11-15 12:52:36 +01:00
|
|
|
if (err < 0) {
|
|
|
|
|
free(s);
|
2019-11-05 14:21:07 +01:00
|
|
|
return err;
|
2019-11-15 12:52:36 +01:00
|
|
|
}
|
2010-09-22 14:31:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/*
|
|
|
|
|
* Parse Modifier Use cases
|
|
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* # Each modifier is described in new section. N modifiers are allowed
|
|
|
|
|
* SectionModifier."Capture Voice" {
|
2010-09-07 15:35:14 +02:00
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* Comment "Record voice call"
|
2011-06-03 14:56:31 -06:00
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* SupportedDevice [
|
|
|
|
|
* "x"
|
|
|
|
|
* "y"
|
|
|
|
|
* ]
|
2011-06-03 14:56:31 -06:00
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* ConflictingDevice [
|
|
|
|
|
* "x"
|
|
|
|
|
* "y"
|
|
|
|
|
* ]
|
2010-09-07 15:35:14 +02:00
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* EnableSequence [
|
|
|
|
|
* ....
|
|
|
|
|
* ]
|
2010-09-07 15:35:14 +02:00
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* DisableSequence [
|
|
|
|
|
* ...
|
|
|
|
|
* ]
|
2010-10-26 14:26:46 +02:00
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* TransitionSequence."ToModifierName" [
|
|
|
|
|
* ...
|
|
|
|
|
* ]
|
2010-09-07 15:35:14 +02:00
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* # 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"
|
|
|
|
|
* }
|
|
|
|
|
* }
|
2011-06-03 14:56:31 -06:00
|
|
|
*
|
|
|
|
|
* SupportedDevice and ConflictingDevice cannot be specified together.
|
|
|
|
|
* Both are optional.
|
2010-09-07 15:35:14 +02:00
|
|
|
*/
|
|
|
|
|
static int parse_modifier(snd_use_case_mgr_t *uc_mgr,
|
2020-05-27 13:53:10 +02:00
|
|
|
snd_config_t *cfg,
|
|
|
|
|
void *data1, void *data2)
|
2010-09-07 15:35:14 +02:00
|
|
|
{
|
|
|
|
|
struct use_case_verb *verb = data1;
|
|
|
|
|
struct use_case_modifier *modifier;
|
2020-05-27 13:53:10 +02:00
|
|
|
char *name;
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_iterator_t i, next;
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_t *n;
|
|
|
|
|
int err;
|
|
|
|
|
|
2020-05-27 13:53:10 +02:00
|
|
|
if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
|
|
|
|
|
return -EINVAL;
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* allocate modifier */
|
|
|
|
|
modifier = calloc(1, sizeof(*modifier));
|
2020-05-27 13:53:10 +02:00
|
|
|
if (modifier == NULL) {
|
|
|
|
|
free(name);
|
2010-09-07 15:35:14 +02:00
|
|
|
return -ENOMEM;
|
2020-05-27 13:53:10 +02:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
INIT_LIST_HEAD(&modifier->enable_list);
|
|
|
|
|
INIT_LIST_HEAD(&modifier->disable_list);
|
|
|
|
|
INIT_LIST_HEAD(&modifier->transition_list);
|
2011-06-03 14:56:31 -06:00
|
|
|
INIT_LIST_HEAD(&modifier->dev_list.list);
|
2010-09-22 14:31:15 +02:00
|
|
|
INIT_LIST_HEAD(&modifier->value_list);
|
2010-09-07 15:35:14 +02:00
|
|
|
list_add_tail(&modifier->list, &verb->modifier_list);
|
2020-05-27 13:53:10 +02:00
|
|
|
modifier->name = name;
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-09-07 15:35:14 +02:00
|
|
|
const char *id;
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "Comment") == 0) {
|
2020-05-27 15:04:31 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &modifier->comment);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to get modifier comment");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "SupportedDevice") == 0) {
|
2011-06-03 14:56:31 -06:00
|
|
|
err = parse_device_list(uc_mgr, &modifier->dev_list,
|
|
|
|
|
DEVLIST_SUPPORTED, n);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
2010-10-26 14:26:46 +02:00
|
|
|
uc_error("error: failed to parse supported"
|
|
|
|
|
" device list");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-03 14:56:31 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
if (strcmp(id, "TransitionSequence") == 0) {
|
2010-09-07 15:35:14 +02:00
|
|
|
err = parse_transition(uc_mgr, &modifier->transition_list, n);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to parse transition"
|
|
|
|
|
" modifier");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-22 14:31:15 +02:00
|
|
|
if (strcmp(id, "Value") == 0) {
|
|
|
|
|
err = parse_value(uc_mgr, &modifier->value_list, n);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
2010-09-22 14:31:15 +02:00
|
|
|
uc_error("error: failed to parse Value");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Parse Device Use Cases
|
|
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* # Each device is described in new section. N devices are allowed
|
|
|
|
|
* SectionDevice."Headphones" {
|
2010-09-07 15:35:14 +02:00
|
|
|
* Comment "Headphones connected to 3.5mm jack"
|
|
|
|
|
*
|
2020-01-17 18:21:08 +01:00
|
|
|
* SupportedDevice [
|
2011-06-03 14:56:31 -06:00
|
|
|
* "x"
|
|
|
|
|
* "y"
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* ConflictingDevice [
|
|
|
|
|
* "x"
|
|
|
|
|
* "y"
|
|
|
|
|
* ]
|
|
|
|
|
*
|
2010-09-07 15:35:14 +02:00
|
|
|
* EnableSequence [
|
|
|
|
|
* ....
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* DisableSequence [
|
|
|
|
|
* ...
|
|
|
|
|
* ]
|
|
|
|
|
*
|
2010-10-26 14:26:46 +02:00
|
|
|
* TransitionSequence."ToDevice" [
|
|
|
|
|
* ...
|
|
|
|
|
* ]
|
|
|
|
|
*
|
2010-09-22 14:31:15 +02:00
|
|
|
* Value {
|
|
|
|
|
* PlaybackVolume "name='Master Playback Volume',index=2"
|
|
|
|
|
* PlaybackSwitch "name='Master Playback Switch',index=2"
|
|
|
|
|
* }
|
2010-09-07 15:35:14 +02:00
|
|
|
* }
|
|
|
|
|
*/
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
static int parse_device(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg,
|
2020-05-27 13:53:10 +02:00
|
|
|
void *data1, void *data2)
|
2010-09-07 15:35:14 +02:00
|
|
|
{
|
|
|
|
|
struct use_case_verb *verb = data1;
|
2020-05-27 13:53:10 +02:00
|
|
|
char *name;
|
2010-09-07 15:35:14 +02:00
|
|
|
struct use_case_device *device;
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_iterator_t i, next;
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_t *n;
|
|
|
|
|
int err;
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
|
2020-05-27 13:53:10 +02:00
|
|
|
if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
|
|
|
|
|
return -EINVAL;
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
device = calloc(1, sizeof(*device));
|
2020-05-27 13:53:10 +02:00
|
|
|
if (device == NULL) {
|
|
|
|
|
free(name);
|
2010-09-07 15:35:14 +02:00
|
|
|
return -ENOMEM;
|
2020-05-27 13:53:10 +02:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
INIT_LIST_HEAD(&device->enable_list);
|
|
|
|
|
INIT_LIST_HEAD(&device->disable_list);
|
|
|
|
|
INIT_LIST_HEAD(&device->transition_list);
|
2011-06-03 14:56:31 -06:00
|
|
|
INIT_LIST_HEAD(&device->dev_list.list);
|
2010-09-22 14:31:15 +02:00
|
|
|
INIT_LIST_HEAD(&device->value_list);
|
2010-09-07 15:35:14 +02:00
|
|
|
list_add_tail(&device->list, &verb->device_list);
|
2020-05-27 13:53:10 +02:00
|
|
|
device->name = name;
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-09-07 15:35:14 +02:00
|
|
|
const char *id;
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "Comment") == 0) {
|
2020-05-27 15:04:31 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &device->comment);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to get device comment");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-03 14:56:31 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
if (strcmp(id, "TransitionSequence") == 0) {
|
|
|
|
|
uc_dbg("TransitionSequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
err = parse_transition(uc_mgr, &device->transition_list, n);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to parse transition"
|
|
|
|
|
" device");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-22 14:31:15 +02:00
|
|
|
if (strcmp(id, "Value") == 0) {
|
|
|
|
|
err = parse_value(uc_mgr, &device->value_list, n);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
2010-09-22 14:31:15 +02:00
|
|
|
uc_error("error: failed to parse Value");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-07 10:09:07 +01:00
|
|
|
/*
|
|
|
|
|
* 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"
|
|
|
|
|
*/
|
2020-05-27 15:13:55 +02:00
|
|
|
static int parse_dev_name_list(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg,
|
2020-02-07 10:09:07 +01:00
|
|
|
struct list_head *list)
|
|
|
|
|
{
|
|
|
|
|
snd_config_t *n;
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
const char *id, *name1;
|
2020-05-27 15:13:55 +02:00
|
|
|
char *name1s, *name2;
|
2020-02-07 10:09:07 +01:00
|
|
|
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;
|
|
|
|
|
|
2020-05-27 15:13:55 +02:00
|
|
|
err = get_string3(uc_mgr, name1, &name1s);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
err = parse_string_substitute3(uc_mgr, n, &name2);
|
2020-02-07 10:09:07 +01:00
|
|
|
if (err < 0) {
|
2020-05-27 15:13:55 +02:00
|
|
|
free(name1s);
|
2020-02-07 10:09:07 +01:00
|
|
|
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);
|
2020-05-27 15:13:55 +02:00
|
|
|
if (strcmp(dev->name1, name1s) == 0) {
|
2020-02-07 10:09:07 +01:00
|
|
|
free(name2);
|
2020-05-27 15:13:55 +02:00
|
|
|
free(name1s);
|
2020-02-07 10:09:07 +01:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-27 15:13:55 +02:00
|
|
|
free(name1s);
|
|
|
|
|
|
2020-02-07 10:09:07 +01:00
|
|
|
dev = calloc(1, sizeof(*dev));
|
2020-05-27 15:13:55 +02:00
|
|
|
if (dev == NULL) {
|
|
|
|
|
free(name2);
|
2020-02-07 10:09:07 +01:00
|
|
|
return -ENOMEM;
|
2020-05-27 15:13:55 +02:00
|
|
|
}
|
2020-02-07 10:09:07 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
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)
|
2010-09-07 15:35:14 +02:00
|
|
|
{
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
const char *id, *idchild;
|
|
|
|
|
int child_ctr = 0, legacy_format = 1;
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *child;
|
2010-09-07 15:35:14 +02:00
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_get_id(cfg, &id);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
|
|
|
|
|
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);
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
static int parse_device_name(snd_use_case_mgr_t *uc_mgr,
|
2010-12-21 23:11:51 +01:00
|
|
|
snd_config_t *cfg,
|
|
|
|
|
void *data1,
|
|
|
|
|
void *data2 ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
return parse_compound_check_legacy(uc_mgr, cfg, parse_device, data1);
|
|
|
|
|
}
|
2010-12-21 23:11:51 +01:00
|
|
|
|
UCM: Deprecate index on Section{Device,Modifier}
The previous supported "legacy" syntax was:
SectionDevice."Speaker".0 {
SectionModifier."Capture Voice".0 {
This change supports new syntax:
SectionDevice."Speaker" {
SectionModifier."Capture Voice" {
... but also allows the old syntax, iff the index is exactly "0". If an
index is present, but not exactly "0", parsing will appear to succeed,
but produce an empty device or modifier.
When naming devices and modifiers, even if the legacy format is used,
any index is not included in the name; i.e. both sets of syntax above
name the device just "Speaker".
The SupportedDevice list syntax still also accepts either "x" or "x.0",
but internally strips ".0" from the tail of any device name. Any other
name including "." is disallowed.
Finally, when comparing device or modifier names, a simple exact string
compare is now used, since no index data is ever present in device or
modifier names.
The one functional change introduced here is that a SupportedDevice
entry of just "x" will now only ever match a single device. It previously
acted as a wildcard for any device named "x.foo".
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2011-06-03 14:56:30 -06:00
|
|
|
static int parse_modifier_name(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg,
|
|
|
|
|
void *data1,
|
|
|
|
|
void *data2 ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
2020-02-07 10:09:07 +01:00
|
|
|
return parse_compound(uc_mgr, cfg, parse_modifier, data1, data2);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-07 16:18:11 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2020-02-10 13:18:23 +01:00
|
|
|
uc_error("error: unable to find device '%s'", dst);
|
2020-02-07 16:18:11 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-07 10:09:07 +01:00
|
|
|
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 */
|
2020-04-06 17:20:24 +02:00
|
|
|
list_for_each(pos, &verb->remove_list) {
|
2020-02-07 10:09:07 +01:00
|
|
|
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);
|
2020-02-07 16:18:11 +01:00
|
|
|
|
|
|
|
|
/* handle conflicting/supported lists */
|
|
|
|
|
return verb_dev_list_check(verb);
|
2010-12-21 23:11:51 +01:00
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/*
|
|
|
|
|
* 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"
|
2010-09-22 14:31:15 +02:00
|
|
|
* cset "name='Master Playback Volume',index=2 25,25"
|
2010-09-07 15:35:14 +02:00
|
|
|
* 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"
|
|
|
|
|
* ]
|
|
|
|
|
*
|
2010-10-26 14:26:46 +02:00
|
|
|
* # Optional transition verb
|
|
|
|
|
* TransitionSequence."ToCaseName" [
|
|
|
|
|
* msleep 1
|
|
|
|
|
* ]
|
|
|
|
|
*
|
2010-09-07 15:35:14 +02:00
|
|
|
* # Optional TQ and ALSA PCMs
|
2010-09-22 14:31:15 +02:00
|
|
|
* Value {
|
|
|
|
|
* TQ HiFi
|
2010-10-26 14:26:46 +02:00
|
|
|
* CapturePCM "hw:0"
|
|
|
|
|
* PlaybackPCM "hw:0"
|
2010-09-22 14:31:15 +02:00
|
|
|
* }
|
2010-09-07 15:35:14 +02:00
|
|
|
* }
|
|
|
|
|
*/
|
|
|
|
|
static int parse_verb(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
struct use_case_verb *verb,
|
|
|
|
|
snd_config_t *cfg)
|
|
|
|
|
{
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_iterator_t i, next;
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_t *n;
|
|
|
|
|
int err;
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* parse verb section */
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-09-07 15:35:14 +02:00
|
|
|
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");
|
2010-10-26 14:26:46 +02:00
|
|
|
err = parse_sequence(uc_mgr, &verb->enable_list, n);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to parse verb enable sequence");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "DisableSequence") == 0) {
|
|
|
|
|
uc_dbg("Parse DisableSequence");
|
2010-10-26 14:26:46 +02:00
|
|
|
err = parse_sequence(uc_mgr, &verb->disable_list, n);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to parse verb disable sequence");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
if (strcmp(id, "TransitionSequence") == 0) {
|
|
|
|
|
uc_dbg("Parse TransitionSequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
err = parse_transition(uc_mgr, &verb->transition_list, n);
|
|
|
|
|
if (err < 0) {
|
2010-10-26 14:26:46 +02:00
|
|
|
uc_error("error: failed to parse transition sequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-22 14:31:15 +02:00
|
|
|
if (strcmp(id, "Value") == 0) {
|
|
|
|
|
uc_dbg("Parse Value");
|
|
|
|
|
err = parse_value(uc_mgr, &verb->value_list, n);
|
2010-09-07 15:35:14 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_iterator_t i, next;
|
2010-09-07 15:35:14 +02:00
|
|
|
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);
|
2016-11-28 13:33:54 +08:00
|
|
|
INIT_LIST_HEAD(&verb->cmpt_device_list);
|
2010-09-07 15:35:14 +02:00
|
|
|
INIT_LIST_HEAD(&verb->modifier_list);
|
2010-09-22 14:31:15 +02:00
|
|
|
INIT_LIST_HEAD(&verb->value_list);
|
2020-02-07 10:09:07 +01:00
|
|
|
INIT_LIST_HEAD(&verb->rename_list);
|
|
|
|
|
INIT_LIST_HEAD(&verb->remove_list);
|
2010-09-07 15:35:14 +02:00
|
|
|
list_add_tail(&verb->list, &uc_mgr->verb_list);
|
2011-01-27 23:17:43 -06:00
|
|
|
if (use_case_name == NULL)
|
|
|
|
|
return -EINVAL;
|
2010-09-07 15:35:14 +02:00
|
|
|
verb->name = strdup(use_case_name);
|
|
|
|
|
if (verb->name == NULL)
|
|
|
|
|
return -ENOMEM;
|
2011-01-27 23:17:43 -06:00
|
|
|
|
|
|
|
|
if (comment != NULL) {
|
|
|
|
|
verb->comment = strdup(comment);
|
|
|
|
|
if (verb->comment == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
|
|
|
|
/* open Verb file for reading */
|
2020-05-16 15:47:19 +02:00
|
|
|
err = uc_mgr_config_load_file(uc_mgr, file, &cfg);
|
|
|
|
|
if (err < 0)
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
2021-01-07 17:32:11 +01:00
|
|
|
goto _err;
|
2019-11-14 15:13:36 +01:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* parse master config sections */
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-09-07 15:35:14 +02:00
|
|
|
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);
|
2019-05-24 21:11:00 +02:00
|
|
|
goto _err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* find device sections and parse them */
|
|
|
|
|
if (strcmp(id, "SectionDevice") == 0) {
|
2010-12-21 23:11:52 +01:00
|
|
|
err = parse_compound(uc_mgr, n,
|
|
|
|
|
parse_device_name, verb, NULL);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: %s failed to parse device",
|
|
|
|
|
file);
|
2019-05-24 21:11:00 +02:00
|
|
|
goto _err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* find modifier sections and parse them */
|
|
|
|
|
if (strcmp(id, "SectionModifier") == 0) {
|
|
|
|
|
err = parse_compound(uc_mgr, n,
|
2010-12-21 23:11:51 +01:00
|
|
|
parse_modifier_name, verb, NULL);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: %s failed to parse modifier",
|
|
|
|
|
file);
|
2019-05-24 21:11:00 +02:00
|
|
|
goto _err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-02-07 10:09:07 +01:00
|
|
|
|
|
|
|
|
/* device renames */
|
|
|
|
|
if (strcmp(id, "RenameDevice") == 0) {
|
2020-05-27 15:13:55 +02:00
|
|
|
err = parse_dev_name_list(uc_mgr, n, &verb->rename_list);
|
2020-02-07 10:09:07 +01:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: %s failed to parse device rename",
|
|
|
|
|
file);
|
|
|
|
|
goto _err;
|
|
|
|
|
}
|
2021-04-12 18:09:21 +02:00
|
|
|
continue;
|
2020-02-07 10:09:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* device remove */
|
|
|
|
|
if (strcmp(id, "RemoveDevice") == 0) {
|
2020-05-27 15:13:55 +02:00
|
|
|
err = parse_dev_name_list(uc_mgr, n, &verb->remove_list);
|
2020-02-07 10:09:07 +01:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: %s failed to parse device remove",
|
|
|
|
|
file);
|
|
|
|
|
goto _err;
|
|
|
|
|
}
|
2021-04-12 18:09:21 +02:00
|
|
|
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;
|
2020-02-07 10:09:07 +01:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-24 21:11:00 +02:00
|
|
|
snd_config_delete(cfg);
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|
2020-02-07 10:09:07 +01:00
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
return 0;
|
2019-05-24 21:11:00 +02:00
|
|
|
|
|
|
|
|
_err:
|
|
|
|
|
snd_config_delete(cfg);
|
|
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_iterator_t i, next;
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_t *n;
|
2020-05-27 14:47:08 +02:00
|
|
|
char *use_case_name, *file = NULL, *comment = NULL;
|
2010-09-07 15:35:14 +02:00
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
uc_error("compound type expected for use case section");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2019-11-05 13:02:41 +01:00
|
|
|
|
2020-05-27 14:47:08 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
2020-05-27 14:47:08 +02:00
|
|
|
goto __error;
|
2019-11-14 15:13:36 +01:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* parse master config sections */
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2020-05-27 14:47:08 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &file);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("failed to get File");
|
2020-05-27 14:47:08 +02:00
|
|
|
goto __error;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* get optional use case comment */
|
|
|
|
|
if (strncmp(id, "Comment", 7) == 0) {
|
2020-05-27 14:47:08 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &comment);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to get Comment");
|
2020-05-27 14:47:08 +02:00
|
|
|
goto __error;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uc_error("unknown field %s in master section");
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-31 14:24:19 +01:00
|
|
|
uc_dbg("use_case_name %s file '%s'", use_case_name, file);
|
2010-09-07 15:35:14 +02:00
|
|
|
|
|
|
|
|
/* do we have both use case name and file ? */
|
|
|
|
|
if (!file) {
|
|
|
|
|
uc_error("error: use case missing file");
|
2020-05-27 14:47:08 +02:00
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse verb file */
|
2020-05-27 14:47:08 +02:00
|
|
|
err = parse_verb_file(uc_mgr, use_case_name, comment, file);
|
|
|
|
|
|
|
|
|
|
__error:
|
|
|
|
|
free(use_case_name);
|
|
|
|
|
free(file);
|
|
|
|
|
free(comment);
|
|
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-05 18:50:02 +01:00
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-14 16:03:00 +02:00
|
|
|
/*
|
|
|
|
|
* parse controls which should be run only at initial boot
|
|
|
|
|
*/
|
2020-05-25 19:07:12 +02:00
|
|
|
static int parse_controls_boot(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
|
2020-05-14 16:03:00 +02:00
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
|
2020-10-06 10:47:11 +02:00
|
|
|
if (!list_empty(&uc_mgr->boot_list)) {
|
|
|
|
|
uc_error("Boot list is not empty");
|
2020-05-14 16:03:00 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2020-10-06 10:47:11 +02:00
|
|
|
err = parse_sequence(uc_mgr, &uc_mgr->boot_list, cfg);
|
2020-05-14 16:03:00 +02:00
|
|
|
if (err < 0) {
|
2020-05-25 19:07:12 +02:00
|
|
|
uc_error("Unable to parse BootSequence");
|
2020-05-14 16:03:00 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/*
|
2010-10-13 11:48:52 +02:00
|
|
|
* parse controls
|
2010-09-07 15:35:14 +02:00
|
|
|
*/
|
|
|
|
|
static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
|
|
|
|
|
{
|
|
|
|
|
int err;
|
2020-05-14 16:03:00 +02:00
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
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);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("Unable to parse SectionDefaults");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2020-05-14 16:03:00 +02:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
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>
|
|
|
|
|
*
|
2010-10-13 11:48:52 +02:00
|
|
|
* Comment "Nice Abstracted Soundcard"
|
|
|
|
|
*
|
2010-09-07 15:35:14 +02:00
|
|
|
* # The file is divided into Use case sections. One section per use case verb.
|
|
|
|
|
*
|
|
|
|
|
* SectionUseCase."Voice Call" {
|
2010-11-29 15:41:34 +01:00
|
|
|
* File "voice_call_blah"
|
|
|
|
|
* Comment "Make a voice phone call."
|
2010-09-07 15:35:14 +02:00
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* SectionUseCase."HiFi" {
|
2010-11-29 15:41:34 +01:00
|
|
|
* File "hifi_blah"
|
|
|
|
|
* Comment "Play and record HiFi quality Music."
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* # Define Value defaults
|
|
|
|
|
*
|
|
|
|
|
* ValueDefaults {
|
2010-11-29 15:49:13 +01:00
|
|
|
* PlaybackCTL "hw:CARD=0"
|
|
|
|
|
* CaptureCTL "hw:CARD=0"
|
2010-09-07 15:35:14 +02:00
|
|
|
* }
|
|
|
|
|
*
|
2020-05-14 16:03:00 +02:00
|
|
|
* # The initial boot (run once) configuration.
|
|
|
|
|
*
|
2020-05-25 19:07:12 +02:00
|
|
|
* BootSequence [
|
2020-05-14 16:03:00 +02:00
|
|
|
* cset "name='Master Playback Switch',index=2 1,1"
|
|
|
|
|
* cset "name='Master Playback Volume',index=2 25,25"
|
2020-05-19 08:31:39 +02:00
|
|
|
* ]
|
2020-05-14 16:03:00 +02:00
|
|
|
*
|
2010-09-07 15:35:14 +02:00
|
|
|
* # This file also stores the default sound card state.
|
|
|
|
|
*
|
|
|
|
|
* SectionDefaults [
|
2010-11-29 15:41:34 +01:00
|
|
|
* 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
|
|
|
|
|
* ........
|
2010-09-07 15:35:14 +02:00
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* # End of example file.
|
|
|
|
|
*/
|
|
|
|
|
static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
|
|
|
|
|
{
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_iterator_t i, next;
|
2010-09-07 15:35:14 +02:00
|
|
|
snd_config_t *n;
|
2010-10-13 11:48:52 +02:00
|
|
|
const char *id;
|
2019-11-09 11:53:32 +01:00
|
|
|
long l;
|
2010-10-13 11:48:52 +02:00
|
|
|
int err;
|
2010-09-07 15:35:14 +02:00
|
|
|
|
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
uc_error("compound type expected for master file");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-09 11:53:32 +01:00
|
|
|
if (uc_mgr->conf_format >= 2) {
|
|
|
|
|
err = snd_config_search(cfg, "Syntax", &n);
|
|
|
|
|
if (err < 0) {
|
2020-02-03 14:44:13 +01:00
|
|
|
uc_error("Syntax field not found in %s", uc_mgr->conf_file_name);
|
2019-11-09 11:53:32 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
err = snd_config_get_integer(n, &l);
|
|
|
|
|
if (err < 0) {
|
2020-02-03 14:44:13 +01:00
|
|
|
uc_error("Syntax field is invalid in %s", uc_mgr->conf_file_name);
|
2019-11-09 11:53:32 +01:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
if (l < 2 || l > SYNTAX_VERSION_MAX) {
|
2020-02-03 14:44:13 +01:00
|
|
|
uc_error("Incompatible syntax %d in %s", l, uc_mgr->conf_file_name);
|
2019-11-09 11:53:32 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2019-11-14 15:13:36 +01:00
|
|
|
/* delete this field to avoid strcmp() call in the loop */
|
|
|
|
|
snd_config_delete(n);
|
2020-05-20 12:17:39 +02:00
|
|
|
uc_mgr->conf_format = l;
|
2019-11-09 11:53:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2019-11-14 15:13:36 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* parse master config sections */
|
2019-11-14 15:13:36 +01:00
|
|
|
snd_config_for_each(i, next, cfg) {
|
2010-10-13 11:48:52 +02:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
2010-10-13 11:48:52 +02:00
|
|
|
if (strcmp(id, "Comment") == 0) {
|
2020-10-06 10:43:38 +02:00
|
|
|
err = parse_string_substitute3(uc_mgr, n, &uc_mgr->comment);
|
2010-10-13 11:48:52 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("error: failed to get master comment");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* find use case section and parse it */
|
|
|
|
|
if (strcmp(id, "SectionUseCase") == 0) {
|
2010-10-13 11:48:52 +02:00
|
|
|
err = parse_compound(uc_mgr, n,
|
2010-09-07 15:35:14 +02:00
|
|
|
parse_master_section,
|
|
|
|
|
NULL, NULL);
|
2010-10-13 11:48:52 +02:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-05 18:50:02 +01:00
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-14 16:03:00 +02:00
|
|
|
/* find default control values section (first boot only) */
|
2020-05-25 19:07:12 +02:00
|
|
|
if (strcmp(id, "BootSequence") == 0) {
|
|
|
|
|
err = parse_controls_boot(uc_mgr, n);
|
2020-05-14 16:03:00 +02:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* find default control values section and parse it */
|
|
|
|
|
if (strcmp(id, "SectionDefaults") == 0) {
|
2010-10-13 11:48:52 +02:00
|
|
|
err = parse_controls(uc_mgr, n);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2010-11-29 15:41:34 +01:00
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 18:09:21 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 10:23:25 +02:00
|
|
|
/* error */
|
|
|
|
|
if (strcmp(id, "Error") == 0)
|
|
|
|
|
return error_node(uc_mgr, n);
|
|
|
|
|
|
2021-05-13 18:28:20 +02:00
|
|
|
uc_error("unknown master file field %s", id);
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-03 19:05:54 +01:00
|
|
|
/* get the card info */
|
2019-11-04 14:42:45 +01:00
|
|
|
static int get_card_info(snd_use_case_mgr_t *mgr,
|
|
|
|
|
const char *ctl_name,
|
2020-06-03 17:12:19 +02:00
|
|
|
snd_ctl_card_info_t **info)
|
2019-11-03 19:05:54 +01:00
|
|
|
{
|
2020-06-03 17:12:19 +02:00
|
|
|
struct ctl_list *ctl_list;
|
2019-11-03 19:05:54 +01:00
|
|
|
int err;
|
|
|
|
|
|
2020-06-03 17:12:19 +02:00
|
|
|
err = uc_mgr_open_ctl(mgr, &ctl_list, ctl_name, 0);
|
2019-11-04 14:42:45 +01:00
|
|
|
if (err < 0)
|
2019-11-03 19:05:54 +01:00
|
|
|
return err;
|
|
|
|
|
|
2020-06-22 09:05:24 +02:00
|
|
|
if (info)
|
|
|
|
|
*info = ctl_list->ctl_info;
|
2019-11-03 19:05:54 +01:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
/* find the card in the local machine */
|
|
|
|
|
static int get_by_card_name(snd_use_case_mgr_t *mgr, const char *card_name)
|
2017-01-18 11:53:35 +08:00
|
|
|
{
|
|
|
|
|
int card, err;
|
|
|
|
|
snd_ctl_card_info_t *info;
|
2020-02-03 15:24:19 +01:00
|
|
|
const char *_driver, *_name, *_long_name;
|
2017-01-18 11:53:35 +08:00
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
|
2020-06-03 17:12:19 +02:00
|
|
|
/* clear the list, keep the only one CTL device */
|
2019-11-05 10:21:36 +01:00
|
|
|
uc_mgr_free_ctl_list(mgr);
|
|
|
|
|
|
2017-01-18 11:53:35 +08:00
|
|
|
sprintf(name, "hw:%d", card);
|
2020-06-03 17:12:19 +02:00
|
|
|
err = get_card_info(mgr, name, &info);
|
2019-11-03 19:05:54 +01:00
|
|
|
|
|
|
|
|
if (err == 0) {
|
2020-02-03 15:24:19 +01:00
|
|
|
_driver = snd_ctl_card_info_get_driver(info);
|
2019-11-03 19:05:54 +01:00
|
|
|
_name = snd_ctl_card_info_get_name(info);
|
|
|
|
|
_long_name = snd_ctl_card_info_get_longname(info);
|
2020-02-03 15:24:19 +01:00
|
|
|
if (!strcmp(card_name, _driver) ||
|
|
|
|
|
!strcmp(card_name, _name) ||
|
2020-05-26 18:54:31 +02:00
|
|
|
!strcmp(card_name, _long_name))
|
2019-11-03 19:05:54 +01:00
|
|
|
return 0;
|
2017-01-18 11:53:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (snd_card_next(&card) < 0) {
|
|
|
|
|
uc_error("snd_card_next");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-05 10:21:36 +01:00
|
|
|
uc_mgr_free_ctl_list(mgr);
|
|
|
|
|
|
2017-01-18 11:53:35 +08:00
|
|
|
return -1;
|
|
|
|
|
}
|
2019-05-24 21:11:00 +02:00
|
|
|
|
2019-11-03 19:05:54 +01:00
|
|
|
/* set the driver name and long name by the card ctl name */
|
2020-06-22 09:05:24 +02:00
|
|
|
static inline int get_by_card(snd_use_case_mgr_t *mgr, const char *ctl_name)
|
2019-11-03 19:05:54 +01:00
|
|
|
{
|
2020-06-22 09:05:24 +02:00
|
|
|
return get_card_info(mgr, ctl_name, NULL);
|
2019-11-03 19:05:54 +01:00
|
|
|
}
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
static int parse_toplevel_path(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
char *filename,
|
|
|
|
|
snd_config_t *cfg)
|
2010-09-07 15:35:14 +02:00
|
|
|
{
|
2020-05-26 18:54:31 +02:00
|
|
|
snd_config_iterator_t i, next, i2, next2;
|
|
|
|
|
snd_config_t *n, *n2;
|
|
|
|
|
const char *id;
|
|
|
|
|
char *dir = NULL, *file = NULL, fn[PATH_MAX];
|
|
|
|
|
long version;
|
2010-09-07 15:35:14 +02:00
|
|
|
int err;
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
uc_error("compound type expected for UseCasePath node");
|
2017-01-18 11:52:35 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
/* 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);
|
2020-06-08 09:09:22 +02:00
|
|
|
goto __next;
|
2020-05-26 18:54:31 +02:00
|
|
|
}
|
|
|
|
|
if (file == NULL) {
|
|
|
|
|
uc_error("File is not defined in %s!", filename);
|
2020-06-08 09:09:22 +02:00
|
|
|
goto __next;
|
2020-05-26 18:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ucm_filename(fn, sizeof(fn), version, dir, file);
|
|
|
|
|
if (access(fn, R_OK) == 0) {
|
|
|
|
|
if (replace_string(&uc_mgr->conf_dir_name, dir) == NULL) {
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
if (replace_string(&uc_mgr->conf_file_name, file) == NULL) {
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
strncpy(filename, fn, PATH_MAX);
|
|
|
|
|
uc_mgr->conf_format = version;
|
|
|
|
|
goto __ok;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-08 09:09:22 +02:00
|
|
|
__next:
|
2020-05-26 18:54:31 +02:00
|
|
|
free(file);
|
|
|
|
|
free(dir);
|
|
|
|
|
dir = NULL;
|
|
|
|
|
file = NULL;
|
2019-11-19 13:10:19 +01:00
|
|
|
}
|
2019-11-04 17:14:40 +01:00
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
err = -ENOENT;
|
|
|
|
|
goto __error;
|
|
|
|
|
|
|
|
|
|
__ok:
|
|
|
|
|
err = 0;
|
|
|
|
|
__error:
|
|
|
|
|
free(file);
|
|
|
|
|
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;
|
|
|
|
|
long l;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
uc_error("compound type expected for toplevel file");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = snd_config_search(cfg, "Syntax", &n);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
2020-05-26 18:54:31 +02:00
|
|
|
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);
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
2020-05-26 18:54:31 +02:00
|
|
|
if (l < 2 || l > SYNTAX_VERSION_MAX) {
|
|
|
|
|
uc_error("Incompatible syntax %d in %s", l, filename);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
/* delete this field to avoid strcmp() call in the loop */
|
|
|
|
|
snd_config_delete(n);
|
|
|
|
|
uc_mgr->conf_format = l;
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 18:28:20 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uc_error("unknown toplevel field %s", id);
|
2020-05-26 18:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2020-01-15 10:31:56 +01:00
|
|
|
|
2010-10-13 11:48:52 +02:00
|
|
|
return 0;
|
2020-05-26 18:54:31 +02:00
|
|
|
|
|
|
|
|
__error:
|
|
|
|
|
return err;
|
2010-10-13 11:48:52 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
/* load master use case file for sound card based on rules in ucm2/ucm.conf
|
2017-01-18 11:53:35 +08:00
|
|
|
*/
|
2010-10-13 11:48:52 +02:00
|
|
|
int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr)
|
|
|
|
|
{
|
|
|
|
|
snd_config_t *cfg;
|
2020-05-26 18:54:31 +02:00
|
|
|
const char *name;
|
2010-10-13 11:48:52 +02:00
|
|
|
int err;
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
name = uc_mgr->card_name;
|
2019-11-03 19:05:54 +01:00
|
|
|
if (strncmp(name, "hw:", 3) == 0) {
|
2020-05-26 18:54:31 +02:00
|
|
|
err = get_by_card(uc_mgr, name);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("card '%s' is not valid", name);
|
|
|
|
|
goto __error;
|
2019-11-03 19:05:54 +01:00
|
|
|
}
|
2020-05-26 18:54:31 +02:00
|
|
|
} 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);
|
2017-01-18 11:53:35 +08:00
|
|
|
}
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
err = load_toplevel_config(uc_mgr, &cfg);
|
2019-11-03 19:05:54 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
err = parse_master_file(uc_mgr, cfg);
|
|
|
|
|
snd_config_delete(cfg);
|
2019-11-05 10:21:36 +01:00
|
|
|
if (err < 0) {
|
|
|
|
|
uc_mgr_free_ctl_list(uc_mgr);
|
2010-09-07 15:35:14 +02:00
|
|
|
uc_mgr_free_verb(uc_mgr);
|
2019-11-05 10:21:36 +01:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
|
|
|
|
return err;
|
2019-11-03 19:05:54 +01:00
|
|
|
|
|
|
|
|
__error:
|
2019-11-04 14:42:45 +01:00
|
|
|
uc_mgr_free_ctl_list(uc_mgr);
|
2020-05-26 18:54:31 +02:00
|
|
|
replace_string(&uc_mgr->conf_dir_name, NULL);
|
2019-11-03 19:05:54 +01:00
|
|
|
return err;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
2010-10-13 11:48:52 +02:00
|
|
|
|
|
|
|
|
static int filename_filter(const struct dirent *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;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-28 13:33:34 +08:00
|
|
|
/* 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
|
2019-11-09 11:53:32 +01:00
|
|
|
* card under /usr/share/alsa/ucm2. This function will scan all the card
|
2016-11-28 13:33:34 +08:00
|
|
|
* directories and skip the component directories defined in the array
|
|
|
|
|
* component_dir.
|
|
|
|
|
*/
|
2010-10-13 11:48:52 +02:00
|
|
|
int uc_mgr_scan_master_configs(const char **_list[])
|
|
|
|
|
{
|
2020-05-26 18:54:31 +02:00
|
|
|
char filename[PATH_MAX], dfl[PATH_MAX], fn[FILENAME_MAX];
|
2019-11-09 11:53:32 +01:00
|
|
|
char *env = getenv(ALSA_CONFIG_UCM2_VAR);
|
|
|
|
|
const char **list, *d_name;
|
2010-10-13 11:48:52 +02:00
|
|
|
snd_config_t *cfg, *c;
|
2019-11-02 08:36:46 +01:00
|
|
|
int i, j, cnt, err;
|
2019-11-09 11:53:32 +01:00
|
|
|
long l;
|
2010-10-26 14:26:46 +02:00
|
|
|
ssize_t ss;
|
2010-10-13 11:48:52 +02:00
|
|
|
struct dirent **namelist;
|
|
|
|
|
|
2017-05-03 00:09:28 +02:00
|
|
|
if (env)
|
2021-04-22 11:47:48 +02:00
|
|
|
snprintf(filename, sizeof(filename), "%s/conf.virt.d", env);
|
2017-05-03 00:09:28 +02:00
|
|
|
else
|
2021-04-22 11:47:48 +02:00
|
|
|
snprintf(filename, sizeof(filename), "%s/ucm2/conf.virt.d",
|
2017-05-03 00:09:28 +02:00
|
|
|
snd_config_topdir());
|
2010-10-13 11:48:52 +02:00
|
|
|
|
2019-03-15 17:17:02 +08:00
|
|
|
#if defined(_GNU_SOURCE) && !defined(__NetBSD__) && !defined(__FreeBSD__) && !defined(__sun) && !defined(ANDROID)
|
2012-07-17 15:30:15 +05:30
|
|
|
#define SORTFUNC versionsort
|
|
|
|
|
#else
|
|
|
|
|
#define SORTFUNC alphasort
|
|
|
|
|
#endif
|
|
|
|
|
err = scandir(filename, &namelist, filename_filter, SORTFUNC);
|
2010-10-13 11:48:52 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
err = -errno;
|
|
|
|
|
uc_error("error: could not scan directory %s: %s",
|
|
|
|
|
filename, strerror(-err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
cnt = err;
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
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';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-13 11:48:52 +02:00
|
|
|
list = calloc(1, cnt * 2 * sizeof(char *));
|
|
|
|
|
if (list == NULL) {
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __err;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-02 08:36:46 +01:00
|
|
|
for (i = j = 0; i < cnt; i++) {
|
2016-11-28 13:33:34 +08:00
|
|
|
|
2019-11-09 11:53:32 +01:00
|
|
|
d_name = namelist[i]->d_name;
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
snprintf(fn, sizeof(fn), "%s.conf", d_name);
|
|
|
|
|
ucm_filename(filename, sizeof(filename), 2, d_name, fn);
|
2020-03-17 16:20:52 +01:00
|
|
|
if (eaccess(filename, R_OK))
|
|
|
|
|
continue;
|
|
|
|
|
|
2019-11-09 11:53:32 +01:00
|
|
|
err = uc_mgr_config_load(2, filename, &cfg);
|
2010-10-13 11:48:52 +02:00
|
|
|
if (err < 0)
|
|
|
|
|
goto __err;
|
2019-11-09 11:53:32 +01:00
|
|
|
err = snd_config_search(cfg, "Syntax", &c);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("Syntax field not found in %s", d_name);
|
2019-11-15 12:52:36 +01:00
|
|
|
snd_config_delete(cfg);
|
2019-11-09 11:53:32 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
err = snd_config_get_integer(c, &l);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
uc_error("Syntax field is invalid in %s", d_name);
|
2019-11-15 12:52:36 +01:00
|
|
|
snd_config_delete(cfg);
|
2019-11-09 11:53:32 +01:00
|
|
|
goto __err;
|
|
|
|
|
}
|
|
|
|
|
if (l < 2 || l > SYNTAX_VERSION_MAX) {
|
|
|
|
|
uc_error("Incompatible syntax %d in %s", l, d_name);
|
2019-11-15 12:52:36 +01:00
|
|
|
snd_config_delete(cfg);
|
2019-11-09 11:53:32 +01:00
|
|
|
goto __err;
|
|
|
|
|
}
|
2010-10-13 11:48:52 +02:00
|
|
|
err = snd_config_search(cfg, "Comment", &c);
|
|
|
|
|
if (err >= 0) {
|
2019-11-02 08:36:46 +01:00
|
|
|
err = parse_string(c, (char **)&list[j+1]);
|
2010-10-13 11:48:52 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
snd_config_delete(cfg);
|
|
|
|
|
goto __err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
snd_config_delete(cfg);
|
2019-11-09 11:53:32 +01:00
|
|
|
list[j] = strdup(d_name);
|
2019-11-02 08:36:46 +01:00
|
|
|
if (list[j] == NULL) {
|
2010-10-13 11:48:52 +02:00
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __err;
|
|
|
|
|
}
|
2019-11-02 08:36:46 +01:00
|
|
|
if (strcmp(dfl, list[j]) == 0) {
|
2010-10-26 14:26:46 +02:00
|
|
|
/* default to top */
|
2019-11-02 08:36:46 +01:00
|
|
|
const char *save1 = list[j];
|
|
|
|
|
const char *save2 = list[j + 1];
|
|
|
|
|
memmove(list + 2, list, j * sizeof(char *));
|
2010-10-26 14:26:46 +02:00
|
|
|
list[0] = save1;
|
|
|
|
|
list[1] = save2;
|
|
|
|
|
}
|
2019-11-02 08:36:46 +01:00
|
|
|
j += 2;
|
2010-10-13 11:48:52 +02:00
|
|
|
}
|
2019-11-02 08:36:46 +01:00
|
|
|
err = j;
|
2010-10-13 11:48:52 +02:00
|
|
|
|
|
|
|
|
__err:
|
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
|
|
|
free(namelist[i]);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
free((void *)list[i * 2]);
|
|
|
|
|
free((void *)list[i * 2 + 1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
free(namelist);
|
|
|
|
|
|
2019-05-24 21:11:00 +02:00
|
|
|
if (err >= 0) {
|
2010-10-13 11:48:52 +02:00
|
|
|
*_list = list;
|
2019-05-24 21:11:00 +02:00
|
|
|
} else {
|
|
|
|
|
free(list);
|
|
|
|
|
}
|
2010-10-13 11:48:52 +02:00
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|