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
|
2025-11-07 14:27:38 +01:00
|
|
|
* 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-12-13 14:40:56 +01:00
|
|
|
#include <sys/stat.h>
|
2021-04-12 18:09:21 +02:00
|
|
|
#include <stdbool.h>
|
2010-10-13 11:48:52 +02:00
|
|
|
#include <dirent.h>
|
2025-11-18 10:41:06 +01:00
|
|
|
#include <ctype.h>
|
2016-12-21 19:46:34 -03:00
|
|
|
#include <limits.h>
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2022-05-19 17:37:46 +02:00
|
|
|
static int filename_filter(const struct dirent64 *dirent);
|
2017-01-18 11:53:35 +08:00
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "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
|
|
|
|
|
*/
|
2023-07-01 19:09:47 +02:00
|
|
|
static int parse_string(snd_config_t *n, char **res)
|
2010-09-07 15:35:14 +02:00
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
*/
|
2023-07-01 19:09:47 +02:00
|
|
|
static int parse_string_substitute(snd_use_case_mgr_t *uc_mgr,
|
2020-05-20 11:55:33 +02:00
|
|
|
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
|
|
|
|
|
*/
|
2023-07-01 19:09:47 +02:00
|
|
|
static int parse_string_substitute3(snd_use_case_mgr_t *uc_mgr,
|
2020-05-27 14:47:08 +02:00
|
|
|
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
|
|
|
|
|
*/
|
2023-07-01 19:09:47 +02:00
|
|
|
static int parse_integer_substitute(snd_use_case_mgr_t *uc_mgr,
|
2020-05-27 15:20:03 +02:00
|
|
|
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
|
|
|
|
|
*/
|
2023-07-01 19:09:47 +02:00
|
|
|
static int parse_integer_substitute3(snd_use_case_mgr_t *uc_mgr,
|
2020-05-27 15:20:03 +02:00
|
|
|
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
|
|
|
|
|
*/
|
2023-07-01 19:09:47 +02:00
|
|
|
static int parse_is_name_safe(const char *name)
|
2011-01-31 14:24:19 +01:00
|
|
|
{
|
|
|
|
|
if (strchr(name, '.')) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "char '.' not allowed in '%s'", name);
|
2011-01-31 14:24:19 +01:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-01 19:09:47 +02:00
|
|
|
static int get_string3(snd_use_case_mgr_t *uc_mgr, const char *s1, char **s)
|
2020-05-27 15:13:55 +02:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-01 19:09:47 +02:00
|
|
|
static int parse_get_safe_name(snd_use_case_mgr_t *uc_mgr, snd_config_t *n,
|
2020-05-27 13:53:10 +02:00
|
|
|
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
|
|
|
|
2025-11-18 10:41:06 +01:00
|
|
|
/*
|
|
|
|
|
* Parse device index from device name
|
|
|
|
|
* According to use-case.h specification, device names can have numeric suffixes
|
|
|
|
|
* like HDMI1, HDMI2, or "Line 1", "Line 2".
|
|
|
|
|
* This function extracts the index and modifies the name to contain only the base.
|
|
|
|
|
*/
|
|
|
|
|
static int parse_device_index(char **name, int *index)
|
|
|
|
|
{
|
|
|
|
|
char *p, *num_start;
|
|
|
|
|
long idx;
|
|
|
|
|
size_t len;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (!name || !*name || !index)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
len = strlen(*name);
|
|
|
|
|
if (len == 0)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
/* Start from the end and find where digits begin */
|
|
|
|
|
p = *name + len - 1;
|
|
|
|
|
|
|
|
|
|
/* Skip trailing digits */
|
|
|
|
|
while (p > *name && isdigit(*p))
|
|
|
|
|
p--;
|
|
|
|
|
|
|
|
|
|
/* If no digits found at the end, index is 0 (no index) */
|
|
|
|
|
if (p == *name + len - 1) {
|
|
|
|
|
*index = 0;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Move to first digit */
|
|
|
|
|
p++;
|
|
|
|
|
|
|
|
|
|
/* Check if there's an optional space before the number */
|
|
|
|
|
if (p > *name && *(p - 1) == ' ')
|
|
|
|
|
p--;
|
|
|
|
|
|
|
|
|
|
/* Parse the index */
|
|
|
|
|
num_start = *p == ' ' ? p + 1 : p;
|
|
|
|
|
err = safe_strtol(num_start, &idx);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
/* No valid number found */
|
|
|
|
|
*index = 0;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*index = (int)idx;
|
|
|
|
|
|
|
|
|
|
/* Truncate the name to remove the index part */
|
|
|
|
|
if (*p == ' ')
|
|
|
|
|
*p = '\0'; /* Remove space and number */
|
|
|
|
|
else if (p > *name)
|
|
|
|
|
*p = '\0'; /* Remove number only */
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to get Error string");
|
2020-10-06 10:23:25 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
2021-10-28 11:48:54 +02:00
|
|
|
if (!uc_mgr->suppress_nodev_errors)
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "%s", s);
|
2020-10-06 10:23:25 +02:00
|
|
|
free(s);
|
|
|
|
|
return -ENXIO;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-20 19:37:32 +02:00
|
|
|
/*
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static int parse_syntax_field(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg, const char *filename)
|
|
|
|
|
{
|
|
|
|
|
snd_config_t *n;
|
|
|
|
|
long l;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_search(cfg, "Syntax", &n);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Syntax field not found in %s", filename);
|
2022-10-20 19:37:32 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
err = snd_config_get_integer(n, &l);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Syntax field is invalid in %s", filename);
|
2022-10-20 19:37:32 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
if (l < 2 || l > SYNTAX_VERSION_MAX) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Incompatible syntax %ld in %s", l, filename);
|
2022-10-20 19:37:32 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
/* delete this field to optimize strcmp() call in the parsing loop */
|
|
|
|
|
snd_config_delete(n);
|
|
|
|
|
uc_mgr->conf_format = l;
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for DefineRegex");
|
2020-05-19 16:48:43 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 15:37:42 +02:00
|
|
|
if (uc_mgr->conf_format < 3) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "DefineRegex is supported in v3+ syntax");
|
2020-05-26 15:37:42 +02:00
|
|
|
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;
|
2022-05-16 13:16:01 +02:00
|
|
|
if (id[0] == '@') {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "value names starting with '@' are reserved for application variables");
|
2022-05-16 13:16:01 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2020-05-19 16:48:43 +02:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for Define");
|
2020-05-20 19:04:36 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 15:37:42 +02:00
|
|
|
if (uc_mgr->conf_format < 3) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Define is supported in v3+ syntax");
|
2020-05-26 15:37:42 +02:00
|
|
|
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;
|
2022-05-16 13:16:01 +02:00
|
|
|
if (id[0] == '@') {
|
|
|
|
|
free(s);
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "value names starting with '@' are reserved for application variables");
|
2022-05-16 13:16:01 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2022-05-13 16:07:38 +02:00
|
|
|
err = uc_mgr_set_variable(uc_mgr, id, s);
|
2020-05-20 19:04:36 +02:00
|
|
|
free(s);
|
2022-05-13 16:07:38 +02:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2020-05-20 19:04:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2022-05-13 16:07:38 +02:00
|
|
|
/*
|
|
|
|
|
* Evaluate macro definitions (in-place delete)
|
|
|
|
|
*/
|
|
|
|
|
static int evaluate_define_macro(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg)
|
|
|
|
|
{
|
|
|
|
|
snd_config_t *d;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_search(cfg, "DefineMacro", &d);
|
|
|
|
|
if (err == -ENOENT)
|
|
|
|
|
return 1;
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for DefineMacro");
|
2022-05-13 16:07:38 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 08:45:33 +02:00
|
|
|
if (uc_mgr->conf_format < 6) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "DefineMacro is supported in v6+ syntax");
|
2022-05-13 16:07:38 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = snd_config_merge(uc_mgr->macros, d, 0);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2025-12-01 13:32:10 +01:00
|
|
|
|
2022-05-13 16:07:38 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int evaluate_macro1(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *dst,
|
|
|
|
|
snd_config_t *args)
|
|
|
|
|
{
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *m, *mc, *a, *n;
|
|
|
|
|
const char *mid, *id;
|
2024-04-24 15:08:48 +02:00
|
|
|
char name[128], *var, *var2;
|
2022-05-13 16:07:38 +02:00
|
|
|
const char *s;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_get_id(args, &mid);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
err = snd_config_search(uc_mgr->macros, mid, &m);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Macro '%s' is not defined", mid);
|
2022-05-13 16:07:38 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a = args;
|
|
|
|
|
if (snd_config_get_type(args) == SND_CONFIG_TYPE_STRING) {
|
|
|
|
|
err = snd_config_get_string(args, &s);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
err = snd_config_load_string(&a, s, 0);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
} else if (snd_config_get_type(args) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* set arguments */
|
|
|
|
|
snd_config_for_each(i, next, a) {
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
err = snd_config_get_id(n, &id);
|
|
|
|
|
if (err < 0)
|
2022-07-08 21:56:57 +02:00
|
|
|
goto __err_path;
|
2024-04-24 15:15:48 +02:00
|
|
|
snprintf(name, sizeof(name), "__%s", id);
|
|
|
|
|
if (uc_mgr_get_variable(uc_mgr, name)) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Macro argument '%s' is already defined", name);
|
2024-04-24 15:15:48 +02:00
|
|
|
goto __err_path;
|
|
|
|
|
}
|
2022-05-13 16:07:38 +02:00
|
|
|
err = snd_config_get_ascii(n, &var);
|
|
|
|
|
if (err < 0)
|
2022-07-08 21:56:57 +02:00
|
|
|
goto __err_path;
|
2024-04-24 15:33:40 +02:00
|
|
|
if (uc_mgr->conf_format < 7) {
|
|
|
|
|
err = uc_mgr_set_variable(uc_mgr, name, var);
|
|
|
|
|
free(var);
|
|
|
|
|
} else {
|
|
|
|
|
err = uc_mgr_get_substituted_value(uc_mgr, &var2, var);
|
|
|
|
|
free(var);
|
|
|
|
|
if (err >= 0) {
|
|
|
|
|
err = uc_mgr_set_variable(uc_mgr, name, var2);
|
|
|
|
|
free(var2);
|
|
|
|
|
}
|
2024-04-24 15:08:48 +02:00
|
|
|
}
|
2022-05-13 16:07:38 +02:00
|
|
|
if (err < 0)
|
2022-07-08 21:56:57 +02:00
|
|
|
goto __err_path;
|
2022-05-13 16:07:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* merge + substitute variables */
|
|
|
|
|
err = snd_config_copy(&mc, m);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __err_path;
|
2022-05-17 19:21:22 +02:00
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, mc);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
snd_config_delete(mc);
|
|
|
|
|
goto __err_path;
|
|
|
|
|
}
|
2022-05-13 16:07:38 +02:00
|
|
|
err = uc_mgr_config_tree_merge(uc_mgr, dst, mc, NULL, NULL);
|
|
|
|
|
snd_config_delete(mc);
|
|
|
|
|
|
|
|
|
|
/* delete arguments */
|
|
|
|
|
snd_config_for_each(i, next, a) {
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
err = snd_config_get_id(n, &id);
|
|
|
|
|
if (err < 0)
|
2022-07-08 21:56:57 +02:00
|
|
|
goto __err_path;
|
2022-05-19 11:27:37 +02:00
|
|
|
snprintf(name, sizeof(name), "__%s", id);
|
2022-05-13 16:07:38 +02:00
|
|
|
err = uc_mgr_delete_variable(uc_mgr, name);
|
|
|
|
|
if (err < 0)
|
2022-07-08 21:56:57 +02:00
|
|
|
goto __err_path;
|
2022-05-13 16:07:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
__err_path:
|
|
|
|
|
if (a != args)
|
|
|
|
|
snd_config_delete(a);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Evaluate macro definitions and instances (in-place delete)
|
|
|
|
|
*/
|
|
|
|
|
static int evaluate_macro(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg)
|
|
|
|
|
{
|
|
|
|
|
snd_config_iterator_t i, i2, next, next2;
|
|
|
|
|
snd_config_t *d, *n, *n2;
|
|
|
|
|
int err, ret;
|
|
|
|
|
|
|
|
|
|
ret = evaluate_define_macro(uc_mgr, cfg);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
err = snd_config_search(cfg, "Macro", &d);
|
|
|
|
|
if (err == -ENOENT)
|
|
|
|
|
return ret;
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for DefineMacro");
|
2022-05-13 16:07:38 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 08:45:33 +02:00
|
|
|
if (uc_mgr->conf_format < 6) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Macro is supported in v6+ syntax");
|
2022-05-13 16:07:38 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snd_config_for_each(i, next, d) {
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
|
2022-05-17 18:19:44 +02:00
|
|
|
const char *id;
|
|
|
|
|
if (snd_config_get_id(n, &id))
|
|
|
|
|
id = "";
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for Macro.%s", id);
|
2022-05-13 16:07:38 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
snd_config_for_each(i2, next2, n) {
|
|
|
|
|
n2 = snd_config_iterator_entry(i2);
|
|
|
|
|
err = evaluate_macro1(uc_mgr, cfg, n2);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snd_config_delete(d);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
*/
|
2022-05-18 13:10:35 +02:00
|
|
|
static int evaluate_condition(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
|
2019-11-14 15:13:36 +01:00
|
|
|
{
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
/*
|
|
|
|
|
* Evaluate variant (in-place)
|
|
|
|
|
*/
|
|
|
|
|
static int evaluate_variant(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
|
|
|
|
|
{
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *n, *c;
|
|
|
|
|
const char *id;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_search(cfg, "Variant", &c);
|
|
|
|
|
if (err == -ENOENT)
|
|
|
|
|
return 1;
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
if (uc_mgr->conf_format < 6) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Variant is supported in v6+ syntax");
|
2022-05-18 13:10:35 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (uc_mgr->parse_master_section)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
if (uc_mgr->parse_variant == NULL)
|
|
|
|
|
goto __ret;
|
|
|
|
|
|
|
|
|
|
snd_config_for_each(i, next, c) {
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, uc_mgr->parse_variant))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, n);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
err = uc_mgr_config_tree_merge(uc_mgr, cfg, n, NULL, NULL);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
snd_config_delete(c);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
__ret:
|
|
|
|
|
snd_config_delete(c);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2022-05-13 16:07:38 +02:00
|
|
|
long iterations = 10000;
|
2022-05-18 13:10:35 +02:00
|
|
|
int err1 = 0, err2 = 0, err3 = 0, err4 = 0, err5 = 0;
|
2020-05-16 15:47:19 +02:00
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0 || err5 == 0) {
|
2022-05-13 16:07:38 +02:00
|
|
|
if (iterations == 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Maximal inplace evaluation iterations number reached (recursive references?)");
|
2022-05-13 16:07:38 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
iterations--;
|
2020-05-20 19:04:36 +02:00
|
|
|
/* 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;
|
2022-05-13 16:07:38 +02:00
|
|
|
/* include or macro may define another variables */
|
2020-05-20 19:34:15 +02:00
|
|
|
/* conditions may depend on them */
|
|
|
|
|
if (err2 == 0)
|
|
|
|
|
continue;
|
2022-05-18 13:10:35 +02:00
|
|
|
err3 = evaluate_variant(uc_mgr, cfg);
|
|
|
|
|
if (err3 < 0)
|
|
|
|
|
return err3;
|
|
|
|
|
if (err3 == 0)
|
|
|
|
|
continue;
|
2022-05-17 19:25:20 +02:00
|
|
|
uc_mgr->macro_hops++;
|
|
|
|
|
if (uc_mgr->macro_hops > 100) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Maximal macro hops reached!");
|
2022-05-17 19:25:20 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2022-05-18 13:10:35 +02:00
|
|
|
err4 = evaluate_macro(uc_mgr, cfg);
|
2022-05-17 19:25:20 +02:00
|
|
|
uc_mgr->macro_hops--;
|
2022-05-13 16:07:38 +02:00
|
|
|
if (err4 < 0)
|
2022-05-18 13:10:35 +02:00
|
|
|
return err4;
|
|
|
|
|
if (err4 == 0)
|
|
|
|
|
continue;
|
|
|
|
|
err5 = evaluate_condition(uc_mgr, cfg);
|
|
|
|
|
if (err5 < 0)
|
|
|
|
|
return err5;
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for %s", id);
|
2021-04-12 18:09:21 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unknown field %s", id);
|
2021-04-12 18:09:21 +02:00
|
|
|
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) {
|
2021-05-18 11:20:18 +02:00
|
|
|
snd_config_delete(cfg);
|
2021-04-12 18:09:21 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
2021-05-18 11:20:56 +02:00
|
|
|
err = snd_config_merge(uc_mgr->local_config, cfg, 0);
|
2021-04-12 18:09:21 +02:00
|
|
|
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);
|
2021-05-18 11:20:18 +02:00
|
|
|
if (err < 0)
|
2021-04-12 18:09:21 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
2021-05-18 11:20:56 +02:00
|
|
|
err = snd_config_merge(uc_mgr->local_config, config, 0);
|
2021-05-18 11:20:18 +02:00
|
|
|
if (err < 0)
|
2021-04-12 18:09:21 +02:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for %s", id);
|
2021-04-12 18:09:21 +02:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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
|
|
|
}
|
2025-11-07 14:27:38 +01: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;
|
2025-11-07 14:27:38 +01:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for %s", id);
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for %s, is %d", id, snd_config_get_type(cfg));
|
2010-09-07 15:35:14 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2025-11-07 14:27:38 +01:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
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') {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "device name %s contains a '.',"
|
|
|
|
|
" and is not legacy foo.0 format", 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;
|
|
|
|
|
}
|
|
|
|
|
*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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "multiple supported or conflicting device lists");
|
2025-11-07 15:46:16 +01:00
|
|
|
|
2011-06-03 14:56:31 -06:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for %s", id);
|
2010-10-26 14:26:46 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "compound is expected for sequence definition");
|
2010-10-26 14:26:46 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "string type is expected for sequence command");
|
2010-11-10 16:06:29 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2025-11-11 16:40:36 +01:00
|
|
|
err = snd_config_get_string(n, &cmd);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2010-11-10 16:06:29 +01:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "cdev requires a string!");
|
2010-11-23 15:58:14 +01:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "%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
|
|
|
|
2022-05-19 10:08:48 +02:00
|
|
|
if (strcmp(cmd, "enadev") == 0 ||
|
|
|
|
|
strcmp(cmd, "disdev") == 0) {
|
|
|
|
|
/* need to enable or disable a component device */
|
2016-11-28 13:33:54 +08:00
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_CMPT_SEQ;
|
2022-05-19 10:08:48 +02:00
|
|
|
err = parse_component_seq(uc_mgr, n,
|
|
|
|
|
strcmp(cmd, "enadev") == 0,
|
2016-11-28 13:33:54 +08:00
|
|
|
&curr->data.cmpt_seq);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "%s requires a valid device!", cmd);
|
2016-11-28 13:33:54 +08:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-19 10:08:48 +02:00
|
|
|
if (strcmp(cmd, "enadev2") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_DEV_ENABLE_SEQ;
|
|
|
|
|
goto device;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(cmd, "disdev2") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_SEQ;
|
|
|
|
|
device:
|
|
|
|
|
err = parse_string_substitute3(uc_mgr, n, &curr->data.device);
|
2016-11-28 13:33:54 +08:00
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "%s requires a valid device!", cmd);
|
2016-11-28 13:33:54 +08:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-24 16:28:21 +02:00
|
|
|
if (strcmp(cmd, "disdevall") == 0) {
|
|
|
|
|
curr->type = SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_ALL;
|
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "usleep requires integer!");
|
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
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "msleep requires integer!");
|
2011-08-22 13:35:32 +08:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "exec requires a string!");
|
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
|
|
|
}
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "sysw requires a string!");
|
2021-05-14 12:20:18 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-09 20:02:57 +01:00
|
|
|
if (strcmp(cmd, "comment") == 0)
|
|
|
|
|
goto skip;
|
|
|
|
|
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "sequence command '%s' is ignored", cmd);
|
2021-03-09 20:02:57 +01:00
|
|
|
|
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "compound is expected for value definition");
|
2010-10-26 14:26:46 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "unable to parse value for id '%s': %s!", id, snd_strerror(err));
|
2019-11-05 14:21:07 +01:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "unable to parse a string for id '%s'!", id);
|
2010-10-26 14:26:46 +02:00
|
|
|
return err;
|
2010-09-22 14:31:15 +02:00
|
|
|
}
|
2010-10-26 14:26:46 +02:00
|
|
|
break;
|
|
|
|
|
default:
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to get modifier comment");
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse conflicting device list");
|
2011-06-03 14:56:31 -06:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse modifier enable sequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "DisableSequence") == 0) {
|
|
|
|
|
err = parse_sequence(uc_mgr, &modifier->disable_list, n);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse modifier disable sequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse transition modifier");
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse Value");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2025-11-20 16:11:32 +01:00
|
|
|
* Parse device configuration fields
|
2010-09-07 15:35:14 +02:00
|
|
|
*/
|
2025-11-20 16:11:32 +01:00
|
|
|
static int parse_device_fields(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg,
|
|
|
|
|
struct use_case_device *device)
|
2010-09-07 15:35:14 +02:00
|
|
|
{
|
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
|
|
|
|
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, "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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to get device comment");
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse supported device list");
|
2025-11-07 15:46:16 +01:00
|
|
|
|
2011-06-03 14:56:31 -06:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "ConflictingDevice") == 0) {
|
|
|
|
|
err = parse_device_list(uc_mgr, &device->dev_list,
|
|
|
|
|
DEVLIST_CONFLICTING, n);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse conflicting device list");
|
2025-11-07 15:46:16 +01:00
|
|
|
|
2011-06-03 14:56:31 -06:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
if (strcmp(id, "EnableSequence") == 0) {
|
|
|
|
|
err = parse_sequence(uc_mgr, &device->enable_list, n);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse device enable sequence");
|
2025-11-07 15:46:16 +01:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "DisableSequence") == 0) {
|
|
|
|
|
err = parse_sequence(uc_mgr, &device->disable_list, n);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse device disable sequence");
|
2025-11-07 15:46:16 +01:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
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, &device->transition_list, n);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse transition device");
|
2025-11-07 15:46:16 +01:00
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse Value");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 16:11:32 +01:00
|
|
|
/*
|
|
|
|
|
* Helper function to copy, evaluate and optionally merge configuration trees.
|
|
|
|
|
*/
|
|
|
|
|
static int uc_mgr_config_copy_eval_merge(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t **dst,
|
|
|
|
|
snd_config_t *src,
|
|
|
|
|
snd_config_t *merge_from)
|
|
|
|
|
{
|
|
|
|
|
snd_config_t *tmp = NULL;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_copy(&tmp, src);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, tmp);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
snd_config_delete(tmp);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (merge_from) {
|
|
|
|
|
err = uc_mgr_config_tree_merge(uc_mgr, tmp, merge_from, NULL, NULL);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
snd_config_delete(tmp);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*dst = tmp;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Parse Device Use Cases
|
|
|
|
|
*
|
|
|
|
|
* # Each device is described in new section. N devices are allowed
|
|
|
|
|
* SectionDevice."Headphones" {
|
|
|
|
|
* Comment "Headphones connected to 3.5mm jack"
|
|
|
|
|
*
|
|
|
|
|
* SupportedDevice [
|
|
|
|
|
* "x"
|
|
|
|
|
* "y"
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* ConflictingDevice [
|
|
|
|
|
* "x"
|
|
|
|
|
* "y"
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* EnableSequence [
|
|
|
|
|
* ....
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* DisableSequence [
|
|
|
|
|
* ...
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* TransitionSequence."ToDevice" [
|
|
|
|
|
* ...
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* Value {
|
|
|
|
|
* PlaybackVolume "name='Master Playback Volume',index=2"
|
|
|
|
|
* PlaybackSwitch "name='Master Playback Switch',index=2"
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static int parse_device_by_name(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg,
|
|
|
|
|
struct use_case_verb *verb,
|
|
|
|
|
const char *name,
|
|
|
|
|
struct use_case_device **ret_device)
|
|
|
|
|
{
|
|
|
|
|
struct use_case_device *device;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
device = calloc(1, sizeof(*device));
|
|
|
|
|
if (device == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&device->enable_list);
|
|
|
|
|
INIT_LIST_HEAD(&device->disable_list);
|
|
|
|
|
INIT_LIST_HEAD(&device->transition_list);
|
|
|
|
|
INIT_LIST_HEAD(&device->dev_list.list);
|
|
|
|
|
INIT_LIST_HEAD(&device->value_list);
|
|
|
|
|
INIT_LIST_HEAD(&device->variants);
|
|
|
|
|
INIT_LIST_HEAD(&device->variant_list);
|
|
|
|
|
list_add_tail(&device->list, &verb->device_list);
|
|
|
|
|
device->name = strdup(name);
|
|
|
|
|
if (device->name == NULL) {
|
|
|
|
|
free(device);
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
device->orig_name = strdup(name);
|
|
|
|
|
if (device->orig_name == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
err = parse_device_fields(uc_mgr, cfg, device);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
if (ret_device)
|
|
|
|
|
*ret_device = device;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parse_device(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
snd_config_t *cfg,
|
|
|
|
|
void *data1, void *data2)
|
|
|
|
|
{
|
|
|
|
|
struct use_case_verb *verb = data1;
|
|
|
|
|
char *name, *colon;
|
|
|
|
|
const char *variant_label = NULL;
|
|
|
|
|
struct use_case_device *device = NULL;
|
|
|
|
|
snd_config_t *primary_cfg_copy = NULL;
|
|
|
|
|
snd_config_t *device_variant = NULL;
|
|
|
|
|
snd_config_t *merged_cfg = NULL;
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *n;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
if (uc_mgr->conf_format >= 8 && (colon = strchr(name, ':'))) {
|
|
|
|
|
variant_label = colon + 1;
|
|
|
|
|
|
|
|
|
|
err = snd_config_search(cfg, "DeviceVariant", &device_variant);
|
|
|
|
|
if (err == 0) {
|
|
|
|
|
snd_config_t *variant_cfg = NULL;
|
|
|
|
|
|
|
|
|
|
/* Save a copy of the primary config for creating variant devices */
|
|
|
|
|
err = snd_config_copy(&primary_cfg_copy, cfg);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
free(name);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = snd_config_search(device_variant, variant_label, &variant_cfg);
|
|
|
|
|
if (err == 0) {
|
|
|
|
|
err = uc_mgr_config_copy_eval_merge(uc_mgr, &merged_cfg, cfg, variant_cfg);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
free(name);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
cfg = merged_cfg;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* in-place evaluation */
|
|
|
|
|
if (cfg != merged_cfg) {
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
free(name);
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = parse_device_by_name(uc_mgr, cfg, verb, name, &device);
|
|
|
|
|
free(name);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
|
|
|
|
|
if (merged_cfg) {
|
|
|
|
|
snd_config_delete(merged_cfg);
|
|
|
|
|
merged_cfg = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (device_variant == NULL)
|
|
|
|
|
goto __end;
|
|
|
|
|
|
|
|
|
|
if (device->dev_list.type == DEVLIST_SUPPORTED) {
|
|
|
|
|
snd_error(UCM, "DeviceVariant cannot be used with SupportedDevice");
|
|
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(device_variant) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
snd_error(UCM, "compound type expected for DeviceVariant");
|
|
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
colon = strchr(device->name, ':');
|
|
|
|
|
if (!colon) {
|
|
|
|
|
snd_error(UCM, "DeviceVariant requires ':' in device name");
|
|
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snd_config_for_each(i, next, device_variant) {
|
|
|
|
|
const char *variant_name;
|
|
|
|
|
char variant_device_name[128];
|
|
|
|
|
struct use_case_device *variant = NULL;
|
|
|
|
|
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_id(n, &variant_name) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* Create variant device name: base:variant_name */
|
|
|
|
|
snprintf(variant_device_name, sizeof(variant_device_name),
|
|
|
|
|
"%.*s:%s", (int)(colon - device->name),
|
|
|
|
|
device->name, variant_name);
|
|
|
|
|
|
|
|
|
|
err = uc_mgr_config_copy_eval_merge(uc_mgr, &merged_cfg, primary_cfg_copy, n);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
|
|
|
|
|
err = parse_device_by_name(uc_mgr, merged_cfg, verb,
|
|
|
|
|
variant_device_name, &variant);
|
|
|
|
|
snd_config_delete(merged_cfg);
|
|
|
|
|
merged_cfg = NULL;
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
|
|
|
|
|
/* Link variant to primary device */
|
|
|
|
|
list_add(&variant->variant_list, &device->variants);
|
|
|
|
|
|
|
|
|
|
err = uc_mgr_put_to_dev_list(&device->dev_list, variant->name);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
if (device->dev_list.type == DEVLIST_NONE)
|
|
|
|
|
device->dev_list.type = DEVLIST_CONFLICTING;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
__end:
|
|
|
|
|
err = 0;
|
|
|
|
|
__error:
|
|
|
|
|
if (merged_cfg)
|
|
|
|
|
snd_config_delete(merged_cfg);
|
|
|
|
|
if (primary_cfg_copy)
|
|
|
|
|
snd_config_delete(primary_cfg_copy);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for %s", id);
|
2020-02-07 10:09:07 +01:00
|
|
|
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);
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to get target device name for '%s'", name1);
|
2020-02-07 10:09:07 +01:00
|
|
|
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)
|
|
|
|
|
{
|
2021-11-07 18:40:18 +01:00
|
|
|
return parse_compound_check_legacy(uc_mgr, cfg, parse_modifier, data1);
|
2020-02-07 10:09:07 +01:00
|
|
|
}
|
|
|
|
|
|
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);
|
2025-11-20 16:11:32 +01:00
|
|
|
|
2020-02-07 16:18:11 +01:00
|
|
|
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 {
|
2025-11-18 14:55:25 +01:00
|
|
|
snd_error(UCM, "incompatible device list type ('%s', '%s')", device->orig_name, src);
|
2020-02-07 16:18:11 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return uc_mgr_put_to_dev_list(&device->dev_list, src);
|
|
|
|
|
}
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:13:20 +01:00
|
|
|
/*
|
|
|
|
|
* Check if a device name is already in use
|
|
|
|
|
*/
|
|
|
|
|
static int is_device_name_used(struct use_case_verb *verb, const char *name, struct use_case_device *current)
|
|
|
|
|
{
|
|
|
|
|
struct list_head *pos;
|
|
|
|
|
struct use_case_device *device;
|
|
|
|
|
|
|
|
|
|
list_for_each(pos, &verb->device_list) {
|
|
|
|
|
device = list_entry(pos, struct use_case_device, list);
|
|
|
|
|
if (device != current && strcmp(device->name, name) == 0)
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:35:06 +01:00
|
|
|
/*
|
|
|
|
|
* Update all references to a device name in modifiers and other devices.
|
|
|
|
|
* This helper function is used when renaming devices to ensure all
|
|
|
|
|
* dev_list references are updated accordingly.
|
|
|
|
|
*/
|
|
|
|
|
static int verb_update_device_references(struct use_case_verb *verb,
|
|
|
|
|
const char *old_name,
|
|
|
|
|
const char *new_name)
|
|
|
|
|
{
|
|
|
|
|
struct list_head *pos, *pos2;
|
|
|
|
|
struct use_case_device *device;
|
|
|
|
|
struct use_case_modifier *modifier;
|
|
|
|
|
struct dev_list_node *dlist;
|
|
|
|
|
char *name_copy;
|
|
|
|
|
|
|
|
|
|
list_for_each(pos, &verb->modifier_list) {
|
|
|
|
|
modifier = list_entry(pos, struct use_case_modifier, list);
|
|
|
|
|
list_for_each(pos2, &modifier->dev_list.list) {
|
|
|
|
|
dlist = list_entry(pos2, struct dev_list_node, list);
|
|
|
|
|
if (strcmp(dlist->name, old_name) == 0) {
|
|
|
|
|
name_copy = strdup(new_name);
|
|
|
|
|
if (name_copy == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
free(dlist->name);
|
|
|
|
|
dlist->name = name_copy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
if (strcmp(dlist->name, old_name) == 0) {
|
|
|
|
|
name_copy = strdup(new_name);
|
|
|
|
|
if (name_copy == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
free(dlist->name);
|
|
|
|
|
dlist->name = name_copy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 10:41:06 +01:00
|
|
|
/*
|
|
|
|
|
* Normalize device names according to use-case.h specification.
|
|
|
|
|
* Device names like "HDMI 1" or "Line 1" should be normalized to "HDMI1" and "Line1".
|
2025-11-18 13:13:20 +01:00
|
|
|
* When device name contains ':', add index and remove everything after ':' (including).
|
|
|
|
|
* If final name is already used, retry with higher index.
|
2025-11-18 10:41:06 +01:00
|
|
|
* Also updates dev_list members in modifiers and devices to reference the normalized names.
|
|
|
|
|
*/
|
2025-11-18 13:13:20 +01:00
|
|
|
static int verb_normalize_device_names(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb)
|
2025-11-18 10:41:06 +01:00
|
|
|
{
|
2025-11-18 13:35:06 +01:00
|
|
|
struct list_head *pos;
|
|
|
|
|
struct use_case_device *device;
|
2025-11-18 13:13:20 +01:00
|
|
|
char *orig_name, *norm_name, *colon;
|
2025-11-18 10:41:06 +01:00
|
|
|
char temp[80];
|
|
|
|
|
int err, index;
|
|
|
|
|
|
|
|
|
|
list_for_each(pos, &verb->device_list) {
|
|
|
|
|
device = list_entry(pos, struct use_case_device, list);
|
|
|
|
|
|
|
|
|
|
orig_name = strdup(device->name);
|
|
|
|
|
if (orig_name == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
norm_name = strdup(device->name);
|
|
|
|
|
if (norm_name == NULL) {
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:13:20 +01:00
|
|
|
if (uc_mgr->conf_format < 8)
|
|
|
|
|
goto __no_colon;
|
|
|
|
|
|
|
|
|
|
colon = strchr(norm_name, ':');
|
|
|
|
|
if (colon) {
|
2025-11-18 14:00:48 +01:00
|
|
|
if (colon[1] == '\0' || strchr(colon + 1, ' ')) {
|
|
|
|
|
snd_error(UCM, "device descriptor cannot be empty or contain spaces '%s'", orig_name);
|
|
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
2025-11-18 13:13:20 +01:00
|
|
|
*colon = '\0';
|
|
|
|
|
index = 1;
|
|
|
|
|
do {
|
|
|
|
|
snprintf(temp, sizeof(temp), "%s%d", norm_name, index);
|
|
|
|
|
if (!is_device_name_used(verb, temp, device))
|
|
|
|
|
break;
|
|
|
|
|
index++;
|
|
|
|
|
} while (index < 100); /* Safety limit */
|
|
|
|
|
if (index >= 100) {
|
2025-11-18 14:55:25 +01:00
|
|
|
snd_error(UCM, "too many device name conflicts for '%s'", orig_name);
|
2025-11-18 13:13:20 +01:00
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
2025-11-18 10:41:06 +01:00
|
|
|
|
2025-11-18 13:13:20 +01:00
|
|
|
} else {
|
|
|
|
|
__no_colon:
|
|
|
|
|
err = parse_device_index(&norm_name, &index);
|
|
|
|
|
if (err < 0) {
|
2025-11-18 14:55:25 +01:00
|
|
|
snd_error(UCM, "cannot parse device name '%s'", orig_name);
|
2025-11-18 13:13:20 +01:00
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index <= 0) {
|
|
|
|
|
free(orig_name);
|
|
|
|
|
free(norm_name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
snprintf(temp, sizeof(temp), "%s%d", norm_name, index);
|
2025-11-18 10:41:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(device->name);
|
|
|
|
|
device->name = strdup(temp);
|
|
|
|
|
if (device->name == NULL) {
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:35:06 +01:00
|
|
|
/* Update all references to the old device name */
|
|
|
|
|
err = verb_update_device_references(verb, orig_name, device->name);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
2025-11-18 10:41:06 +01:00
|
|
|
|
|
|
|
|
free(orig_name);
|
|
|
|
|
free(norm_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
__error:
|
|
|
|
|
free(orig_name);
|
|
|
|
|
free(norm_name);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:35:06 +01:00
|
|
|
/*
|
|
|
|
|
* Strip index from single device names.
|
|
|
|
|
* According to use-case.h specification, if there is only one device
|
|
|
|
|
* with a given base name (e.g., only "HDMI1" and no "HDMI2"), the index
|
|
|
|
|
* should be stripped to produce the final name (e.g., "HDMI").
|
|
|
|
|
*/
|
|
|
|
|
static int verb_strip_single_device_index(struct use_case_verb *verb)
|
|
|
|
|
{
|
|
|
|
|
struct list_head *pos, *pos2;
|
|
|
|
|
struct use_case_device *device, *device2;
|
|
|
|
|
char *base_name, *test_base;
|
|
|
|
|
char *orig_name;
|
|
|
|
|
int count, index, test_index, err;
|
|
|
|
|
|
|
|
|
|
list_for_each(pos, &verb->device_list) {
|
|
|
|
|
device = list_entry(pos, struct use_case_device, list);
|
|
|
|
|
|
|
|
|
|
base_name = strdup(device->name);
|
|
|
|
|
if (base_name == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
err = parse_device_index(&base_name, &index);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
free(base_name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index <= 0) {
|
|
|
|
|
free(base_name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Count how many devices have the same base name */
|
|
|
|
|
count = 0;
|
|
|
|
|
list_for_each(pos2, &verb->device_list) {
|
|
|
|
|
device2 = list_entry(pos2, struct use_case_device, list);
|
|
|
|
|
test_base = strdup(device2->name);
|
|
|
|
|
if (test_base == NULL) {
|
|
|
|
|
free(base_name);
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = parse_device_index(&test_base, &test_index);
|
|
|
|
|
if (err >= 0 && strcmp(test_base, base_name) == 0)
|
|
|
|
|
count++;
|
|
|
|
|
|
|
|
|
|
free(test_base);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count == 1) {
|
|
|
|
|
orig_name = device->name;
|
|
|
|
|
device->name = base_name;
|
|
|
|
|
|
|
|
|
|
err = verb_update_device_references(verb, orig_name, device->name);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
device->name = orig_name;
|
|
|
|
|
free(base_name);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(orig_name);
|
|
|
|
|
} else {
|
|
|
|
|
free(base_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 14:23:18 +01:00
|
|
|
/*
|
|
|
|
|
* Determine priority for a device.
|
|
|
|
|
* Priority order:
|
|
|
|
|
* 1. If 'Priority' value exists, use it as the sort key
|
|
|
|
|
* 2. If 'PlaybackPriority' value exists, use it as the sort key
|
|
|
|
|
* 3. If 'CapturePriority' value exists, use it as the sort key
|
|
|
|
|
* 4. Fallback: LONG_MIN (no priority)
|
|
|
|
|
*/
|
|
|
|
|
static long verb_device_get_priority(struct use_case_device *device)
|
|
|
|
|
{
|
|
|
|
|
struct list_head *pos;
|
|
|
|
|
struct ucm_value *val;
|
|
|
|
|
const char *priority_str = NULL;
|
|
|
|
|
long priority = LONG_MIN;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
list_for_each(pos, &device->value_list) {
|
|
|
|
|
val = list_entry(pos, struct ucm_value, list);
|
|
|
|
|
if (strcmp(val->name, "Priority") == 0) {
|
|
|
|
|
priority_str = val->data;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!priority_str) {
|
|
|
|
|
list_for_each(pos, &device->value_list) {
|
|
|
|
|
val = list_entry(pos, struct ucm_value, list);
|
|
|
|
|
if (strcmp(val->name, "PlaybackPriority") == 0) {
|
|
|
|
|
priority_str = val->data;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!priority_str) {
|
|
|
|
|
list_for_each(pos, &device->value_list) {
|
|
|
|
|
val = list_entry(pos, struct ucm_value, list);
|
|
|
|
|
if (strcmp(val->name, "CapturePriority") == 0) {
|
|
|
|
|
priority_str = val->data;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priority_str) {
|
|
|
|
|
err = safe_strtol(priority_str, &priority);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
priority = LONG_MIN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return priority;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Sort devices based on priority values.
|
|
|
|
|
* Priority order:
|
|
|
|
|
* 1. If 'Priority' value exists, use it as the sort key
|
|
|
|
|
* 2. If 'PlaybackPriority' value exists, use it as the sort key
|
|
|
|
|
* 3. If 'CapturePriority' value exists, use it as the sort key
|
|
|
|
|
* 4. Fallback: use device->name (original) as the sort key
|
|
|
|
|
* Higher priority values are placed first in the list.
|
|
|
|
|
*/
|
|
|
|
|
static int verb_sort_devices(struct use_case_verb *verb)
|
|
|
|
|
{
|
|
|
|
|
struct list_head sorted_list;
|
|
|
|
|
struct list_head *pos, *npos;
|
|
|
|
|
struct use_case_device *device, *insert_dev;
|
|
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&sorted_list);
|
|
|
|
|
|
|
|
|
|
/* First pass: determine and cache priority for all devices */
|
|
|
|
|
list_for_each(pos, &verb->device_list) {
|
|
|
|
|
device = list_entry(pos, struct use_case_device, list);
|
|
|
|
|
device->sort_priority = verb_device_get_priority(device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Move devices from verb->device_list to sorted_list in sorted order */
|
|
|
|
|
list_for_each_safe(pos, npos, &verb->device_list) {
|
|
|
|
|
device = list_entry(pos, struct use_case_device, list);
|
|
|
|
|
|
|
|
|
|
/* Remove device from original list */
|
|
|
|
|
list_del(&device->list);
|
|
|
|
|
|
|
|
|
|
/* Find the insertion point in sorted_list */
|
|
|
|
|
/* Devices are sorted in descending order of priority (higher priority first) */
|
|
|
|
|
/* If priorities are equal or not defined, use device name as key */
|
|
|
|
|
if (list_empty(&sorted_list)) {
|
|
|
|
|
list_add_tail(&device->list, &sorted_list);
|
|
|
|
|
} else {
|
|
|
|
|
struct list_head *pos2, *insert_pos = &sorted_list;
|
|
|
|
|
list_for_each(pos2, &sorted_list) {
|
|
|
|
|
insert_dev = list_entry(pos2, struct use_case_device, list);
|
|
|
|
|
|
|
|
|
|
if (device->sort_priority > insert_dev->sort_priority) {
|
|
|
|
|
insert_pos = pos2;
|
|
|
|
|
break;
|
|
|
|
|
} else if (device->sort_priority == insert_dev->sort_priority) {
|
|
|
|
|
if (strcmp(device->name, insert_dev->name) < 0) {
|
|
|
|
|
insert_pos = pos2;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list_add_tail(&device->list, insert_pos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Move sorted list back to verb->device_list */
|
|
|
|
|
list_splice_init(&sorted_list, &verb->device_list);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 13:13:20 +01:00
|
|
|
static int verb_device_management(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb)
|
2020-02-07 10:09:07 +01:00
|
|
|
{
|
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "cannot rename device '%s' to '%s'", dev->name1, dev->name2);
|
2020-02-07 10:09:07 +01:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "cannot remove device '%s'", dev->name2);
|
2020-02-07 10:09:07 +01:00
|
|
|
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
|
|
|
|
2025-11-18 14:23:18 +01:00
|
|
|
/* strip index from single device names */
|
|
|
|
|
if (uc_mgr->conf_format >= 8) {
|
|
|
|
|
/* sort devices by priority */
|
|
|
|
|
err = verb_sort_devices(verb);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 10:41:06 +01:00
|
|
|
/* normalize device names to remove spaces per use-case.h specification */
|
2025-11-18 13:13:20 +01:00
|
|
|
err = verb_normalize_device_names(uc_mgr, verb);
|
2025-11-18 10:41:06 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
2025-11-18 13:35:06 +01:00
|
|
|
/* strip index from single device names */
|
|
|
|
|
if (uc_mgr->conf_format >= 8) {
|
|
|
|
|
err = verb_strip_single_device_index(verb);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
2025-11-18 14:23:18 +01:00
|
|
|
|
2025-11-18 13:35:06 +01:00
|
|
|
}
|
|
|
|
|
|
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;
|
2025-11-07 14:27:38 +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 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) {
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse verb enable sequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "DisableSequence") == 0) {
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse verb disable sequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-26 14:26:46 +02:00
|
|
|
if (strcmp(id, "TransitionSequence") == 0) {
|
2025-11-07 15:58:04 +01:00
|
|
|
snd_debug(UCM, "Parse TransitionSequence");
|
2010-09-07 15:35:14 +02:00
|
|
|
err = parse_transition(uc_mgr, &verb->transition_list, n);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "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) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2025-11-17 18:36:32 +01:00
|
|
|
* Parse a Use case verb configuration.
|
2010-09-07 15:35:14 +02:00
|
|
|
*
|
2025-11-17 18:36:32 +01:00
|
|
|
* This configuration contains the following :-
|
2010-09-07 15:35:14 +02:00
|
|
|
* 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.
|
|
|
|
|
*/
|
2025-11-17 18:36:32 +01:00
|
|
|
static int parse_verb_config(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
const char *use_case_name,
|
|
|
|
|
const char *comment,
|
|
|
|
|
snd_config_t *cfg,
|
|
|
|
|
const char *what)
|
2010-09-07 15:35:14 +02:00
|
|
|
{
|
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;
|
|
|
|
|
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
|
|
|
|
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)
|
2025-11-17 18:36:32 +01:00
|
|
|
return 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) {
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_error(UCM, "%s failed to parse verb", what);
|
|
|
|
|
return 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) {
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_error(UCM, "%s failed to parse device", what);
|
|
|
|
|
return 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) {
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_error(UCM, "%s failed to parse modifier", what);
|
|
|
|
|
return 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) {
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_error(UCM, "%s failed to parse device rename", what);
|
|
|
|
|
return err;
|
2020-02-07 10:09:07 +01:00
|
|
|
}
|
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) {
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_error(UCM, "%s failed to parse device remove", what);
|
|
|
|
|
return err;
|
2020-02-07 10:09:07 +01:00
|
|
|
}
|
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) {
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_error(UCM, "%s failed to parse LibConfig", what);
|
|
|
|
|
return err;
|
2021-04-12 18:09:21 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
2020-02-07 10:09:07 +01:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* use case verb must have at least 1 device */
|
|
|
|
|
if (list_empty(&verb->device_list)) {
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_error(UCM, "no use case device defined");
|
2010-09-07 15:35:14 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
2020-02-07 10:09:07 +01:00
|
|
|
|
|
|
|
|
/* do device rename and delete */
|
2025-11-18 13:13:20 +01:00
|
|
|
err = verb_device_management(uc_mgr, verb);
|
2020-02-07 10:09:07 +01:00
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "device management error in verb '%s'", verb->name);
|
2020-02-07 10:09:07 +01:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
/*
|
|
|
|
|
* Parse variant information
|
|
|
|
|
*/
|
|
|
|
|
static int parse_variant(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
|
|
|
|
|
char **_vfile, char **_vcomment)
|
|
|
|
|
{
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *n;
|
|
|
|
|
char *file = NULL, *comment = NULL;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
/* parse master config sections */
|
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
|
|
|
const char *id;
|
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* get use case verb file name */
|
|
|
|
|
if (strcmp(id, "File") == 0) {
|
|
|
|
|
if (_vfile) {
|
|
|
|
|
err = parse_string_substitute3(uc_mgr, n, &file);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "failed to get File");
|
2022-05-18 13:10:35 +02:00
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* get optional use case comment */
|
|
|
|
|
if (strncmp(id, "Comment", 7) == 0) {
|
|
|
|
|
if (_vcomment) {
|
|
|
|
|
err = parse_string_substitute3(uc_mgr, n, &comment);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to get Comment");
|
2022-05-18 13:10:35 +02:00
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unknown field '%s' in Variant section", id);
|
2022-05-18 13:10:35 +02:00
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_vfile)
|
|
|
|
|
*_vfile = file;
|
|
|
|
|
if (_vcomment)
|
|
|
|
|
*_vcomment = comment;
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
__error:
|
|
|
|
|
free(file);
|
|
|
|
|
free(comment);
|
|
|
|
|
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;
|
2025-11-17 18:36:32 +01:00
|
|
|
snd_config_t *n, *variant = NULL, *config = NULL;
|
2020-05-27 14:47:08 +02:00
|
|
|
char *use_case_name, *file = NULL, *comment = NULL;
|
2022-05-18 13:10:35 +02:00
|
|
|
bool variant_ok = false;
|
2010-09-07 15:35:14 +02:00
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for use case section");
|
2010-09-07 15:35:14 +02:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unable to get name for use case section");
|
2020-05-27 14:47:08 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-16 15:47:19 +02:00
|
|
|
/* in-place evaluation */
|
2022-05-18 13:10:35 +02:00
|
|
|
uc_mgr->parse_master_section = 1;
|
2020-05-16 15:47:19 +02:00
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
|
2022-05-18 13:10:35 +02:00
|
|
|
uc_mgr->parse_master_section = 0;
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "failed to get File");
|
2020-05-27 14:47:08 +02:00
|
|
|
goto __error;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 18:36:32 +01:00
|
|
|
/* get use case verb configuration block (syntax version 8+) */
|
|
|
|
|
if (strcmp(id, "Config") == 0) {
|
|
|
|
|
if (uc_mgr->conf_format < 8) {
|
|
|
|
|
snd_error(UCM, "Config is supported in v8+ syntax");
|
|
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
|
|
|
|
|
snd_error(UCM, "compound type expected for Config");
|
|
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
config = n;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-07 15:35:14 +02:00
|
|
|
/* 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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to get Comment");
|
2020-05-27 14:47:08 +02:00
|
|
|
goto __error;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
if (uc_mgr->conf_format >= 6 && strcmp(id, "Variant") == 0) {
|
|
|
|
|
snd_config_iterator_t i2, next2;
|
|
|
|
|
variant = n;
|
|
|
|
|
snd_config_for_each(i2, next2, n) {
|
|
|
|
|
const char *id2;
|
|
|
|
|
snd_config_t *n2;
|
|
|
|
|
n2 = snd_config_iterator_entry(i2);
|
|
|
|
|
if (snd_config_get_id(n2, &id2) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
err = uc_mgr_evaluate_inplace(uc_mgr, n2);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
if (strcmp(use_case_name, id2) == 0)
|
|
|
|
|
variant_ok = true;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unknown field '%s' in SectionUseCase", id);
|
2022-05-18 13:10:35 +02:00
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
if (variant && !variant_ok) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "undefined variant '%s'", use_case_name);
|
2020-05-27 14:47:08 +02:00
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
2010-09-07 15:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
2025-11-17 18:36:32 +01:00
|
|
|
/* check mutual exclusivity of File and Config */
|
|
|
|
|
if (file && config) {
|
|
|
|
|
snd_error(UCM, "both File and Config specified in SectionUseCase");
|
|
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
if (!variant) {
|
2025-11-07 15:58:04 +01:00
|
|
|
snd_debug(UCM, "use_case_name %s file '%s'", use_case_name, file);
|
2022-05-18 13:10:35 +02:00
|
|
|
|
2025-11-17 18:36:32 +01:00
|
|
|
/* do we have both use case name and (file or config) ? */
|
|
|
|
|
if (!file && !config) {
|
|
|
|
|
snd_error(UCM, "use case missing file or config");
|
2022-05-18 13:10:35 +02:00
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 18:36:32 +01:00
|
|
|
/* parse verb from file or config */
|
|
|
|
|
if (file) {
|
|
|
|
|
snd_config_t *cfg;
|
|
|
|
|
/* load config from file */
|
|
|
|
|
err = uc_mgr_config_load_file(uc_mgr, file, &cfg);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
/* parse the config */
|
|
|
|
|
err = parse_verb_config(uc_mgr, use_case_name, comment, cfg, file);
|
|
|
|
|
snd_config_delete(cfg);
|
|
|
|
|
} else {
|
|
|
|
|
/* inline config - parse directly */
|
|
|
|
|
err = parse_verb_config(uc_mgr, use_case_name, comment, config,
|
|
|
|
|
comment ? comment : use_case_name);
|
|
|
|
|
}
|
2022-05-18 13:10:35 +02:00
|
|
|
} else {
|
|
|
|
|
/* parse variants */
|
2025-11-07 17:59:12 +01:00
|
|
|
struct list_head orig_variable_list;
|
|
|
|
|
snd_config_t *orig_macros = NULL;
|
|
|
|
|
int first_iteration = 1;
|
|
|
|
|
|
|
|
|
|
/* save original variable list */
|
|
|
|
|
err = uc_mgr_duplicate_variables(&orig_variable_list, &uc_mgr->variable_list);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __error;
|
|
|
|
|
|
|
|
|
|
/* save original macros */
|
|
|
|
|
if (uc_mgr->macros) {
|
|
|
|
|
err = snd_config_copy(&orig_macros, uc_mgr->macros);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __variant_error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
snd_config_for_each(i, next, variant) {
|
|
|
|
|
char *vfile, *vcomment;
|
|
|
|
|
const char *id;
|
2025-11-07 17:59:12 +01:00
|
|
|
|
|
|
|
|
/* restore variables and macros for second and later iterations */
|
|
|
|
|
if (!first_iteration) {
|
|
|
|
|
uc_mgr_free_value(&uc_mgr->variable_list);
|
|
|
|
|
|
|
|
|
|
err = uc_mgr_duplicate_variables(&uc_mgr->variable_list, &orig_variable_list);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __variant_error;
|
|
|
|
|
|
|
|
|
|
if (uc_mgr->macros) {
|
|
|
|
|
snd_config_delete(uc_mgr->macros);
|
|
|
|
|
uc_mgr->macros = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (orig_macros) {
|
|
|
|
|
err = snd_config_copy(&uc_mgr->macros, orig_macros);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __variant_error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
first_iteration = 0;
|
|
|
|
|
|
2022-05-18 13:10:35 +02:00
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
if (!parse_is_name_safe(id)) {
|
|
|
|
|
err = -EINVAL;
|
2025-11-07 17:59:12 +01:00
|
|
|
goto __variant_error;
|
2022-05-18 13:10:35 +02:00
|
|
|
}
|
|
|
|
|
err = parse_variant(uc_mgr, n, &vfile, &vcomment);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
break;
|
|
|
|
|
uc_mgr->parse_variant = id;
|
2025-11-17 18:36:32 +01:00
|
|
|
if (vfile || file) {
|
|
|
|
|
snd_config_t *cfg;
|
|
|
|
|
const char *fname = vfile ? vfile : file;
|
|
|
|
|
/* load config from file */
|
|
|
|
|
err = uc_mgr_config_load_file(uc_mgr, fname, &cfg);
|
|
|
|
|
if (err >= 0) {
|
|
|
|
|
err = parse_verb_config(uc_mgr, id,
|
|
|
|
|
vcomment ? vcomment : comment,
|
|
|
|
|
cfg, fname);
|
|
|
|
|
snd_config_delete(cfg);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* inline config from variant */
|
|
|
|
|
err = parse_verb_config(uc_mgr, id,
|
|
|
|
|
vcomment ? vcomment : comment,
|
|
|
|
|
config,
|
|
|
|
|
vcomment ? vcomment : (comment ? comment : id));
|
|
|
|
|
}
|
2022-05-18 13:10:35 +02:00
|
|
|
uc_mgr->parse_variant = NULL;
|
|
|
|
|
free(vfile);
|
|
|
|
|
free(vcomment);
|
2025-11-07 17:59:12 +01:00
|
|
|
if (err < 0)
|
|
|
|
|
break;
|
2022-05-18 13:10:35 +02:00
|
|
|
}
|
2025-11-07 17:59:12 +01:00
|
|
|
|
|
|
|
|
__variant_error:
|
|
|
|
|
uc_mgr_free_value(&orig_variable_list);
|
|
|
|
|
if (orig_macros)
|
|
|
|
|
snd_config_delete(orig_macros);
|
2022-05-18 13:10:35 +02:00
|
|
|
}
|
2020-05-27 14:47:08 +02:00
|
|
|
|
|
|
|
|
__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)) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "FixedBoot list is not empty");
|
2021-03-05 18:50:02 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
err = parse_sequence(uc_mgr, &uc_mgr->fixedboot_list, cfg);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Unable to parse FixedBootSequence");
|
2021-03-05 18:50:02 +01:00
|
|
|
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)) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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)) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Default list is not empty");
|
2010-10-26 14:26:46 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
err = parse_sequence(uc_mgr, &uc_mgr->default_list, cfg);
|
2010-09-07 15:35:14 +02:00
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Unable to parse SectionDefaults");
|
2010-09-07 15:35:14 +02:00
|
|
|
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;
|
|
|
|
|
int err;
|
2010-09-07 15:35:14 +02:00
|
|
|
|
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for master file");
|
2010-09-07 15:35:14 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-09 11:53:32 +01:00
|
|
|
if (uc_mgr->conf_format >= 2) {
|
2022-10-20 19:37:32 +02:00
|
|
|
err = parse_syntax_field(uc_mgr, cfg, uc_mgr->conf_file_name);
|
|
|
|
|
if (err < 0)
|
2019-11-09 11:53:32 +01: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)
|
|
|
|
|
return err;
|
|
|
|
|
|
2025-12-01 13:32:10 +01:00
|
|
|
/* parse ValueDefaults first */
|
|
|
|
|
err = snd_config_search(cfg, "ValueDefaults", &n);
|
|
|
|
|
if (err == 0) {
|
|
|
|
|
err = parse_value(uc_mgr, &uc_mgr->value_list, n);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
snd_error(UCM, "failed to parse ValueDefaults");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = uc_mgr_check_value(&uc_mgr->value_list, "BootCardGroup");
|
|
|
|
|
if (err == 0) {
|
|
|
|
|
uc_mgr->card_group = true;
|
|
|
|
|
/* if we are in boot, skip the main parsing loop */
|
|
|
|
|
if (uc_mgr->in_boot)
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to get master comment");
|
2010-10-13 11:48:52 +02:00
|
|
|
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
|
|
|
|
2025-12-01 13:32:10 +01:00
|
|
|
/* ValueDefaults is now parsed at the top of this function */
|
2010-11-29 15:41:34 +01:00
|
|
|
if (strcmp(id, "ValueDefaults") == 0) {
|
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse LibraryConfig");
|
2021-04-12 18:09:21 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 10:23:25 +02:00
|
|
|
/* error */
|
|
|
|
|
if (strcmp(id, "Error") == 0)
|
|
|
|
|
return error_node(uc_mgr, n);
|
|
|
|
|
|
2022-10-20 19:37:32 +02:00
|
|
|
/* skip further Syntax value updates (Include) */
|
|
|
|
|
if (strcmp(id, "Syntax") == 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "no soundcards found...");
|
2017-01-18 11:53:35 +08:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "snd_card_next");
|
2017-01-18 11:53:35 +08:00
|
|
|
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];
|
2022-05-19 17:37:46 +02:00
|
|
|
struct stat64 st;
|
2020-05-26 18:54:31 +02:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for UseCasePath.something node");
|
2020-05-26 18:54:31 +02:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unable to parse UcmDirectory");
|
2020-05-26 18:54:31 +02:00
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
if (version < 1 || version > 2) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Version must be 1 or 2");
|
2020-05-26 18:54:31 +02:00
|
|
|
err = -EINVAL;
|
|
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "Directory") == 0) {
|
|
|
|
|
err = parse_string_substitute(uc_mgr, n2, &dir);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unable to parse Directory");
|
2020-05-26 18:54:31 +02:00
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcmp(id, "File") == 0) {
|
|
|
|
|
err = parse_string_substitute(uc_mgr, n2, &file);
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unable to parse File");
|
2020-05-26 18:54:31 +02:00
|
|
|
goto __error;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "unknown UseCasePath field %s", id);
|
2020-05-26 18:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dir == NULL) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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);
|
2022-05-19 17:37:46 +02:00
|
|
|
if (access(fn, R_OK) == 0 && lstat64(fn, &st) == 0) {
|
2022-07-08 14:50:54 +02:00
|
|
|
if (S_ISLNK(st.st_mode)) {
|
2021-12-13 14:40:56 +01:00
|
|
|
ssize_t r;
|
|
|
|
|
char *link, *dir2, *p;
|
|
|
|
|
|
|
|
|
|
link = malloc(PATH_MAX);
|
|
|
|
|
if (link == NULL)
|
|
|
|
|
goto __enomem;
|
|
|
|
|
r = readlink(fn, link, PATH_MAX - 1);
|
|
|
|
|
if (r <= 0) {
|
|
|
|
|
free(link);
|
|
|
|
|
goto __next;
|
|
|
|
|
}
|
|
|
|
|
link[r] = '\0';
|
|
|
|
|
p = strrchr(link, '/');
|
|
|
|
|
if (p) {
|
|
|
|
|
*p = '\0';
|
|
|
|
|
dir2 = malloc(PATH_MAX);
|
|
|
|
|
if (dir2 == NULL) {
|
|
|
|
|
free(link);
|
|
|
|
|
goto __enomem;
|
|
|
|
|
}
|
|
|
|
|
strncpy(dir2, dir, PATH_MAX - 1);
|
|
|
|
|
strncat(dir2, "/", PATH_MAX - 1);
|
|
|
|
|
strncat(dir2, link, PATH_MAX - 1);
|
|
|
|
|
fn[PATH_MAX - 1] = '\0';
|
|
|
|
|
free(dir);
|
|
|
|
|
dir = dir2;
|
|
|
|
|
}
|
|
|
|
|
free(link);
|
2020-05-26 18:54:31 +02:00
|
|
|
}
|
2021-12-13 14:40:56 +01:00
|
|
|
if (replace_string(&uc_mgr->conf_dir_name, dir) == NULL)
|
|
|
|
|
goto __enomem;
|
|
|
|
|
if (replace_string(&uc_mgr->conf_file_name, file) == NULL)
|
|
|
|
|
goto __enomem;
|
2020-05-26 18:54:31 +02:00
|
|
|
strncpy(filename, fn, PATH_MAX);
|
2021-12-13 14:40:56 +01:00
|
|
|
filename[PATH_MAX - 1] = '\0';
|
2020-05-26 18:54:31 +02:00
|
|
|
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);
|
2021-12-13 14:40:56 +01:00
|
|
|
if (dir != fn)
|
|
|
|
|
free(dir);
|
2020-05-26 18:54:31 +02:00
|
|
|
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;
|
|
|
|
|
|
2021-12-13 14:40:56 +01:00
|
|
|
__enomem:
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __error;
|
|
|
|
|
|
2020-05-26 18:54:31 +02:00
|
|
|
__ok:
|
|
|
|
|
err = 0;
|
|
|
|
|
__error:
|
|
|
|
|
free(file);
|
2021-12-13 14:40:56 +01:00
|
|
|
if (dir != fn)
|
|
|
|
|
free(dir);
|
2020-05-26 18:54:31 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parse_toplevel_config(snd_use_case_mgr_t *uc_mgr,
|
|
|
|
|
char *filename,
|
|
|
|
|
snd_config_t *cfg)
|
|
|
|
|
{
|
|
|
|
|
snd_config_iterator_t i, next;
|
|
|
|
|
snd_config_t *n;
|
|
|
|
|
const char *id;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "compound type expected for toplevel file");
|
2020-05-26 18:54:31 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-20 19:37:32 +02:00
|
|
|
err = parse_syntax_field(uc_mgr, cfg, filename);
|
|
|
|
|
if (err < 0)
|
2010-09-07 15:35:14 +02:00
|
|
|
return err;
|
|
|
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "failed to parse LibConfig");
|
2021-05-13 18:28:20 +02:00
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-20 19:37:32 +02:00
|
|
|
/* skip further Syntax value updates (Include) */
|
|
|
|
|
if (strcmp(id, "Syntax") == 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Unable to find the top-level configuration file '%s'.", filename);
|
2020-05-26 18:54:31 +02:00
|
|
|
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) {
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "could not parse configuration for card %s", uc_mgr->card_name);
|
2020-05-26 18:54:31 +02:00
|
|
|
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;
|
|
|
|
|
|
2022-05-27 14:28:13 +02:00
|
|
|
err = snd_config_top(&uc_mgr->local_config);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
err = snd_config_top(&uc_mgr->macros);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "card '%s' is not valid", name);
|
2020-05-26 18:54:31 +02:00
|
|
|
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);
|
2022-05-13 16:07:38 +02:00
|
|
|
if (uc_mgr->macros) {
|
|
|
|
|
snd_config_delete(uc_mgr->macros);
|
|
|
|
|
uc_mgr->macros = NULL;
|
|
|
|
|
}
|
2010-09-07 15:35:14 +02:00
|
|
|
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
|
|
|
|
2022-05-19 17:37:46 +02:00
|
|
|
static int filename_filter(const struct dirent64 *dirent)
|
2010-10-13 11:48:52 +02:00
|
|
|
{
|
|
|
|
|
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);
|
2022-06-16 18:03:03 +02:00
|
|
|
snd_use_case_mgr_t *uc_mgr;
|
2019-11-09 11:53:32 +01:00
|
|
|
const char **list, *d_name;
|
2022-06-16 18:03:03 +02:00
|
|
|
char *s;
|
2010-10-13 11:48:52 +02:00
|
|
|
snd_config_t *cfg, *c;
|
2022-06-16 18:03:03 +02:00
|
|
|
int i, j, cnt, err, cards;
|
2019-11-09 11:53:32 +01:00
|
|
|
long l;
|
2010-10-26 14:26:46 +02:00
|
|
|
ssize_t ss;
|
2022-05-19 17:37:46 +02:00
|
|
|
struct dirent64 **namelist;
|
2010-10-13 11:48:52 +02:00
|
|
|
|
2022-06-16 18:03:03 +02:00
|
|
|
i = -1;
|
|
|
|
|
cards = 0;
|
|
|
|
|
while (1) {
|
|
|
|
|
err = snd_card_next(&i);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
return err;
|
|
|
|
|
if (i < 0)
|
|
|
|
|
break;
|
|
|
|
|
cards++;
|
|
|
|
|
}
|
|
|
|
|
cards += 4; /* plug-and-play */
|
|
|
|
|
|
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
|
|
|
|
2025-07-29 14:58:43 +08:00
|
|
|
#if defined(_GNU_SOURCE) && \
|
|
|
|
|
!defined(__NetBSD__) && \
|
|
|
|
|
!defined(__FreeBSD__) && \
|
|
|
|
|
!defined(__OpenBSD__) && \
|
|
|
|
|
!defined(__DragonFly__) && \
|
|
|
|
|
!defined(__sun) && \
|
|
|
|
|
!defined(__ANDROID__) && \
|
|
|
|
|
!defined(__OHOS__)
|
2022-05-19 17:37:46 +02:00
|
|
|
#define SORTFUNC versionsort64
|
2012-07-17 15:30:15 +05:30
|
|
|
#else
|
2022-05-19 17:37:46 +02:00
|
|
|
#define SORTFUNC alphasort64
|
2012-07-17 15:30:15 +05:30
|
|
|
#endif
|
2022-05-19 17:37:46 +02:00
|
|
|
err = scandir64(filename, &namelist, filename_filter, SORTFUNC);
|
2010-10-13 11:48:52 +02:00
|
|
|
if (err < 0) {
|
|
|
|
|
err = -errno;
|
2025-11-07 16:37:36 +01:00
|
|
|
snd_error(UCM, "could not scan directory %s: %s", filename, strerror(-err));
|
2010-10-13 11:48:52 +02:00
|
|
|
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';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-16 18:03:03 +02:00
|
|
|
j = 0;
|
|
|
|
|
list = calloc(1, (cards + cnt) * 2 * sizeof(char *));
|
2010-10-13 11:48:52 +02:00
|
|
|
if (list == NULL) {
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __err;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-16 18:03:03 +02:00
|
|
|
i = -1;
|
|
|
|
|
while (j / 2 < cards) {
|
|
|
|
|
err = snd_card_next(&i);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __err;
|
|
|
|
|
if (i < 0)
|
|
|
|
|
break;
|
|
|
|
|
snprintf(fn, sizeof(fn), "-hw:%d", i);
|
|
|
|
|
err = snd_use_case_mgr_open(&uc_mgr, fn);
|
|
|
|
|
if (err == -ENOENT || err == -ENXIO)
|
|
|
|
|
continue;
|
|
|
|
|
if (err < 0) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "Unable to open '%s': %s", fn, snd_strerror(err));
|
2022-06-16 18:03:03 +02:00
|
|
|
goto __err;
|
|
|
|
|
}
|
|
|
|
|
err = snd_use_case_get(uc_mgr, "comment", (const char **)&s);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
err = snd_card_get_longname(i, &s);
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto __err;
|
|
|
|
|
}
|
|
|
|
|
snd_use_case_mgr_close(uc_mgr);
|
|
|
|
|
list[j] = strdup(fn + 1);
|
|
|
|
|
if (list[j] == NULL) {
|
|
|
|
|
free(s);
|
|
|
|
|
err = -ENOMEM;
|
|
|
|
|
goto __err;
|
|
|
|
|
}
|
|
|
|
|
list[j + 1] = s;
|
|
|
|
|
j += 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < cnt; i++) {
|
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);
|
2021-06-14 12:24:10 +08:00
|
|
|
#ifdef HAVE_EACCESS
|
2020-03-17 16:20:52 +01:00
|
|
|
if (eaccess(filename, R_OK))
|
2021-06-14 12:24:10 +08:00
|
|
|
#else
|
|
|
|
|
if (access(filename, R_OK))
|
|
|
|
|
#endif
|
2020-03-17 16:20:52 +01:00
|
|
|
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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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) {
|
2025-11-07 15:46:16 +01:00
|
|
|
snd_error(UCM, "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
|
|
|
}
|
2022-06-16 18:03:03 +02:00
|
|
|
err = 0;
|
2010-10-13 11:48:52 +02:00
|
|
|
|
|
|
|
|
__err:
|
2022-06-16 18:03:03 +02:00
|
|
|
for (i = 0; i < cnt; i++)
|
2010-10-13 11:48:52 +02:00
|
|
|
free(namelist[i]);
|
2022-06-16 18:03:03 +02:00
|
|
|
free(namelist);
|
|
|
|
|
if (err < 0) {
|
|
|
|
|
for (i = 0; i < j; i++) {
|
2010-10-13 11:48:52 +02:00
|
|
|
free((void *)list[i * 2]);
|
|
|
|
|
free((void *)list[i * 2 + 1]);
|
|
|
|
|
}
|
2019-05-24 21:11:00 +02:00
|
|
|
free(list);
|
2022-06-16 18:03:03 +02:00
|
|
|
return err;
|
2019-05-24 21:11:00 +02:00
|
|
|
}
|
2010-10-13 11:48:52 +02:00
|
|
|
|
2022-06-16 18:03:03 +02:00
|
|
|
*_list = list;
|
|
|
|
|
return j;
|
2010-10-13 11:48:52 +02:00
|
|
|
}
|