diff --git a/include/topology.h b/include/topology.h index d1feee4d9..8cc793314 100644 --- a/include/topology.h +++ b/include/topology.h @@ -766,6 +766,8 @@ enum snd_tplg_type { SND_TPLG_TYPE_LINK, /*!< Physical DAI link */ SND_TPLG_TYPE_HW_CONFIG, /*!< Link HW config */ SND_TPLG_TYPE_DAI, /*!< Physical DAI */ + SND_TPLG_TYPE_CLASS, /*!< Class */ + SND_TPLG_TYPE_OBJECT, /*!< Object */ }; /** Fit for all user cases */ @@ -1102,12 +1104,12 @@ typedef struct snd_tplg_obj_template { } snd_tplg_obj_template_t; /** - * \brief Register topology template object. + * \brief Register topology template element. * \param tplg Topology instance. - * \param t Template object. + * \param t Template element. * \return Zero on success, otherwise a negative error code */ -int snd_tplg_add_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int snd_tplg_add_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); /** * \brief Build all registered topology data into binary file. diff --git a/src/topology/Makefile.am b/src/topology/Makefile.am index 9f48891f5..cbc2360f9 100644 --- a/src/topology/Makefile.am +++ b/src/topology/Makefile.am @@ -30,8 +30,14 @@ libatopology_la_SOURCES =\ elem.c \ save.c \ decoder.c \ - log.c + log.c \ + class.c \ + object.c \ + dapm-object.c \ + pcm-object.c \ + dai-object.c \ + custom-object.c -noinst_HEADERS = tplg_local.h +noinst_HEADERS = tplg_local.h tplg2_local.h AM_CPPFLAGS=-I$(top_srcdir)/include diff --git a/src/topology/channel.c b/src/topology/channel.c index ebdff4696..884f8cce4 100644 --- a/src/topology/channel.c +++ b/src/topology/channel.c @@ -60,7 +60,7 @@ static const struct map_elem channel_map[] = { }; -static int lookup_channel(const char *c) +int lookup_channel(const char *c) { unsigned int i; diff --git a/src/topology/class.c b/src/topology/class.c new file mode 100644 index 000000000..4e9bc641c --- /dev/null +++ b/src/topology/class.c @@ -0,0 +1,800 @@ +/* + Copyright(c) 2020-2021 Intel Corporation + All rights reserved. + + 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.1 of + the License, or (at your option) any later version. + + This program 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. + + Author: Ranjani Sridharan +*/ + +#include "list.h" +#include "local.h" +#include "tplg_local.h" +#include "tplg2_local.h" +#include + +/* mapping of widget text names to types */ +static const struct map_elem class_map[] = { + {"Base", SND_TPLG_CLASS_TYPE_BASE}, + {"Pipeline", SND_TPLG_CLASS_TYPE_PIPELINE}, + {"Component", SND_TPLG_CLASS_TYPE_COMPONENT}, + {"Control", SND_TPLG_CLASS_TYPE_CONTROL}, + {"Dai", SND_TPLG_CLASS_TYPE_DAI}, + {"PCM", SND_TPLG_CLASS_TYPE_PCM}, +}; + + +int lookup_class_type(const char *c) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(class_map); i++) { + if (strcmp(class_map[i].name, c) == 0) + return class_map[i].id; + } + + return -EINVAL; +} + +/* save valid values for attributes */ +static int tplg_parse_constraint_valid_values(snd_tplg_t *tplg, snd_config_t *cfg, + struct attribute_constraint *c, + char *name) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + snd_config_for_each(i, next, cfg) { + struct tplg_attribute_ref *v; + const char *id, *s; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) { + SNDERR("invalid reference value for '%s'\n", name); + return -EINVAL; + } + + err = snd_config_get_string(n, &s); + if (err < 0) { + SNDERR("Invalid value for '%s'\n", name); + return err; + } + + v = calloc(1, sizeof(*v)); + + /* + * some attributes come with valid string values that translate to integer values + */ + if (c->value_ref) { + struct tplg_elem *token_elem; + + v->string = s; + + /* get reference token elem */ + token_elem = tplg_elem_lookup(&tplg->token_list, + c->value_ref, + SND_TPLG_TYPE_TOKEN, SND_TPLG_INDEX_ALL); + if (!token_elem) { + SNDERR("No valid token elem for ref '%s'\n", + c->value_ref); + free(v); + return -EINVAL; + } + + /* save the value corresponding to the string */ + v->value = get_token_value(s, token_elem->tokens); + } else { + /* others just have valid string values */ + v->string = s; + v->value = -EINVAL; + } + + list_add(&v->list, &c->value_list); + } + + return 0; +} + +/* + * Attributes can be associated with constraints such as min, max values. + * Some attributes could also have pre-defined valid values. + * The pre-defined values are human-readable values that sometimes need to be translated + * to tuple values for provate data. For ex: the value "playback" and "capture" for + * direction attributes need to be translated to 0 and 1 respectively for a DAI widget + */ +static int tplg_parse_class_constraints(snd_tplg_t *tplg, snd_config_t *cfg, + struct attribute_constraint *c, char *name) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int err; + + snd_config_for_each(i, next, cfg) { + const char *id, *s; + long v; + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + continue; + + /* set min value constraint */ + if (!strcmp(id, "min")) { + err = snd_config_get_integer(n, &v); + if (err < 0) { + SNDERR("Invalid min constraint for %s\n", name); + return err; + } + c->min = v; + continue; + } + + /* set max value constraint */ + if (!strcmp(id, "max")) { + err = snd_config_get_integer(n, &v); + if (err < 0) { + SNDERR("Invalid min constraint for %s\n", name); + return err; + } + c->max = v; + continue; + } + + /* parse reference for string values that need to be translated to tuple values */ + if (!strcmp(id, "value_ref")) { + err = snd_config_get_string(n, &s); + if (err < 0) { + SNDERR("Invalid value ref for %s\n", name); + return err; + } + c->value_ref = s; + continue; + } + + /* parse the list of valid values */ + if (!strcmp(id, "values")) { + err = tplg_parse_constraint_valid_values(tplg, n, c, name); + if (err < 0) { + SNDERR("Error parsing valid values for %s\n", name); + return err; + } + continue; + } + } + + return 0; +} + +/* check if mandatory and immutable attributes have been provided a value */ +static bool tplg_class_attribute_sanity_check(struct tplg_class *class) +{ + struct list_head *pos; + + list_for_each(pos, &class->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + /* immutable attributes must be provided a value in the class definition */ + if ((attr->constraint.mask & TPLG_CLASS_ATTRIBUTE_MASK_IMMUTABLE) && + !attr->found) { + SNDERR("Missing value for mmutable attribute '%s'in class '%s'", + attr->name, class->name); + return false; + } + } + + return true; +} + +/* + * Validate attributes that can have an array of values. Note that the array of values + * is not parsed here and should be handled by the compiler when the object containing + * this attribute is parsed. + */ +static int tplg_parse_attribute_compound_value(snd_config_t *cfg, struct tplg_attribute *attr) +{ + snd_config_iterator_t i, next; + struct list_head *pos; + snd_config_t *n; + + /* every value in the array must be valid */ + snd_config_for_each(i, next, cfg) { + const char *id, *s; + bool found = false; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) { + SNDERR("invalid cfg id for attribute %s\n", attr->name); + return -EINVAL; + } + + if (snd_config_get_string(n, &s) < 0) { + SNDERR("invalid string for attribute %s\n", attr->name); + return -EINVAL; + } + + if (list_empty(&attr->constraint.value_list)) + continue; + + list_for_each(pos, &attr->constraint.value_list) { + struct tplg_attribute_ref *v; + + v = list_entry(pos, struct tplg_attribute_ref, list); + if (!strcmp(s, v->string)) { + found = true; + break; + } + } + + if (!found) { + SNDERR("Invalid value %s for attribute %s\n", s, attr->name); + return -EINVAL; + } + } + + return 0; +} + +/* helper function to get an attribute by name */ +struct tplg_attribute *tplg_get_attribute_by_name(struct list_head *list, const char *name) +{ + struct list_head *pos; + + list_for_each(pos, list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, name)) + return attr; + } + + return NULL; +} + +/* apply the category mask to the attribute constraint */ +static int tplg_parse_class_attribute_category(snd_config_t *cfg, struct tplg_class *class, + int category) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + + snd_config_for_each(i, next, cfg) { + struct tplg_attribute *attr; + const char *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_string(n, &id) < 0) { + SNDERR("invalid attribute category name for class %s\n", class->name); + return -EINVAL; + } + + attr = tplg_get_attribute_by_name(&class->attribute_list, id); + if (!attr) + continue; + + attr->constraint.mask |= category; + } + + return 0; +} + +/* + * At the end of class attribute definitions, there could be section categorizing attributes + * as mandatory, immutable or deprecated etc. Parse these and apply them to the attribute + * constraint. + */ +static int tplg_parse_class_attribute_categories(snd_config_t *cfg, struct tplg_class *class) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int category = 0; + int ret; + + snd_config_for_each(i, next, cfg) { + const char *id; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) { + SNDERR("invalid attribute category for class %s\n", class->name); + return -EINVAL; + } + + if (!strcmp(id, "mandatory")) + category = TPLG_CLASS_ATTRIBUTE_MASK_MANDATORY; + + if (!strcmp(id, "immutable")) + category = TPLG_CLASS_ATTRIBUTE_MASK_IMMUTABLE; + + if (!strcmp(id, "deprecated")) + category = TPLG_CLASS_ATTRIBUTE_MASK_DEPRECATED; + + if (!strcmp(id, "automatic")) + category = TPLG_CLASS_ATTRIBUTE_MASK_AUTOMATIC; + + if (!strcmp(id, "unique")) { + struct tplg_attribute *unique_attr; + const char *s; + int err = snd_config_get_string(n, &s); + assert(err >= 0); + + unique_attr = tplg_get_attribute_by_name(&class->attribute_list, s); + if (!unique_attr) + continue; + + unique_attr->constraint.mask |= TPLG_CLASS_ATTRIBUTE_MASK_UNIQUE; + continue; + } + + if (!category) + continue; + + /* apply the constraint to the attribute */ + ret = tplg_parse_class_attribute_category(n, class, category); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Parse attribute values and set the attribute's type field. Attributes/arguments with + * constraints are validated against them before saving the value. + */ +int tplg_parse_attribute_value(snd_config_t *cfg, struct list_head *list, bool override) +{ + snd_config_type_t type = snd_config_get_type(cfg); + struct tplg_attribute *attr = NULL; + struct list_head *pos; + bool found = false; + int err; + const char *id; + + if (snd_config_get_id(cfg, &id) < 0) { + SNDERR("No name for attribute\n"); + return -EINVAL; + } + + /* ignore non-existent attributes */ + list_for_each(pos, list) { + attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, id)) { + found = true; + break; + } + } + + if (!found) + return 0; + + /* do not override previously set value */ + if (!override && attr->found) + return 0; + + attr->cfg = cfg; + + /* parse value */ + switch (type) { + case SND_CONFIG_TYPE_INTEGER: + { + long v; + + err = snd_config_get_integer(cfg, &v); + assert(err >= 0); + + if (v < attr->constraint.min || v > attr->constraint.max) { + SNDERR("Value %d out of range for attribute %s\n", v, attr->name); + return -EINVAL; + } + attr->value.integer = v; + break; + } + case SND_CONFIG_TYPE_INTEGER64: + { + long long v; + + err = snd_config_get_integer64(cfg, &v); + assert(err >= 0); + if (v < attr->constraint.min || v > attr->constraint.max) { + SNDERR("Value %ld out of range for attribute %s\n", v, attr->name); + return -EINVAL; + } + + attr->value.integer64 = v; + break; + } + case SND_CONFIG_TYPE_STRING: + { + struct list_head *pos; + const char *s; + + err = snd_config_get_string(cfg, &s); + assert(err >= 0); + + /* attributes with no pre-defined valid values */ + if (list_empty(&attr->constraint.value_list)) { + + /* change bool string to integer value */ + if (!strcmp(s, "true")) { + attr->value.integer = 1; + attr->type = SND_CONFIG_TYPE_INTEGER; + attr->found = true; + return 0; + } else if (!strcmp(s, "false")) { + attr->value.integer = 0; + attr->type = SND_CONFIG_TYPE_INTEGER; + attr->found = true; + return 0; + } + + snd_strlcpy(attr->value.string, s, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + break; + } + + /* Check if value is in the accepted valid values list */ + list_for_each(pos, &attr->constraint.value_list) { + struct tplg_attribute_ref *v; + + v = list_entry(pos, struct tplg_attribute_ref, list); + + if (!strcmp(s, v->string)) { + snd_strlcpy(attr->value.string, v->string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + attr->type = type; + attr->found = true; + return 0; + } + } + + SNDERR("Invalid value %s for attribute %s\n", s, attr->name); + return -EINVAL; + } + case SND_CONFIG_TYPE_REAL: + { + double d; + + err = snd_config_get_real(cfg, &d); + assert(err >= 0); + attr->value.d = d; + break; + } + case SND_CONFIG_TYPE_COMPOUND: + /* for attributes that have an array of values */ + err = tplg_parse_attribute_compound_value(cfg, attr); + if (err < 0) + return err; + break; + default: + SNDERR("Unsupported type %d for attribute %s\n", type, attr->name); + return -EINVAL; + } + + attr->type = type; + attr->found = true; + + return 0; +} + +static int tplg_parse_class_attribute(snd_tplg_t *tplg, snd_config_t *cfg, + struct tplg_attribute *attr) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + int ret; + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + continue; + + /* Parse class attribute constraints */ + if (!strcmp(id, "constraints")) { + ret = tplg_parse_class_constraints(tplg, n, &attr->constraint, + attr->name); + if (ret < 0) { + SNDERR("Error parsing constraints for %s\n", attr->name); + return -EINVAL; + } + continue; + } + + /* + * Parse token reference for class attributes/arguments. The token_ref field stores the + * name of SectionVendorTokens and type that will be used to build the tuple value for the + * attribute. For ex: "sof_tkn_dai.word" refers to the SectionVendorTokens with the name + * "sof_tkn_dai" and "word" refers to the tuple types. + */ + if (!strcmp(id, "token_ref")) { + const char *s; + + if (snd_config_get_string(n, &s) < 0) { + SNDERR("invalid token_ref for attribute %s\n", attr->name); + return -EINVAL; + } + + snd_strlcpy(attr->token_ref, s, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + continue; + } + } + + return 0; +} + + +/* Parse class attributes/arguments and add to class attribute_list */ +static int tplg_parse_class_attributes(snd_tplg_t *tplg, snd_config_t *cfg, + struct tplg_class *class, int type) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + int ret, j = 0; + + snd_config_for_each(i, next, cfg) { + struct tplg_attribute *attr; + + attr = calloc(1, sizeof(*attr)); + if (!attr) + return -ENOMEM; + attr->param_type = type; + if (type == TPLG_CLASS_PARAM_TYPE_ARGUMENT) + j++; + + /* init attribute */ + INIT_LIST_HEAD(&attr->constraint.value_list); + attr->constraint.min = INT_MIN; + attr->constraint.max = INT_MAX; + + n = snd_config_iterator_entry(i); + + if (snd_config_get_id(n, &id) < 0) + continue; + + /* set attribute name */ + snd_strlcpy(attr->name, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + ret = tplg_parse_class_attribute(tplg, n, attr); + if (ret < 0) + return ret; + + /* add to class attribute list */ + list_add_tail(&attr->list, &class->attribute_list); + } + + if (type == TPLG_CLASS_PARAM_TYPE_ARGUMENT) + class->num_args = j; + + return 0; +} + +/* create topology elem of type SND_TPLG_TYPE_CLASS */ +static struct tplg_elem *tplg_class_elem(snd_tplg_t *tplg, snd_config_t *cfg, int type) +{ + struct tplg_class *class; + struct tplg_elem *elem; + const char *id; + + if (snd_config_get_id(cfg, &id) < 0) + return NULL; + + elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_CLASS); + if (!elem) + return NULL; + + /* init class */ + class = calloc(1, sizeof(*class)); + if (!class) + return NULL; + + class->type = type; + snd_strlcpy(class->name, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + INIT_LIST_HEAD(&class->attribute_list); + INIT_LIST_HEAD(&class->object_list); + elem->class = class; + + return elem; +} + +static int tplg_create_class_object(snd_tplg_t *tplg, snd_config_t *cfg, struct list_head *list, + struct tplg_elem *class_elem) +{ + snd_config_iterator_t i, next; + struct tplg_object *object; + snd_config_t *n; + const char *id; + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* create object */ + object = tplg_create_object(tplg, n, class_elem->class, NULL, list); + if (!object) { + SNDERR("Failed to create object for class %s\n", class_elem->id); + return -EINVAL;; + } + } + + return 0; +} + +/* + * Class definition can have pre-defined objects, for ex: a PGA widget can have a mixer object. + * Parse and create these objects. They will be built when then parent object is instantiated. + */ +static int tplg_create_class_objects(snd_tplg_t *tplg, snd_config_t *cfg, struct list_head *list) +{ + snd_config_iterator_t i, next; + struct tplg_elem *class_elem; + snd_config_t *n; + const char *id; + int ret; + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + class_elem = tplg_elem_lookup(&tplg->class_list, id, + SND_TPLG_TYPE_CLASS, SND_TPLG_INDEX_ALL); + if (!class_elem) + continue; + + /* create object */ + ret = tplg_create_class_object(tplg, n, list, class_elem); + if (ret < 0) { + SNDERR("Failed to create object for class %s\n", class_elem->id); + return ret; + } + } + + return 0; +} + +static int tplg_define_class(snd_tplg_t *tplg, snd_config_t *cfg, int type) +{ + snd_config_iterator_t i, next; + struct tplg_elem *class_elem; + struct tplg_class *class; + struct tplg_elem *elem; + snd_config_t *n; + const char *id; + int ret; + + if (snd_config_get_id(cfg, &id) < 0) { + SNDERR("Invalid name for class\n"); + return -EINVAL; + } + + /* check if the class exists already */ + class_elem = tplg_elem_lookup(&tplg->class_list, id, + SND_TPLG_TYPE_CLASS, SND_TPLG_INDEX_ALL); + if (class_elem) + return 0; + + /* create class topology elem */ + elem = tplg_class_elem(tplg, cfg, type); + if (!elem) + return -ENOMEM; + + class = elem->class; + + /* Parse the class definition */ + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* parse arguments */ + if (!strcmp(id, "DefineArgument")) { + ret = tplg_parse_class_attributes(tplg, n, class, + TPLG_CLASS_PARAM_TYPE_ARGUMENT); + if (ret < 0) { + SNDERR("failed to parse args for class %s\n", class->name); + return ret; + } + + continue; + } + + /* parse attributes */ + if (!strcmp(id, "DefineAttribute")) { + ret = tplg_parse_class_attributes(tplg, n, class, + TPLG_CLASS_PARAM_TYPE_ATTRIBUTE); + if (ret < 0) { + SNDERR("failed to parse attributes for class %s\n", class->name); + return ret; + } + continue; + } + + /* parse objects */ + if (!strcmp(id, "Object")) { + ret = tplg_create_class_objects(tplg, n, &class->object_list); + if (ret < 0) { + SNDERR("Cannot create objects for class %s\n", class->name); + return -EINVAL; + } + } + + /* parse attribute constraint category and apply the constraint */ + if (!strcmp(id, "attributes")) { + ret = tplg_parse_class_attribute_categories(n, class); + if (ret < 0) { + SNDERR("failed to parse attributes for class %s\n", class->name); + return ret; + } + continue; + } + + /* class definitions come with default attribute values, process them too */ + ret = tplg_parse_attribute_value(n, &class->attribute_list, false); + if (ret < 0) { + SNDERR("failed to parse attribute value for class %s\n", class->name); + return -EINVAL; + } + } + + /* ensure immutable attributes have been provided values */ + return tplg_class_attribute_sanity_check(class); +} + +int tplg_define_classes(snd_tplg_t *tplg, snd_config_t *cfg, void *priv ATTRIBUTE_UNUSED) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + int class, ret; + + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + + /* classes must belong to one of the pre-defined types */ + class = lookup_class_type(id); + if (class < 0) { + SNDERR("Invalid class type %s\n", id); + return -EINVAL; + } + + /* create class */ + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + ret = tplg_define_class(tplg, n, class); + if (ret < 0) { + SNDERR("Failed to create class %s\n", id); + return ret; + } + } + + return 0; +} + +void tplg2_free_elem_class(struct tplg_elem *elem) +{ + struct tplg_class *class = elem->class; + struct list_head *pos, *npos; + struct tplg_attribute *attr; + + /* free attributes */ + list_for_each_safe(pos, npos, &class->attribute_list) { + attr = list_entry(pos, struct tplg_attribute, list); + list_del(&attr->list); + free(attr); + } +} diff --git a/src/topology/ctl.c b/src/topology/ctl.c index dd05424d3..dfd42cf5a 100644 --- a/src/topology/ctl.c +++ b/src/topology/ctl.c @@ -45,16 +45,21 @@ static const struct ctl_access_elem ctl_access[] = { }; /* find CTL access strings and conver to values */ -static int parse_access_values(snd_config_t *cfg, - struct snd_soc_tplg_ctl_hdr *hdr) +int parse_access_values(snd_config_t *cfg, struct snd_soc_tplg_ctl_hdr *hdr) { snd_config_iterator_t i, next; snd_config_t *n; - const char *value = NULL; + const char *id, *value = NULL; unsigned int j; tplg_dbg(" Access:"); + if (snd_config_get_id(cfg, &id) < 0) + return 0; + + if (strcmp(id, "access")) + return 0; + snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); @@ -67,6 +72,7 @@ static int parse_access_values(snd_config_t *cfg, if (strcmp(value, ctl_access[j].name) == 0) { hdr->access |= ctl_access[j].value; tplg_dbg("\t%s", value); + break; } } @@ -314,6 +320,36 @@ int tplg_build_controls(snd_tplg_t *tplg) return 0; } +int tplg_parse_tlv_dbscale_param(snd_config_t *n, struct snd_soc_tplg_tlv_dbscale *scale) +{ + const char *id = NULL; + int val; + + /* get ID */ + if (snd_config_get_id(n, &id) < 0) + return -EINVAL; + + /* get value */ + if (tplg_get_integer(n, &val, 0)) + return 0; + + tplg_dbg("\t%s = %i", id, val); + + /* get TLV data */ + if (strcmp(id, "min") == 0) + scale->min = val; + else if (strcmp(id, "step") == 0) + scale->step = val; + else if (strcmp(id, "mute") == 0) + scale->mute = val; + else if (strcmp(id, "name")) { + SNDERR("unknown id '%s'", id); + return -EINVAL; + } + + return 0; +} + /* * Parse TLV of DBScale type. @@ -326,10 +362,9 @@ static int tplg_parse_tlv_dbscale(snd_config_t *cfg, struct tplg_elem *elem) snd_config_t *n; struct snd_soc_tplg_ctl_tlv *tplg_tlv = elem->tlv; struct snd_soc_tplg_tlv_dbscale *scale; - const char *id = NULL; - int val; + int ret; - tplg_dbg(" scale: %s", elem->id); + tplg_dbg("scale: %s", elem->id); tplg_tlv->size = sizeof(struct snd_soc_tplg_ctl_tlv); tplg_tlv->type = SNDRV_CTL_TLVT_DB_SCALE; @@ -339,25 +374,9 @@ static int tplg_parse_tlv_dbscale(snd_config_t *cfg, struct tplg_elem *elem) n = snd_config_iterator_entry(i); - /* get ID */ - if (snd_config_get_id(n, &id) < 0) - return -EINVAL; - - /* get value */ - if (tplg_get_integer(n, &val, 0)) - continue; - - tplg_dbg("\t%s = %i", id, val); - - /* get TLV data */ - if (strcmp(id, "min") == 0) - scale->min = val; - else if (strcmp(id, "step") == 0) - scale->step = val; - else if (strcmp(id, "mute") == 0) - scale->mute = val; - else - SNDERR("unknown id '%s'", id); + ret = tplg_parse_tlv_dbscale_param(n, scale); + if (ret < 0) + return ret; } return 0; @@ -427,6 +446,94 @@ int tplg_save_tlv(snd_tplg_t *tplg ATTRIBUTE_UNUSED, return err; } +int tplg_parse_control_bytes_param(snd_tplg_t *tplg, snd_config_t *n, + struct snd_soc_tplg_bytes_control *be, + struct tplg_elem *elem) +{ + + const char *id, *val = NULL; + int err, ival; + + if (snd_config_get_id(n, &id) < 0) + return 0; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + return 0; + if (id[0] == '#') + return 0; + + if (strcmp(id, "base") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; + + be->base = ival; + tplg_dbg("\t%s: %d", id, be->base); + return 0; + } + + if (strcmp(id, "num_regs") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; + + be->num_regs = ival; + tplg_dbg("\t%s: %d", id, be->num_regs); + return 0; + } + + if (strcmp(id, "max") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; + + be->max = ival; + tplg_dbg("\t%s: %d", id, be->max); + return 0; + } + + if (strcmp(id, "mask") == 0) { + if (tplg_get_integer(n, &ival, 16)) + return -EINVAL; + + be->mask = ival; + tplg_dbg("\t%s: %d", id, be->mask); + return 0; + } + + if (strcmp(id, "data") == 0) { + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "tlv") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + err = tplg_ref_add(elem, SND_TPLG_TYPE_TLV, val); + if (err < 0) + return err; + + tplg_dbg("\t%s: %s", id, val); + return 0; + } + + if (strcmp(id, "ops") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_ops, &be->hdr); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "extops") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_ext_ops, be); + if (err < 0) + return err; + return 0; + } + + return 0; +} /* Parse Control Bytes */ int tplg_parse_control_bytes(snd_tplg_t *tplg, snd_config_t *cfg, @@ -436,8 +543,8 @@ int tplg_parse_control_bytes(snd_tplg_t *tplg, struct tplg_elem *elem; snd_config_iterator_t i, next; snd_config_t *n; - const char *id, *val = NULL; - int err, ival; + const char *id; + int err; bool access_set = false, tlv_set = false; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_BYTES); @@ -453,94 +560,29 @@ int tplg_parse_control_bytes(snd_tplg_t *tplg, snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); - if (snd_config_get_id(n, &id) < 0) - continue; - - /* skip comments */ - if (strcmp(id, "comment") == 0) - continue; - if (id[0] == '#') - continue; - - if (strcmp(id, "base") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; - - be->base = ival; - tplg_dbg("\t%s: %d", id, be->base); - continue; - } - - if (strcmp(id, "num_regs") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; - - be->num_regs = ival; - tplg_dbg("\t%s: %d", id, be->num_regs); - continue; - } - - if (strcmp(id, "max") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; - - be->max = ival; - tplg_dbg("\t%s: %d", id, be->max); - continue; - } - - if (strcmp(id, "mask") == 0) { - if (tplg_get_integer(n, &ival, 16)) - return -EINVAL; - - be->mask = ival; - tplg_dbg("\t%s: %d", id, be->mask); - continue; - } - - if (strcmp(id, "data") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); - if (err < 0) - return err; - continue; - } - - if (strcmp(id, "tlv") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; - - err = tplg_ref_add(elem, SND_TPLG_TYPE_TLV, val); - if (err < 0) - return err; - - tlv_set = true; - tplg_dbg("\t%s: %s", id, val); - continue; - } - - if (strcmp(id, "ops") == 0) { - err = tplg_parse_compound(tplg, n, tplg_parse_ops, - &be->hdr); - if (err < 0) - return err; - continue; - } + err = tplg_parse_control_bytes_param(tplg, n, be, elem); + if (err < 0) + return err; + } - if (strcmp(id, "extops") == 0) { - err = tplg_parse_compound(tplg, n, tplg_parse_ext_ops, - be); - if (err < 0) - return err; + /* check if access/tlv are set. No need to check for error or parse these again */ + 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, "access") == 0) { + if (!cfg) + return 0; err = parse_access(cfg, &be->hdr); if (err < 0) return err; access_set = true; continue; } + + if (!strcmp(id, "tlv")) + tlv_set = true; } /* set CTL access to default values if none are provided */ @@ -730,6 +772,86 @@ int tplg_save_control_enum(snd_tplg_t *tplg ATTRIBUTE_UNUSED, return err; } +int tplg_parse_control_mixer_param(snd_tplg_t *tplg, snd_config_t *n, + struct snd_soc_tplg_mixer_control *mc, + struct tplg_elem *elem) +{ + const char *id, *val = NULL; + int err, ival; + + if (snd_config_get_id(n, &id) < 0) + return 0; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + return 0; + if (id[0] == '#') + return 0; + + if (strcmp(id, "channel") == 0) { + if (mc->num_channels >= SND_SOC_TPLG_MAX_CHAN) { + SNDERR("too many channels %s", elem->id); + return -EINVAL; + } + + err = tplg_parse_compound(tplg, n, tplg_parse_channel, + mc->channel); + if (err < 0) + return err; + + mc->num_channels = tplg->channel_idx; + return 0; + } + + if (strcmp(id, "max") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; + + mc->max = ival; + tplg_dbg("\t%s: %d", id, mc->max); + return 0; + } + + if (strcmp(id, "invert") == 0) { + ival = snd_config_get_bool(n); + if (ival < 0) + return -EINVAL; + mc->invert = ival; + + tplg_dbg("\t%s: %d", id, mc->invert); + return 0; + } + + if (strcmp(id, "ops") == 0) { + err = tplg_parse_compound(tplg, n, tplg_parse_ops, + &mc->hdr); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "tlv") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + err = tplg_ref_add(elem, SND_TPLG_TYPE_TLV, val); + if (err < 0) + return err; + + tplg_dbg("\t%s: %s", id, val); + return 0; + } + + if (strcmp(id, "data") == 0) { + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); + if (err < 0) + return err; + return 0; + } + + return 0; +} + /* Parse Controls. * * Mixer control. Supports multiple channels. @@ -742,8 +864,8 @@ int tplg_parse_control_mixer(snd_tplg_t *tplg, struct tplg_elem *elem; snd_config_iterator_t i, next; snd_config_t *n; - const char *id, *val = NULL; - int err, j, ival; + const char *id; + int err, j; bool access_set = false, tlv_set = false; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_MIXER); @@ -763,87 +885,32 @@ int tplg_parse_control_mixer(snd_tplg_t *tplg, tplg_dbg(" Control Mixer: %s", elem->id); - /* giterate trough each mixer elment */ + /* iterate through each mixer element */ snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); - if (snd_config_get_id(n, &id) < 0) - continue; - - /* skip comments */ - if (strcmp(id, "comment") == 0) - continue; - if (id[0] == '#') - continue; - - if (strcmp(id, "channel") == 0) { - if (mc->num_channels >= SND_SOC_TPLG_MAX_CHAN) { - SNDERR("too many channels %s", elem->id); - return -EINVAL; - } - - err = tplg_parse_compound(tplg, n, tplg_parse_channel, - mc->channel); - if (err < 0) - return err; - - mc->num_channels = tplg->channel_idx; - continue; - } - - if (strcmp(id, "max") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; - - mc->max = ival; - tplg_dbg("\t%s: %d", id, mc->max); - continue; - } - - if (strcmp(id, "invert") == 0) { - ival = snd_config_get_bool(n); - if (ival < 0) - return -EINVAL; - mc->invert = ival; - - tplg_dbg("\t%s: %d", id, mc->invert); - continue; - } - - if (strcmp(id, "ops") == 0) { - err = tplg_parse_compound(tplg, n, tplg_parse_ops, - &mc->hdr); - if (err < 0) - return err; - continue; - } - - if (strcmp(id, "tlv") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; - - err = tplg_ref_add(elem, SND_TPLG_TYPE_TLV, val); - if (err < 0) - return err; - - tlv_set = true; - tplg_dbg("\t%s: %s", id, val); - continue; - } + err = tplg_parse_control_mixer_param(tplg, n, mc, elem); + if (err < 0) + return err; + } - if (strcmp(id, "data") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); - if (err < 0) - return err; + /* check if access/tlv are set. No need to check for error or parse these again */ + 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, "access") == 0) { + if (!cfg) + return 0; err = parse_access(cfg, &mc->hdr); if (err < 0) return err; access_set = true; continue; } + + if (!strcmp(id, "tlv")) + tlv_set = true; } /* set CTL access to default values if none are provided */ @@ -1193,17 +1260,17 @@ int tplg_add_bytes(snd_tplg_t *tplg, struct snd_tplg_bytes_template *bytes_ctl, return 0; } -int tplg_add_mixer_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_mixer_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { return tplg_add_mixer(tplg, t->mixer, NULL); } -int tplg_add_enum_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_enum_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { return tplg_add_enum(tplg, t->enum_ctl, NULL); } -int tplg_add_bytes_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_bytes_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { return tplg_add_bytes(tplg, t->bytes_ctl, NULL); } @@ -1315,7 +1382,7 @@ int tplg_decode_control_mixer(snd_tplg_t *tplg, err = tplg_decode_control_mixer1(tplg, &heap, &mt, pos, bin, size2); if (err >= 0) { t.mixer = &mt; - err = snd_tplg_add_object(tplg, &t); + err = snd_tplg_add_element(tplg, &t); } tplg_free(&heap); if (err < 0) @@ -1421,7 +1488,7 @@ int tplg_decode_control_enum(snd_tplg_t *tplg, err = tplg_decode_control_enum1(tplg, &heap, &et, pos, ec); if (err >= 0) { t.enum_ctl = &et; - err = snd_tplg_add_object(tplg, &t); + err = snd_tplg_add_element(tplg, &t); } tplg_free(&heap); if (err < 0) @@ -1510,7 +1577,7 @@ int tplg_decode_control_bytes(snd_tplg_t *tplg, return err; t.bytes_ctl = &bt; - err = snd_tplg_add_object(tplg, &t); + err = snd_tplg_add_element(tplg, &t); if (err < 0) return err; diff --git a/src/topology/custom-object.c b/src/topology/custom-object.c new file mode 100644 index 000000000..ba4c6584a --- /dev/null +++ b/src/topology/custom-object.c @@ -0,0 +1,351 @@ +/* + Copyright(c) 2020-2021 Intel Corporation + All rights reserved. + + 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.1 of + the License, or (at your option) any later version. + + This program 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. + + Author: Ranjani Sridharan +*/ + +/* + * This file contains the create/build routines for custom classes that are not + * DAI, component or PCM type + */ +#define TPLG_DEBUG + +#include "list.h" +#include "local.h" +#include "tplg_local.h" +#include "tplg2_local.h" +#include + +static void tplg_set_stream_name(struct tplg_object *object) +{ + struct tplg_attribute *pcm_name, *pcm_id, *dir, *stream_name; + int ret; + + pcm_name = tplg_get_attribute_by_name(&object->attribute_list, "pcm_name"); + pcm_id = tplg_get_attribute_by_name(&object->attribute_list, "pcm_id"); + dir = tplg_get_attribute_by_name(&object->attribute_list, "direction"); + stream_name = tplg_get_attribute_by_name(&object->attribute_list, "stream_name"); + + ret = snprintf(stream_name->value.string, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "%s.%s.%ld", pcm_name->value.string, + dir->value.string, pcm_id->value.integer); + if (ret > SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + SNDERR("warning: widget stream name truncated \n"); + + stream_name->found = true; + stream_name->type = SND_CONFIG_TYPE_STRING; +} + +/* pipeline object customization */ +int tplg_create_pipeline_object(struct tplg_class *class, struct tplg_object *object) +{ + struct list_head *pos; + + /* check if child objects are of the right type */ + list_for_each(pos, &class->object_list) { + struct tplg_object *obj = list_entry(pos, struct tplg_object, list); + + switch (obj->type) { + case SND_TPLG_CLASS_TYPE_BASE: + if (!strcmp(obj->class_name, "connection") || + !strcmp(obj->class_name, "endpoint")) + break; + SNDERR("Unexpected child class %s for pipeline %s\n", obj->class_name, + object->name); + return -EINVAL; + case SND_TPLG_CLASS_TYPE_COMPONENT: + case SND_TPLG_CLASS_TYPE_PCM: + break; + default: + SNDERR("Unexpected child object type %d for %s\n", obj->type, object->name); + return -EINVAL; + } + } + + return 0; +} + +static int tplg_get_sample_size_from_format(char *format) +{ + if (!strcmp(format, "s32le") || !strcmp(format, "s24le") || !strcmp(format, "float") ) + return 4; + + if (!strcmp(format, "s16le")) + return 2; + + return -EINVAL; +} + +static int tplg_update_buffer_size(struct tplg_object *buffer_object, + struct tplg_object *pipeline_object) +{ + struct list_head *pos; + struct tplg_attribute *size_attribute = NULL; + char pipeline_format[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int periods = 0; + int sample_size; + int channels = 0; + int frames = 0; + int rate = 0; + int schedule_period = 0; + + /* get periods and channels from buffer object */ + list_for_each(pos, &buffer_object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, "periods")) { + if (attr->type == SND_CONFIG_TYPE_INTEGER) { + periods = attr->value.integer; + } else { + SNDERR("Invalid value for periods for object %s \n", + buffer_object->name); + return -EINVAL; + } + } + + if (!strcmp(attr->name, "channels")) { + if (attr->type == SND_CONFIG_TYPE_INTEGER) { + channels = attr->value.integer; + } else { + SNDERR("Invalid value for channels for object %s \n", + buffer_object->name); + return -EINVAL; + } + } + + if (!strcmp(attr->name, "size")) + size_attribute = attr; + } + + if (!size_attribute) { + SNDERR("Can't find size attribute for %s\n", buffer_object->name); + return -EINVAL; + } + + /* get schedule_period, channels, rate and format from pipeline object */ + list_for_each(pos, &pipeline_object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, "period")) { + if (attr->type == SND_CONFIG_TYPE_INTEGER) { + schedule_period = attr->value.integer; + } else { + SNDERR("Invalid value for period for object %s \n", + pipeline_object->name); + return -EINVAL; + } + } + + if (!strcmp(attr->name, "rate")) { + if (attr->type == SND_CONFIG_TYPE_INTEGER) { + rate = attr->value.integer; + } else { + SNDERR("Invalid value for rate for object %s \n", + pipeline_object->name); + return -EINVAL; + } + } + + if (!strcmp(attr->name, "format")) { + if (attr->type == SND_CONFIG_TYPE_STRING) { + snd_strlcpy(pipeline_format, attr->value.string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + } else { + SNDERR("Invalid format for pipeline %s \n", + pipeline_object->name); + return -EINVAL; + } + } + } + + sample_size = tplg_get_sample_size_from_format(pipeline_format); + if (sample_size < 0) { + SNDERR("Invalid value for sample size for object %s \n", pipeline_object->name); + return sample_size; + } + + /* compute buffer size */ + frames = (rate * schedule_period)/1000000; + size_attribute->value.integer = periods * sample_size * channels * frames; + if (!size_attribute->value.integer) { + SNDERR("Invalid buffer size %d for %s \n",size_attribute->value.integer, + buffer_object->name); + return -EINVAL; + } + + size_attribute->found = true; + size_attribute->type = SND_CONFIG_TYPE_INTEGER; + + return 0; +} + +/* + * Widget names for route source/sink or pipeline endpoints can be of the following type: + * "Object.class.index" which refers to an object of class "class" with index in the + * parent object_list or the global topology object_list + */ +static int tplg_set_widget_name(snd_tplg_t *tplg, struct tplg_object *object, + struct tplg_object *parent, char *string, + struct tplg_attribute *dest_widget) +{ + struct tplg_object *child = NULL; + char *object_str, *last_dot; + char class_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN], *index_str; + + /* strip "Object." from the string */ + object_str = strchr(string, '.'); + if (!object_str) { + SNDERR("Incomplete name for source object in route %s for parent %s\n", + object->name, parent->name); + return -EINVAL; + } + + /* get last occurence of '.' */ + last_dot = strrchr(string, '.'); + + /* get index of object */ + index_str = strchr(object_str + 1, '.'); + if (!index_str) { + SNDERR("No unique attribute for object in route %s for parent %s\n", + object->name, parent->name); + return 0; + } + + /* get class name */ + snd_strlcpy(class_name, object_str + 1, strlen(object_str) - strlen(index_str)); + + + /* look up object from parent object_list */ + if (parent) + child = tplg_object_lookup_in_list(&parent->object_list, class_name, + index_str + 1); + else + /* look up object from global list */ + child = tplg_object_elem_lookup(tplg, class_name, index_str + 1); + + if (!child) { + SNDERR("No object %s.%s found in parent %s\n", + class_name, index_str, object->name, parent->name); + return -EINVAL; + } + + /* end of string? */ + if (last_dot != index_str) { + char *str = strchr(index_str + 1, '.'); + char new_str[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + snprintf(new_str, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s%s", "Object", str); + + return tplg_set_widget_name(tplg, object, child, new_str, dest_widget); + } + + /* for endpoint objects, gets the widget name */ + if (!strcmp(child->class_name, "endpoint")) { + struct tplg_attribute *widget_name; + + widget_name = tplg_get_attribute_by_name(&child->attribute_list, "widget_name"); + snd_strlcpy(dest_widget->value.string, widget_name->value.string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + } else { + snd_strlcpy(dest_widget->value.string, child->name, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + } + + return 0; +} + +/* Update Object references for source/sink in route objects */ +static int tplg_update_route_object(snd_tplg_t *tplg, struct tplg_object *object, + struct tplg_object *parent) +{ + struct tplg_attribute *widget, *src_widget_name, *sink_widget_name; + int ret; + + /* set source widget name */ + widget = tplg_get_attribute_by_name(&object->attribute_list, "source"); + src_widget_name = tplg_get_attribute_by_name(&object->attribute_list, "source_widget"); + ret = tplg_set_widget_name(tplg, object, parent, widget->value.string, + src_widget_name); + if (ret < 0) { + SNDERR("Failed to set source widget name for %s\n", object->name); + return ret; + } + + /* set sink widget name */ + widget = tplg_get_attribute_by_name(&object->attribute_list, "sink"); + sink_widget_name = tplg_get_attribute_by_name(&object->attribute_list, "sink_widget"); + ret = tplg_set_widget_name(tplg, object, parent, widget->value.string, + sink_widget_name); + if (ret < 0) + SNDERR("Failed to set sink widget name for %s\n", object->name); + + tplg_dbg("route: source: %s -> sink: %s", src_widget_name->value.string, + sink_widget_name->value.string); + + return ret; +} + +int tplg_update_automatic_attributes(snd_tplg_t *tplg, struct tplg_object *object, + struct tplg_object *parent) +{ + struct list_head *pos; + int ret; + + /* set source/sink widget names for routes */ + if (!strcmp(object->class_name, "connection")) { + ret = tplg_update_route_object(tplg, object, parent); + if (ret < 0) + return ret; + } + + /* set widget name for pipeline endpoint objects */ + if (!strcmp(object->class_name, "endpoint")) { + struct tplg_attribute *widget_name, *widget; + + widget = tplg_get_attribute_by_name(&object->attribute_list, "widget"); + widget_name = tplg_get_attribute_by_name(&object->attribute_list, "widget_name"); + ret = tplg_set_widget_name(tplg, object, parent, widget->value.string, + widget_name); + if (ret < 0) { + SNDERR("Failed to set source widget name for %s\n", object->name); + return ret; + } + + tplg_dbg("endpoint widget name %s", widget_name->value.string); + } + + if (!strcmp(object->class_name, "host") || + !strcmp(object->class_name, "copier")) { + tplg_set_stream_name(object); + } + + if (!strcmp(object->class_name, "buffer")) { + if (parent) { + ret = tplg_update_buffer_size(object, parent); + if (ret < 0) + return 0; + } + } + + /* now update all automatic attributes for all child objects */ + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + + ret = tplg_update_automatic_attributes(tplg, child, object); + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/src/topology/dai-object.c b/src/topology/dai-object.c new file mode 100644 index 000000000..923453bf4 --- /dev/null +++ b/src/topology/dai-object.c @@ -0,0 +1,180 @@ +/* + Copyright(c) 2020-2021 Intel Corporation + All rights reserved. + + 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.1 of + the License, or (at your option) any later version. + + This program 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. + + Author: Ranjani Sridharan +*/ + +#include "list.h" +#include "local.h" +#include "tplg_local.h" +#include "tplg2_local.h" +#include + +int tplg_create_dai_object(struct tplg_class *class, struct tplg_object *object) +{ + struct list_head *pos; + + /* check if child objects are of the right type */ + list_for_each(pos, &class->object_list) { + struct tplg_object *obj = list_entry(pos, struct tplg_object, list); + + switch (obj->type) { + case SND_TPLG_CLASS_TYPE_BASE: + if (!strcmp(obj->class_name, "endpoint")) + break; + + SNDERR("Unexpected child class %s for dai %s\n", obj->class_name, + object->name); + return -EINVAL; + case SND_TPLG_CLASS_TYPE_COMPONENT: + break; + default: + SNDERR("Unexpected child type %d for %s\n", obj->type, object->name); + return -EINVAL; + } + } + + return 0; +} + +static int tplg_create_link_elem(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct tplg_attribute *stream_name, *id; + struct tplg_attribute *default_hw_cfg; + struct tplg_dai_object *dai = &object->object_type.dai; + struct tplg_elem *link_elem, *data_elem; + struct snd_soc_tplg_link_config *link; + int ret; + + stream_name = tplg_get_attribute_by_name(&object->attribute_list, "stream_name"); + id = tplg_get_attribute_by_name(&object->attribute_list, "id"); + default_hw_cfg = tplg_get_attribute_by_name(&object->attribute_list, "default_hw_config"); + + if (!stream_name || stream_name->type != SND_CONFIG_TYPE_STRING) { + SNDERR("No DAI name for %s\n", object->name); + return -EINVAL; + } + + link_elem = tplg_elem_new_common(tplg, NULL, stream_name->value.string, SND_TPLG_TYPE_BE); + if (!link_elem) + return -ENOMEM; + dai->link_elem = link_elem; + + link = link_elem->link; + link->size = link_elem->size; + snd_strlcpy(link->name, link_elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + link->default_hw_config_id = default_hw_cfg->value.integer; + link->id = id->value.integer; + + /* create data elem for link */ + data_elem = tplg_elem_new_common(tplg, NULL, object->name, SND_TPLG_TYPE_DATA); + if (!data_elem) + return -ENOMEM; + + ret = tplg_ref_add(link_elem, SND_TPLG_TYPE_DATA, data_elem->id); + if (ret < 0) { + SNDERR("failed to add data elem %s to link elem %s\n", data_elem->id, + link_elem->id); + return ret; + } + + return 0; +} + +int tplg_build_dai_object(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct tplg_dai_object *dai = &object->object_type.dai; + struct snd_soc_tplg_link_config *link; + struct tplg_elem *l_elem; + struct list_head *pos, *_pos; + int i = 0; + int ret; + + ret = tplg_create_link_elem(tplg, object); + if (ret < 0) { + SNDERR("Failed to create widget elem for object\n", object->name); + return ret; + } + l_elem = dai->link_elem; + link = l_elem->link; + + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + struct list_head *pos1; + + if (!strcmp(child->class_name, "hw_config")) { + struct tplg_attribute *id; + struct snd_soc_tplg_hw_config *hw_cfg = &link->hw_config[i++]; + + /* set hw_config ID */ + id = tplg_get_attribute_by_name(&child->attribute_list, "id"); + if (!id || id->type != SND_CONFIG_TYPE_INTEGER) { + SNDERR("No ID for hw_config %s\n", child->name); + return -EINVAL; + } + hw_cfg->id = id->value.integer; + + /* parse hw_config params from attributes */ + list_for_each(pos1, &child->attribute_list) { + struct tplg_attribute *attr; + + attr = list_entry(pos1, struct tplg_attribute, list); + if (!attr->cfg) + continue; + + ret = tplg_set_hw_config_param(attr->cfg, hw_cfg); + if (ret < 0) { + SNDERR("Error parsing hw_config for object %s\n", + object->name); + return ret; + } + } + tplg_dbg("HW Config: %d", hw_cfg->id); + } + + if (!strcmp(child->class_name, "pdm_config")) { + /* build tuple sets for pdm_config object */ + ret = tplg_build_object_tuple_sets(child); + if (ret < 0) + return ret; + + list_for_each_safe(pos1, _pos, &child->tuple_set_list) { + struct tplg_tuple_set *set; + set = list_entry(pos1, struct tplg_tuple_set, list); + list_del(&set->list); + list_add_tail(&set->list, &object->tuple_set_list); + } + } + } + + /* parse link params from attributes */ + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!attr->cfg) + continue; + + ret = tplg_parse_link_param(tplg, attr->cfg, link, NULL); + if (ret < 0) { + SNDERR("Error parsing hw_config for object %s\n", + object->name); + return ret; + } + } + + link->num_hw_configs = i; + tplg_dbg("Link elem: %s num_hw_configs: %d", l_elem->id, link->num_hw_configs); + + return tplg_build_private_data(tplg, object); +} diff --git a/src/topology/dapm-object.c b/src/topology/dapm-object.c new file mode 100644 index 000000000..d21f14c06 --- /dev/null +++ b/src/topology/dapm-object.c @@ -0,0 +1,572 @@ +/* + Copyright(c) 2020-2021 Intel Corporation + All rights reserved. + + 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.1 of + the License, or (at your option) any later version. + + This program 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. + + Author: Ranjani Sridharan +*/ +#include "list.h" +#include "local.h" +#include "tplg_local.h" +#include "tplg2_local.h" +#include + +int tplg_create_component_object(struct tplg_object *object) +{ + struct tplg_comp_object *comp = &object->object_type.component; + struct tplg_attribute *widget_type; + int widget_id; + + widget_type = tplg_get_attribute_by_name(&object->attribute_list, "widget_type"); + if (!widget_type) { + SNDERR("No widget_type given for %s\n", object->name); + return -EINVAL; + } + + widget_id = lookup_widget(widget_type->value.string); + + if (widget_id < 0) { + SNDERR("Invalid widget ID for %s\n", object->name); + return widget_id; + } + + comp->widget_id = widget_id; + return 0; +} + +static int tplg_dapm_route_validate_widget(snd_tplg_t *tplg, char *wname, char *dest) +{ + struct tplg_elem *w_elem; + + /* check if it is a valid widget */ + w_elem = tplg_elem_lookup(&tplg->widget_list, wname, + SND_TPLG_TYPE_DAPM_WIDGET, SND_TPLG_INDEX_ALL); + if (!w_elem) { + SNDERR("No widget %s found\n", wname); + return -EINVAL; + } + + snd_strlcpy(dest, w_elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + return 0; +} + +int tplg_build_dapm_route(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct snd_soc_tplg_dapm_graph_elem *line; + struct list_head *pos; + struct tplg_elem *elem; + int ret; + + /* create graph elem */ + elem = tplg_elem_new_route(tplg, 0); + if (!elem) + return -ENOMEM; + + line = elem->route; + + /* set graph elem index and control values */ + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, "pipeline_id")) { + elem->index = attr->value.integer; + continue; + } + + if (!strcmp(attr->name, "control")) + snd_strlcpy(line->control, attr->value.string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + if (!strcmp(attr->name, "source_widget")) { + ret = tplg_dapm_route_validate_widget(tplg, attr->value.string, + line->source); + if (ret < 0) { + SNDERR("Failed to find source widget for route %s\n", object->name); + return ret; + } + } + + if (!strcmp(attr->name, "sink_widget")) { + ret = tplg_dapm_route_validate_widget(tplg, attr->value.string, + line->sink); + if (ret < 0) { + SNDERR("Failed to find sink widget for route %s\n", object->name); + return ret; + } + } + } + + tplg_dbg("DAPM route: %s -> %s", line->source, line->sink); + + return 0; +} + +static int tplg2_parse_channel(struct tplg_object *object, struct tplg_elem *mixer_elem) +{ + struct snd_soc_tplg_mixer_control *mc = mixer_elem->mixer_ctrl; + struct snd_soc_tplg_channel *channel = mc->channel; + struct list_head *pos; + char *channel_name = strchr(object->name, '.') + 1; + int channel_id = lookup_channel(channel_name); + + if (channel_id < 0) { + SNDERR("invalid channel %d for mixer %s", channel_id, mixer_elem->id); + return -EINVAL; + } + + channel += mc->num_channels; + + channel->id = channel_id; + channel->size = sizeof(*channel); + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, "reg")) + channel->reg = attr->value.integer; + + + if (!strcmp(attr->name, "shift")) + channel->shift = attr->value.integer; + } + + mc->num_channels++; + if (mc->num_channels >= SND_SOC_TPLG_MAX_CHAN) { + SNDERR("Max channels exceeded for %s\n", mixer_elem->id); + return -EINVAL; + } + + tplg_dbg("channel: %s id: %d reg:%d shift %d", channel_name, channel->id, channel->reg, channel->shift); + + return 0; +} + +static int tplg2_parse_tlv(snd_tplg_t *tplg, struct tplg_object *object, + struct tplg_elem *mixer_elem) +{ + struct snd_soc_tplg_ctl_tlv *tplg_tlv; + struct snd_soc_tplg_tlv_dbscale *scale; + struct tplg_elem *elem; + struct list_head *pos; + int ret; + + /* Just add ref if TLV elem exists already */ + elem = tplg_elem_lookup(&tplg->widget_list, object->name, SND_TPLG_TYPE_TLV, + SND_TPLG_INDEX_ALL); + if (elem) { + tplg_tlv = elem->tlv; + scale = &tplg_tlv->scale; + goto ref; + } + + /* otherwise create new TLV elem */ + elem = tplg_elem_new_common(tplg, NULL, object->name, SND_TPLG_TYPE_TLV); + if (!elem) + return -ENOMEM; + + tplg_tlv = elem->tlv; + tplg_tlv->size = sizeof(struct snd_soc_tplg_ctl_tlv); + tplg_tlv->type = SNDRV_CTL_TLVT_DB_SCALE; + scale = &tplg_tlv->scale; + + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + + if (!strcmp(child->class_name, "scale")) { + list_for_each(pos, &child->attribute_list) { + struct tplg_attribute *attr; + + attr = list_entry(pos, struct tplg_attribute, list); + if (!attr->cfg) + continue; + + ret = tplg_parse_tlv_dbscale_param(attr->cfg, scale); + if (ret < 0) { + SNDERR("failed to DBScale for tlv %s", object->name); + return ret; + } + } + + break; + } + } +ref: + tplg_dbg("TLV: %s scale min: %d step %d mute %d", elem->id, scale->min, scale->step, scale->mute); + + ret = tplg_ref_add(mixer_elem, SND_TPLG_TYPE_TLV, elem->id); + if (ret < 0) { + SNDERR("failed to add tlv elem %s to mixer elem %s\n", + elem->id, mixer_elem->id); + return ret; + } + + return 0; +} + +static struct tplg_elem *tplg_build_comp_mixer(snd_tplg_t *tplg, struct tplg_object *object, + char *name) +{ + struct snd_soc_tplg_mixer_control *mc; + struct snd_soc_tplg_ctl_hdr *hdr; + struct tplg_elem *elem; + struct list_head *pos; + bool access_set = false, tlv_set = false; + int j, ret; + + elem = tplg_elem_new_common(tplg, NULL, name, SND_TPLG_TYPE_MIXER); + if (!elem) + return NULL; + + /* init new mixer */ + mc = elem->mixer_ctrl; + snd_strlcpy(mc->hdr.name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + mc->hdr.type = SND_SOC_TPLG_TYPE_MIXER; + mc->size = elem->size; + hdr = &mc->hdr; + + /* set channel reg to default state */ + for (j = 0; j < SND_SOC_TPLG_MAX_CHAN; j++) + mc->channel[j].reg = -1; + + /* parse some control params from attributes */ + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr; + + attr = list_entry(pos, struct tplg_attribute, list); + + if (!attr->cfg) + continue; + + ret = tplg_parse_control_mixer_param(tplg, attr->cfg, mc, elem); + if (ret < 0) { + SNDERR("Error parsing hw_config for %s\n", object->name); + return NULL; + } + + if (!strcmp(attr->name, "access")) { + ret = parse_access_values(attr->cfg, &mc->hdr); + if (ret < 0) { + SNDERR("Error parsing access attribute for %s\n", object->name); + return NULL; + } + access_set = true; + } + + } + + /* parse the rest from child objects */ + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + + if (!object->cfg) + continue; + + if (!strcmp(child->class_name, "ops")) { + ret = tplg_parse_ops(tplg, child->cfg, hdr); + if (ret < 0) { + SNDERR("Error parsing ops for mixer %s\n", object->name); + return NULL; + } + continue; + } + + if (!strcmp(child->class_name, "tlv")) { + ret = tplg2_parse_tlv(tplg, child, elem); + if (ret < 0) { + SNDERR("Error parsing tlv for mixer %s\n", object->name); + return NULL; + } + tlv_set = true; + continue; + } + + if (!strcmp(child->class_name, "channel")) { + ret = tplg2_parse_channel(child, elem); + if (ret < 0) { + SNDERR("Error parsing channel %d for mixer %s\n", child->name, + object->name); + return NULL; + } + continue; + } + } + tplg_dbg("Mixer: %s, num_channels: %d", elem->id, mc->num_channels); + tplg_dbg("Ops info: %d get: %d put: %d max: %d", hdr->ops.info, hdr->ops.get, hdr->ops.put, mc->max); + + /* set CTL access to default values if none provided */ + if (!access_set) { + mc->hdr.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (tlv_set) + mc->hdr.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + } + + return elem; +} + +static struct tplg_elem *tplg_build_comp_bytes(snd_tplg_t *tplg, struct tplg_object *object, + char *name) +{ + struct snd_soc_tplg_bytes_control *be; + struct snd_soc_tplg_ctl_hdr *hdr; + struct tplg_elem *elem; + struct list_head *pos; + bool access_set = false, tlv_set = false; + int ret; + + elem = tplg_elem_new_common(tplg, NULL, name, SND_TPLG_TYPE_BYTES); + if (!elem) + return NULL; + + /* init new byte control */ + be = elem->bytes_ext; + snd_strlcpy(be->hdr.name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + be->hdr.type = SND_SOC_TPLG_TYPE_BYTES; + be->size = elem->size; + hdr = &be->hdr; + + /* parse some control params from attributes */ + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr; + + attr = list_entry(pos, struct tplg_attribute, list); + + if (!attr->cfg) + continue; + + ret = tplg_parse_control_bytes_param(tplg, attr->cfg, be, elem); + if (ret < 0) { + SNDERR("Error parsing control bytes params for %s\n", object->name); + return NULL; + } + + if (!strcmp(attr->name, "access")) { + ret = parse_access_values(attr->cfg, &be->hdr); + if (ret < 0) { + SNDERR("Error parsing access attribute for %s\n", object->name); + return NULL; + } else { + access_set = true; + } + } + + } + + /* parse the rest from child objects */ + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + + if (!object->cfg) + continue; + + if (!strcmp(child->class_name, "ops")) { + ret = tplg_parse_ops(tplg, child->cfg, hdr); + if (ret < 0) { + SNDERR("Error parsing ops for mixer %s\n", object->name); + return NULL; + } + continue; + } + + if (!strcmp(child->class_name, "tlv")) { + ret = tplg2_parse_tlv(tplg, child, elem); + if (ret < 0) { + SNDERR("Error parsing tlv for mixer %s\n", object->name); + return NULL; + } else { + tlv_set = true; + } + continue; + } + + if (!strcmp(child->class_name, "extops")) { + ret = tplg_parse_ext_ops(tplg, child->cfg, &be->hdr); + if (ret < 0) { + SNDERR("Error parsing ext ops for bytes %s\n", object->name); + return NULL; + } + continue; + } + + /* add data reference for byte control by adding a new obect */ + if (!strcmp(child->class_name, "data")) { + struct tplg_attribute *name; + + name = tplg_get_attribute_by_name(&child->attribute_list, "name"); + /* add reference to data elem */ + ret = tplg_ref_add(elem, SND_TPLG_TYPE_DATA, name->value.string); + if (ret < 0) { + SNDERR("failed to add data elem %s to byte control %s\n", + name->value.string, elem->id); + return NULL; + } + } + } + + tplg_dbg("Bytes: %s Ops info: %d get: %d put: %d", elem->id, hdr->ops.info, hdr->ops.get, + hdr->ops.put); + tplg_dbg("Ext Ops info: %d get: %d put: %d", be->ext_ops.info, be->ext_ops.get, be->ext_ops.put); + + /* set CTL access to default values if none provided */ + if (!access_set) { + be->hdr.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (tlv_set) + be->hdr.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + } + + return elem; +} + +static int tplg_create_widget_elem(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct tplg_comp_object *widget_object = &object->object_type.component; + struct tplg_elem *widget_elem, *data_elem; + struct snd_soc_tplg_dapm_widget *widget; + char *class_name = object->class_name; + char *elem_name; + int ret; + + if (strcmp(class_name, "virtual_widget")) + elem_name = object->name; + else + elem_name = strchr(object->name, '.') + 1; + + widget_elem = tplg_elem_new_common(tplg, NULL, elem_name, + SND_TPLG_TYPE_DAPM_WIDGET); + if (!widget_elem) + return -ENOMEM; + + /* create data elem for w */ + data_elem = tplg_elem_new_common(tplg, NULL, elem_name, SND_TPLG_TYPE_DATA); + if (!data_elem) + return -ENOMEM; + + ret = tplg_ref_add(widget_elem, SND_TPLG_TYPE_DATA, data_elem->id); + if (ret < 0) { + SNDERR("failed to add data elem %s to widget elem %s\n", data_elem->id, + widget_elem->id); + return ret; + } + + widget_object->widget_elem = widget_elem; + widget = widget_elem->widget; + widget->id = widget_object->widget_id; + widget->size = widget_elem->size; + snd_strlcpy(widget->name, widget_elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + return 0; +} + +int tplg_build_comp_object(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct tplg_attribute *pipeline_id; + struct snd_soc_tplg_dapm_widget *widget; + struct tplg_comp_object *comp = &object->object_type.component; + struct tplg_elem *w_elem; + struct list_head *pos; + int ret; + + ret = tplg_create_widget_elem(tplg, object); + if (ret < 0) { + SNDERR("Failed to create widget elem for object %s\n", object->name); + return ret; + } + w_elem = comp->widget_elem; + widget = w_elem->widget; + + pipeline_id = tplg_get_attribute_by_name(&object->attribute_list, "pipeline_id"); + if (pipeline_id) + w_elem->index = pipeline_id->value.integer; + + /* parse widget params from attributes */ + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, "stream_name") && attr->found) { + snd_strlcpy(widget->sname, attr->value.string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + continue; + } + + if (!attr->cfg) + continue; + + /* widget type is already processed */ + if (!strcmp(attr->name, "type")) + continue; + + ret = tplg_parse_dapm_widget_param(attr->cfg, widget, NULL); + if (ret < 0) { + SNDERR("Error parsing widget params for %s\n", object->name); + return ret; + } + } + + /* build controls */ + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + struct tplg_elem *elem; + char *class_name = child->class_name; + + if (!strcmp(class_name, "mixer")) { + struct tplg_attribute *name_attr; + + /* skip if no name is provided */ + name_attr = tplg_get_attribute_by_name(&child->attribute_list, + "name"); + + if (!name_attr || !strcmp(name_attr->value.string, "")) + continue; + + elem = tplg_build_comp_mixer(tplg, child, name_attr->value.string); + if (!elem) { + SNDERR("Failed to build mixer control for %s\n", object->name); + return -EINVAL; + } + + ret = tplg_ref_add(w_elem, SND_TPLG_TYPE_MIXER, elem->id); + if (ret < 0) { + SNDERR("failed to add mixer elem %s to widget elem %s\n", + elem->id, w_elem->id); + return ret; + } + } + + if (!strcmp(class_name, "bytes")) { + struct tplg_attribute *name_attr; + + /* skip if no name is provided */ + name_attr = tplg_get_attribute_by_name(&child->attribute_list, + "name"); + + if (!name_attr || !strcmp(name_attr->value.string, "")) + continue; + + elem = tplg_build_comp_bytes(tplg, child, name_attr->value.string); + if (!elem) { + SNDERR("Failed to build bytes control for %s\n", object->name); + return -EINVAL; + } + + ret = tplg_ref_add(w_elem, SND_TPLG_TYPE_BYTES, elem->id); + if (ret < 0) { + SNDERR("failed to add bytes control elem %s to widget elem %s\n", + elem->id, w_elem->id); + return ret; + } + } + } + + tplg_dbg("Widget: %s id: %d stream_name: %s no_pm: %d", + w_elem->id, widget->id, widget->sname, widget->reg); + + return tplg_build_private_data(tplg, object); +} diff --git a/src/topology/dapm.c b/src/topology/dapm.c index f6a84a603..e8ad9dd50 100644 --- a/src/topology/dapm.c +++ b/src/topology/dapm.c @@ -48,7 +48,7 @@ static const struct map_elem widget_map[] = { {"decoder", SND_SOC_TPLG_DAPM_DECODER}, }; -static int lookup_widget(const char *w) +int lookup_widget(const char *w) { unsigned int i; @@ -507,161 +507,173 @@ int tplg_save_dapm_graph(snd_tplg_t *tplg, int index, return err; } -/* DAPM Widget */ -int tplg_parse_dapm_widget(snd_tplg_t *tplg, - snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +int tplg_parse_dapm_widget_param(snd_config_t *n, struct snd_soc_tplg_dapm_widget *widget, + struct tplg_elem *elem) { - struct snd_soc_tplg_dapm_widget *widget; - struct tplg_elem *elem; - snd_config_iterator_t i, next; - snd_config_t *n; const char *id, *val = NULL; int widget_type, err, ival; - elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_DAPM_WIDGET); - if (!elem) - return -ENOMEM; + if (snd_config_get_id(n, &id) < 0) + return 0; - tplg_dbg(" Widget: %s", elem->id); + /* skip comments */ + if (strcmp(id, "comment") == 0) + return 0; + if (id[0] == '#') + return 0; - widget = elem->widget; - snd_strlcpy(widget->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); - widget->size = elem->size; + if (strcmp(id, "type") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - snd_config_for_each(i, next, cfg) { + widget_type = lookup_widget(val); + if (widget_type < 0){ + SNDERR("widget '%s': Unsupported widget type %s", + elem->id, val); + return -EINVAL; + } - n = snd_config_iterator_entry(i); - if (snd_config_get_id(n, &id) < 0) - continue; + widget->id = widget_type; + tplg_dbg("\t%s: %s", id, val); + return 0; + } - /* skip comments */ - if (strcmp(id, "comment") == 0) - continue; - if (id[0] == '#') - continue; + if (strcmp(id, "stream_name") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - if (strcmp(id, "type") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + snd_strlcpy(widget->sname, val, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + tplg_dbg("\t%s: %s", id, val); + return 0; + } - widget_type = lookup_widget(val); - if (widget_type < 0){ - SNDERR("widget '%s': Unsupported widget type %s", - elem->id, val); - return -EINVAL; - } + if (strcmp(id, "no_pm") == 0) { + ival = snd_config_get_bool(n); + if (ival < 0) + return -EINVAL; - widget->id = widget_type; - tplg_dbg("\t%s: %s", id, val); - continue; - } + widget->reg = ival ? -1 : 0; - if (strcmp(id, "stream_name") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + tplg_dbg("\t%s: %s", id, val); + return 0; + } - snd_strlcpy(widget->sname, val, - SNDRV_CTL_ELEM_ID_NAME_MAXLEN); - tplg_dbg("\t%s: %s", id, val); - continue; - } + if (strcmp(id, "shift") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; - if (strcmp(id, "no_pm") == 0) { - ival = snd_config_get_bool(n); - if (ival < 0) - return -EINVAL; + widget->shift = ival; + tplg_dbg("\t%s: %d", id, widget->shift); + return 0; + } - widget->reg = ival ? -1 : 0; + if (strcmp(id, "reg") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; - tplg_dbg("\t%s: %s", id, val); - continue; - } + widget->reg = ival; + tplg_dbg("\t%s: %d", id, widget->reg); + return 0; + } - if (strcmp(id, "shift") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; + if (strcmp(id, "invert") == 0) { + ival = snd_config_get_bool(n); + if (ival < 0) + return -EINVAL; - widget->shift = ival; - tplg_dbg("\t%s: %d", id, widget->shift); - continue; - } + widget->invert = ival; + tplg_dbg("\t%s: %d", id, widget->invert); + return 0; + } - if (strcmp(id, "reg") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; + if (strcmp(id, "subseq") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; - widget->reg = ival; - tplg_dbg("\t%s: %d", id, widget->reg); - continue; - } + widget->subseq = ival; + tplg_dbg("\t%s: %d", id, widget->subseq); + return 0; + } - if (strcmp(id, "invert") == 0) { - ival = snd_config_get_bool(n); - if (ival < 0) - return -EINVAL; + if (strcmp(id, "event_type") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; - widget->invert = ival; - tplg_dbg("\t%s: %d", id, widget->invert); - continue; - } + widget->event_type = ival; + tplg_dbg("\t%s: %d", id, widget->event_type); + return 0; + } - if (strcmp(id, "subseq") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; + if (strcmp(id, "event_flags") == 0) { + if (tplg_get_integer(n, &ival, 0)) + return -EINVAL; - widget->subseq = ival; - tplg_dbg("\t%s: %d", id, widget->subseq); - continue; - } + widget->event_flags = ival; + tplg_dbg("\t%s: %d", id, widget->event_flags); + return 0; + } - if (strcmp(id, "event_type") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; + if (strcmp(id, "enum") == 0) { + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_ENUM); + if (err < 0) + return err; - widget->event_type = ival; - tplg_dbg("\t%s: %d", id, widget->event_type); - continue; - } + return 0; + } - if (strcmp(id, "event_flags") == 0) { - if (tplg_get_integer(n, &ival, 0)) - return -EINVAL; + if (strcmp(id, "mixer") == 0) { + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_MIXER); + if (err < 0) + return err; - widget->event_flags = ival; - tplg_dbg("\t%s: %d", id, widget->event_flags); - continue; - } + return 0; + } - if (strcmp(id, "enum") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_ENUM); - if (err < 0) - return err; + if (strcmp(id, "bytes") == 0) { + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_BYTES); + if (err < 0) + return err; - continue; - } + return 0; + } - if (strcmp(id, "mixer") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_MIXER); - if (err < 0) - return err; + if (strcmp(id, "data") == 0) { + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); + if (err < 0) + return err; + return 0; + } - continue; - } + return 0; +} - if (strcmp(id, "bytes") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_BYTES); - if (err < 0) - return err; +/* DAPM Widget */ +int tplg_parse_dapm_widget(snd_tplg_t *tplg, + snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_dapm_widget *widget; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + int ret; - continue; - } + elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_DAPM_WIDGET); + if (!elem) + return -ENOMEM; - if (strcmp(id, "data") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); - if (err < 0) - return err; - continue; - } + tplg_dbg(" Widget: %s", elem->id); + + widget = elem->widget; + snd_strlcpy(widget->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + widget->size = elem->size; + + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + ret = tplg_parse_dapm_widget_param(n, widget, elem); + if (ret < 0) + return ret; } return 0; @@ -749,7 +761,7 @@ int tplg_add_route(snd_tplg_t *tplg, struct snd_tplg_graph_elem *t, int index) return 0; } -int tplg_add_graph_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_graph_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { struct snd_tplg_graph_template *gt = t->graph; int i, ret; @@ -763,7 +775,7 @@ int tplg_add_graph_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) return 0; } -int tplg_add_widget_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_widget_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { struct snd_tplg_widget_template *wt = t->widget; struct snd_soc_tplg_dapm_widget *w; @@ -1010,7 +1022,7 @@ int tplg_decode_dapm_widget(snd_tplg_t *tplg, } t.widget = wt; - err = snd_tplg_add_object(tplg, &t); + err = snd_tplg_add_element(tplg, &t); tplg_free(&heap); if (err < 0) return err; @@ -1061,5 +1073,5 @@ int tplg_decode_dapm_graph(snd_tplg_t *tplg, } t.graph = gt; - return snd_tplg_add_object(tplg, &t); + return snd_tplg_add_element(tplg, &t); } diff --git a/src/topology/data.c b/src/topology/data.c index 0546d63e4..9c79710c4 100644 --- a/src/topology/data.c +++ b/src/topology/data.c @@ -18,6 +18,7 @@ */ #include "list.h" +#include "local.h" #include "tplg_local.h" #include @@ -340,7 +341,7 @@ static int get_hex_num(const char *str) } /* get uuid from a string made by 16 characters separated by commas */ -static int get_uuid(const char *str, unsigned char *uuid_le) +int get_uuid(const char *str, unsigned char *uuid_le) { unsigned long int val; char *tmp, *s = NULL; @@ -465,8 +466,7 @@ static int copy_data_hex(char *data, int off, const char *str, int width) return 0; } -static int tplg_parse_data_hex(snd_config_t *cfg, struct tplg_elem *elem, - int width) +int tplg_parse_data_hex(snd_config_t *cfg, struct tplg_elem *elem, int width) { struct snd_soc_tplg_private *priv; const char *value = NULL; @@ -516,8 +516,7 @@ static int tplg_parse_data_hex(snd_config_t *cfg, struct tplg_elem *elem, } /* get the token integer value from its id */ -static int get_token_value(const char *token_id, - struct tplg_vendor_tokens *tokens) +int get_token_value(const char *token_id, struct tplg_vendor_tokens *tokens) { unsigned int i; @@ -588,93 +587,103 @@ unsigned int tplg_get_tuple_size(int type) } } -/* Add a tuples object to the private buffer of its parent data element */ -static int copy_tuples(struct tplg_elem *elem, - struct tplg_vendor_tuples *tuples, - struct tplg_vendor_tokens *tokens) +int scan_tuple_set(struct tplg_elem *elem, struct tplg_tuple_set *tuple_set, + struct tplg_vendor_tokens *tokens, int size) { struct snd_soc_tplg_private *priv = elem->data, *priv2; - struct tplg_tuple_set *tuple_set; - struct tplg_tuple *tuple; - struct snd_soc_tplg_vendor_array *array; - struct snd_soc_tplg_vendor_uuid_elem *uuid; struct snd_soc_tplg_vendor_string_elem *string; struct snd_soc_tplg_vendor_value_elem *value; - int set_size, size, off; - unsigned int i, j; + struct snd_soc_tplg_vendor_uuid_elem *uuid; + struct snd_soc_tplg_vendor_array *array; + struct tplg_tuple *tuple; + int set_size, off; + unsigned int j; int token_val; - size = priv ? priv->size : 0; /* original private data size */ - /* scan each tuples set (one set per type) */ - for (i = 0; i < tuples->num_sets ; i++) { - tuple_set = tuples->set[i]; - set_size = sizeof(struct snd_soc_tplg_vendor_array) - + tplg_get_tuple_size(tuple_set->type) - * tuple_set->num_tuples; - size += set_size; - if (size > TPLG_MAX_PRIV_SIZE) { - SNDERR("data too big %d", size); - return -EINVAL; - } + set_size = sizeof(*array) + tplg_get_tuple_size(tuple_set->type) * tuple_set->num_tuples; + size += set_size; + if (size > TPLG_MAX_PRIV_SIZE) { + SNDERR("data too big %d", size); + return -EINVAL; + } - if (priv != NULL) { - priv2 = realloc(priv, sizeof(*priv) + size); - if (priv2 == NULL) { - free(priv); - priv = NULL; - } else { - priv = priv2; - } + if (priv != NULL) { + priv2 = realloc(priv, sizeof(*priv) + size); + if (priv2 == NULL) { + free(priv); + priv = NULL; } else { - priv = calloc(1, sizeof(*priv) + size); + priv = priv2; } - if (!priv) - return -ENOMEM; + } else { + priv = calloc(1, sizeof(*priv) + size); + } + if (!priv) + return -ENOMEM; - off = priv->size; - priv->size = size; /* update private data size */ - elem->data = priv; - - array = (struct snd_soc_tplg_vendor_array *)(priv->data + off); - memset(array, 0, set_size); - array->size = set_size; - array->type = tuple_set->type; - array->num_elems = tuple_set->num_tuples; - - /* fill the private data buffer */ - for (j = 0; j < tuple_set->num_tuples; j++) { - tuple = &tuple_set->tuple[j]; - token_val = get_token_value(tuple->token, tokens); - if (token_val < 0) - return -EINVAL; + off = priv->size; + priv->size = size; /* update private data size */ + elem->data = priv; - switch (tuple_set->type) { - case SND_SOC_TPLG_TUPLE_TYPE_UUID: - uuid = &array->uuid[j]; - uuid->token = token_val; - memcpy(uuid->uuid, tuple->uuid, 16); - break; - - case SND_SOC_TPLG_TUPLE_TYPE_STRING: - string = &array->string[j]; - string->token = token_val; - snd_strlcpy(string->string, tuple->string, - SNDRV_CTL_ELEM_ID_NAME_MAXLEN); - break; - - default: - value = &array->value[j]; - value->token = token_val; - value->value = tuple->value; - break; - } + array = (struct snd_soc_tplg_vendor_array *)(priv->data + off); + memset(array, 0, set_size); + array->size = set_size; + array->type = tuple_set->type; + array->num_elems = tuple_set->num_tuples; + + /* fill the private data buffer */ + for (j = 0; j < tuple_set->num_tuples; j++) { + tuple = &tuple_set->tuple[j]; + token_val = get_token_value(tuple->token, tokens); + if (token_val < 0) + return -EINVAL; + + switch (tuple_set->type) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + uuid = &array->uuid[j]; + uuid->token = token_val; + memcpy(uuid->uuid, tuple->uuid, 16); + break; + + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + string = &array->string[j]; + string->token = token_val; + snd_strlcpy(string->string, tuple->string, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + break; + + default: + value = &array->value[j]; + value->token = token_val; + value->value = tuple->value; + break; } } return 0; } +/* Add a tuples to the private buffer of its parent data element */ +static int copy_tuples(struct tplg_elem *elem, + struct tplg_vendor_tuples *tuples, + struct tplg_vendor_tokens *tokens) +{ + struct snd_soc_tplg_private *priv = elem->data; + unsigned int i; + int size, ret; + + /* scan each tuples set (one set per type) */ + for (i = 0; i < tuples->num_sets ; i++) { + size = priv ? priv->size : 0; /* original private data size */ + ret = scan_tuple_set(elem, tuples->set[i], tokens, size); + if (ret < 0) + return ret; + priv = elem->data; + } + + return 0; +} + /* build a data element from its tuples */ static int build_tuples(snd_tplg_t *tplg, struct tplg_elem *elem) { @@ -708,7 +717,7 @@ static int build_tuples(snd_tplg_t *tplg, struct tplg_elem *elem) return -EINVAL; } - /* a data object can have multiple tuples objects */ + /* a data element can have multiple tuples */ err = copy_tuples(elem, tuples->tuples, tokens->tokens); if (err < 0) return err; @@ -756,7 +765,7 @@ static struct tuple_type tuple_types[] = { }, }; -static int get_tuple_type(const char *name) +int get_tuple_type(const char *name) { struct tuple_type *t; unsigned int i; @@ -1550,7 +1559,7 @@ int tplg_copy_data(snd_tplg_t *tplg, struct tplg_elem *elem, return 0; } -/* check data objects and build those with tuples */ +/* check data elements and build those with tuples */ int tplg_build_data(snd_tplg_t *tplg) { struct list_head *base, *pos; diff --git a/src/topology/elem.c b/src/topology/elem.c index cbd7f4b63..3b2d4a3d9 100644 --- a/src/topology/elem.c +++ b/src/topology/elem.c @@ -19,6 +19,7 @@ #include "list.h" #include "tplg_local.h" +#include "tplg2_local.h" struct tplg_table tplg_table[] = { { @@ -226,6 +227,22 @@ struct tplg_table tplg_table[] = { .enew = 1, .parse = tplg_parse_hw_config, .save = tplg_save_hw_config, + }, + { + .name = "Class Definition", + .id = "Class", + .loff = offsetof(snd_tplg_t, class_list), + .type = SND_TPLG_TYPE_CLASS, + .enew = 1, + .parse = tplg_define_classes, + }, + { + .name = "New Object", + .id = "Object", + .loff = offsetof(snd_tplg_t, object_list), + .type = SND_TPLG_TYPE_OBJECT, + .enew = 1, + .parse = tplg_create_objects, } }; @@ -303,9 +320,15 @@ void tplg_elem_free(struct tplg_elem *elem) { list_del(&elem->list); + if (elem->type == SND_TPLG_TYPE_CLASS) + tplg2_free_elem_class(elem); + + if (elem->type == SND_TPLG_TYPE_OBJECT) + tplg2_free_elem_object(elem); + tplg_ref_free_list(&elem->ref_list); - /* free struct snd_tplg_ object, + /* free struct snd_tplg_ element, * the union pointers share the same address */ if (elem->obj) { diff --git a/src/topology/object.c b/src/topology/object.c new file mode 100644 index 000000000..67c429988 --- /dev/null +++ b/src/topology/object.c @@ -0,0 +1,1347 @@ +/* + Copyright(c) 2020-2021 Intel Corporation + All rights reserved. + + 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.1 of + the License, or (at your option) any later version. + + This program 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. + + Author: Ranjani Sridharan +*/ +#include "list.h" +#include "tplg_local.h" +#include "tplg2_local.h" +#include + +static bool tplg_object_unique_attribute_match(struct tplg_object *object, char *input) +{ + struct tplg_attribute *attr; + struct list_head *pos; + bool found = false; + + /* find attribute with TPLG_CLASS_ATTRIBUTE_MASK_UNIQUE mask */ + list_for_each(pos, &object->attribute_list) { + attr = list_entry(pos, struct tplg_attribute, list); + + if (attr->constraint.mask & TPLG_CLASS_ATTRIBUTE_MASK_UNIQUE) { + found = true; + break; + } + } + + if (!found) + return false; + + /* check if the value matches based on type */ + switch (attr->type) { + case SND_CONFIG_TYPE_INTEGER: + if (attr->value.integer == atoi(input)) + return true; + break; + case SND_CONFIG_TYPE_STRING: + if (!strcmp(attr->value.string, input)) + return true; + break; + default: + break; + } + + return false; +} + +/* look up object based on class type and input value for the unique attribute */ +struct tplg_object *tplg_object_elem_lookup(snd_tplg_t *tplg, const char *class_name, + char *input) +{ + struct list_head *pos; + struct tplg_elem *elem; + + if (!class_name) + return NULL; + + list_for_each(pos, &tplg->object_list) { + struct tplg_object *object; + + elem = list_entry(pos, struct tplg_elem, list); + object = elem->object; + + /* check if class_name natches */ + if (strcmp(object->class_name, class_name)) + continue; + + if (tplg_object_unique_attribute_match(object, input)) + return elem->object; + } + + return NULL; +} + +/* look up object based on class type and unique attribute value in a list */ +struct tplg_object *tplg_object_lookup_in_list(struct list_head *list, const char *class_name, + char *input) +{ + struct list_head *pos; + + if (!class_name) + return NULL; + + list_for_each(pos, list) { + struct tplg_object *object = list_entry(pos, struct tplg_object, list); + + /* check if class_name natches */ + if (strcmp(object->class_name, class_name)) + continue; + + if (tplg_object_unique_attribute_match(object, input)) + return object; + } + + return NULL; +} + +/* Set child object attributes */ +static int tplg_set_child_attributes(snd_tplg_t *tplg, snd_config_t *cfg, + struct tplg_object *object) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int ret; + + snd_config_for_each(i, next, cfg) { + snd_config_iterator_t first, second; + snd_config_t *first_cfg, *second_cfg; + struct tplg_elem *class_elem; + struct tplg_object *child; + const char *class_name, *index_str, *attribute_name; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &class_name) < 0) + continue; + + if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) + continue; + + /* check if it is a valid class name */ + class_elem = tplg_elem_lookup(&tplg->class_list, class_name, + SND_TPLG_TYPE_CLASS, SND_TPLG_INDEX_ALL); + if (!class_elem) + continue; + + /* get index */ + first = snd_config_iterator_first(n); + first_cfg = snd_config_iterator_entry(first); + if (snd_config_get_id(first_cfg, &index_str) < 0) + continue; + + if (snd_config_get_type(first_cfg) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("No attribute name for child %s.%s\n", class_name, index_str); + return -EINVAL; + } + + /* the next node can either be an attribute name or a child class */ + second = snd_config_iterator_first(first_cfg); + second_cfg = snd_config_iterator_entry(second); + if (snd_config_get_id(second_cfg, &attribute_name) < 0) + continue; + + /* get object of type class_name and unique attribute value */ + child = tplg_object_lookup_in_list(&object->object_list, class_name, (char *)index_str); + if (!child) { + SNDERR("No child %s.%s found for object %s\n", + class_name, index_str, object->name); + return -EINVAL; + } + + /* + * if the second conf node is an attribute name, set the value but do not + * override the object value if already set. + */ + if (snd_config_get_type(second_cfg) != SND_CONFIG_TYPE_COMPOUND) { + ret = tplg_parse_attribute_value(second_cfg, &child->attribute_list, false); + + if (ret < 0) { + SNDERR("Failed to set attribute for object %s\n", object->name); + return ret; + } + continue; + } + + /* otherwise pass it down to the child object */ + ret = tplg_set_child_attributes(tplg, first_cfg, child); + if (ret < 0) + return ret; + } + + return 0; +} + +/* Process the attribute values provided during object instantiation */ +static int tplg_process_attributes(snd_config_t *cfg, struct tplg_object *object) +{ + snd_config_iterator_t i, next; + struct list_head *pos; + snd_config_t *n; + const char *id; + int ret; + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + /* copy new value based on type */ + if (!strcmp(id, attr->name)) { + /* cannot update immutable attributes */ + if (attr->constraint.mask & TPLG_CLASS_ATTRIBUTE_MASK_IMMUTABLE) { + SNDERR("Warning: cannot update immutable attribute: %s for object %s\n", + attr->name, object->name); + continue; + } + + ret = tplg_parse_attribute_value(n, &object->attribute_list, true); + if (ret < 0) { + SNDERR("Error parsing attribute %s value: %d\n", + attr->name, ret); + return ret; + } + + attr->found = true; + break; + } + } + } + + return 0; +} + +static int create_child_object_instance(snd_tplg_t *tplg, snd_config_t *cfg, + struct tplg_object *parent, struct list_head *list, + struct tplg_elem *class_elem) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + + snd_config_for_each(i, next, cfg) { + struct tplg_object *object; + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + object = tplg_create_object(tplg, n, class_elem->class, parent, list); + if (!object) { + SNDERR("Error creating child %s for parent %s\n", id, parent->name); + return -EINVAL; + } + } + + return 0; +} + +/* create child object */ +int tplg_create_child_object_type(snd_tplg_t *tplg, snd_config_t *cfg, + struct tplg_object *parent, struct list_head *list) +{ + snd_config_iterator_t i, next; + struct tplg_elem *class_elem; + snd_config_t *n; + const char *id; + int ret; + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* check if it is a valid object */ + class_elem = tplg_elem_lookup(&tplg->class_list, id, + SND_TPLG_TYPE_CLASS, SND_TPLG_INDEX_ALL); + + if (!class_elem) + continue; + + ret = create_child_object_instance(tplg, n, parent, list, class_elem); + if (ret < 0) { + SNDERR("Error creating %s type child object for parent %s\n", + class_elem->id, parent->name); + return ret; + } + } + + return 0; +} + +/* create child objects that are part of the parent object instance */ +static int tplg_create_child_objects(snd_tplg_t *tplg, snd_config_t *cfg, + struct tplg_object *parent) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + const char *id; + int ret; + + 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, "Object")) + continue; + + /* create object */ + ret = tplg_create_child_object_type(tplg, n, parent, &parent->object_list); + if (ret < 0) { + SNDERR("Error creating child objects for %s\n", parent->name); + return ret; + } + } + + return 0; +} + +/* + * Child objects could have arguments inherited from the parent. Update the name now that the + * parent has been instantiated and values updated. + */ +static int tplg_update_object_name_from_args(struct tplg_object *object) +{ + struct list_head *pos; + char string[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int ret; + + snprintf(string, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s", object->class_name); + + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + char new_str[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + if (attr->param_type != TPLG_CLASS_PARAM_TYPE_ARGUMENT) + continue; + + switch (attr->type) { + case SND_CONFIG_TYPE_INTEGER: + ret = snprintf(new_str, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s.%ld", + string, attr->value.integer); + if (ret > SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { + SNDERR("Object name too long for %s\n", object->name); + return -EINVAL; + } + snd_strlcpy(string, new_str, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + break; + case SND_CONFIG_TYPE_STRING: + ret = snprintf(new_str, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s.%s", + string, attr->value.string); + if (ret > SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { + SNDERR("Object name too long for %s\n", object->name); + return -EINVAL; + } + snd_strlcpy(string, new_str, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + break; + default: + break; + } + } + + snd_strlcpy(object->name, string, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + return 0; +} + +/* update attributes inherited from parent */ +static int tplg_update_attributes_from_parent(struct tplg_object *object, + struct tplg_object *ref_object) +{ + struct list_head *pos, *pos1; + + /* update object attribute values from reference object */ + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (attr->found) + continue; + + list_for_each(pos1, &ref_object->attribute_list) { + struct tplg_attribute *ref_attr; + + ref_attr = list_entry(pos1, struct tplg_attribute, list); + if (!ref_attr->found) + continue; + + if (!strcmp(attr->name, ref_attr->name)) { + switch (ref_attr->type) { + case SND_CONFIG_TYPE_INTEGER: + attr->value.integer = ref_attr->value.integer; + attr->type = ref_attr->type; + break; + case SND_CONFIG_TYPE_INTEGER64: + attr->value.integer64 = ref_attr->value.integer64; + attr->type = ref_attr->type; + break; + case SND_CONFIG_TYPE_STRING: + snd_strlcpy(attr->value.string, + ref_attr->value.string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + attr->type = ref_attr->type; + break; + case SND_CONFIG_TYPE_REAL: + attr->value.d = ref_attr->value.d; + attr->type = ref_attr->type; + break; + default: + SNDERR("Unsupported type %d for attribute %s\n", + attr->type, attr->name); + return -EINVAL; + } + attr->found = true; + } + } + } + + return 0; +} + +/* Propdagate the updated attribute values to child o bjects */ +static int tplg_process_child_objects(struct tplg_object *parent) +{ + struct list_head *pos; + int ret; + + list_for_each(pos, &parent->object_list) { + struct tplg_object *object = list_entry(pos, struct tplg_object, list); + + /* update attribute values inherited from parent */ + ret = tplg_update_attributes_from_parent(object, parent); + if (ret < 0) { + SNDERR("failed to update arguments for %s\n", object->name); + return ret; + } + + /* update object name after args update */ + ret = tplg_update_object_name_from_args(object); + if (ret < 0) + return ret; + + /* update the object elem ID as well */ + snd_strlcpy(object->elem->id, object->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + /* now process its child objects */ + ret = tplg_process_child_objects(object); + if (ret < 0) { + SNDERR("Cannot update child object for %s\n", object->name); + } + } + + return 0; +} + +/* copy the preset attribute values and constraints */ +static int tplg_copy_attribute(struct tplg_attribute *attr, struct tplg_attribute *ref_attr) +{ + struct list_head *pos1; + + snd_strlcpy(attr->name, ref_attr->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + snd_strlcpy(attr->token_ref, ref_attr->token_ref, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + attr->found = ref_attr->found; + attr->param_type = ref_attr->param_type; + attr->cfg = ref_attr->cfg; + attr->type = ref_attr->type; + + /* copy value */ + if (ref_attr->found) { + switch (ref_attr->type) { + case SND_CONFIG_TYPE_INTEGER: + attr->value.integer = ref_attr->value.integer; + break; + case SND_CONFIG_TYPE_INTEGER64: + attr->value.integer64 = ref_attr->value.integer64; + break; + case SND_CONFIG_TYPE_STRING: + snd_strlcpy(attr->value.string, ref_attr->value.string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + break; + case SND_CONFIG_TYPE_REAL: + { + attr->value.d = ref_attr->value.d; + break; + } + case SND_CONFIG_TYPE_COMPOUND: + break; + default: + SNDERR("Unsupported type %d for attribute %s\n", attr->type, attr->name); + return -EINVAL; + } + } + + /* copy attribute constraints */ + INIT_LIST_HEAD(&attr->constraint.value_list); + attr->constraint.value_ref = ref_attr->constraint.value_ref; + list_for_each(pos1, &ref_attr->constraint.value_list) { + struct tplg_attribute_ref *ref; + struct tplg_attribute_ref *new_ref = calloc(1, sizeof(*new_ref)); + + ref = list_entry(pos1, struct tplg_attribute_ref, list); + memcpy(new_ref, ref, sizeof(*ref)); + list_add(&new_ref->list, &attr->constraint.value_list); + } + attr->constraint.mask = ref_attr->constraint.mask; + attr->constraint.min = INT_MIN; + attr->constraint.max = INT_MAX; + + return 0; +} + +/* find the attribute with the mask TPLG_CLASS_ATTRIBUTE_MASK_UNIQUE and set the value */ +static int tplg_object_set_unique_attribute(struct tplg_object *object, snd_config_t *cfg) +{ + struct tplg_attribute *attr; + struct list_head *pos; + const char *id; + bool found = false; + + list_for_each(pos, &object->attribute_list) { + attr = list_entry(pos, struct tplg_attribute, list); + + if (attr->constraint.mask & TPLG_CLASS_ATTRIBUTE_MASK_UNIQUE) { + found = true; + break; + } + } + + /* no unique attribute for object */ + if (!found) { + SNDERR("No unique attribute set for object %s\n", object->name); + return -EINVAL; + } + + /* no need to check return value as it is already checked in tplg_create_object() */ + snd_config_get_id(cfg, &id); + + if (id[0] >= '0' && id[0] <= '9') { + attr->value.integer = atoi(id); + attr->type = SND_CONFIG_TYPE_INTEGER; + } else { + snd_strlcpy(attr->value.string, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + attr->type = SND_CONFIG_TYPE_STRING; + } + + attr->found = true; + + return 0; +} + +static int tplg_object_attributes_sanity_check(struct tplg_object *object) +{ + struct list_head *pos; + + /* sanity check for object attributes */ + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + /* check if mandatory and value provided */ + if ((attr->constraint.mask & TPLG_CLASS_ATTRIBUTE_MASK_MANDATORY) && + !(attr->constraint.mask & TPLG_CLASS_ATTRIBUTE_MASK_IMMUTABLE) && + !attr->found) { + SNDERR("Mandatory attribute %s not found for object %s\n", attr->name, + object->name); + return -EINVAL; + } + + /* translate string values to integer if needed to be added to private data */ + switch (attr->type) { + case SND_CONFIG_TYPE_STRING: + { + struct list_head *pos1; + + /* skip attributes with no pre-defined valid values */ + if (list_empty(&attr->constraint.value_list)) + continue; + + /* Translate the string value to integer if needed */ + list_for_each(pos1, &attr->constraint.value_list) { + struct tplg_attribute_ref *v; + + v = list_entry(pos1, struct tplg_attribute_ref, list); + + if (!strcmp(attr->value.string, v->string)) { + if (v->value != -EINVAL) { + attr->value.integer = v->value; + attr->type = SND_CONFIG_TYPE_INTEGER; + } + break; + } + } + break; + } + default: + break; + } + } + + /* recursively check all child objects */ + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + int ret; + + ret = tplg_object_attributes_sanity_check(child); + if (ret < 0) + return ret; + } + + return 0; +} + +/* Copy object from class definition and create the topology element for the newly copied object */ +static int tplg_copy_object(snd_tplg_t *tplg, struct tplg_object *src, struct tplg_object *dest, + struct list_head *list) +{ + struct tplg_elem *elem; + struct list_head *pos; + int ret; + + if (!src || !dest) { + SNDERR("Invalid src/dest object\n"); + return -EINVAL; + } + + dest->num_args = src->num_args; + snd_strlcpy(dest->name, src->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + snd_strlcpy(dest->class_name, src->class_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + dest->type = src->type; + dest->cfg = src->cfg; + INIT_LIST_HEAD(&dest->tuple_set_list); + INIT_LIST_HEAD(&dest->attribute_list); + INIT_LIST_HEAD(&dest->object_list); + + /* copy attributes */ + list_for_each(pos, &src->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + struct tplg_attribute *new_attr = calloc(1, sizeof(*attr)); + + if (!new_attr) + return -ENOMEM; + + ret = tplg_copy_attribute(new_attr, attr); + if (ret < 0) { + SNDERR("Error copying attribute %s\n", attr->name); + free(new_attr); + return -ENOMEM; + } + list_add_tail(&new_attr->list, &dest->attribute_list); + } + + switch(src->type) { + case SND_TPLG_CLASS_TYPE_COMPONENT: + { + struct tplg_comp_object *dest_comp_object = &dest->object_type.component; + struct tplg_comp_object *src_comp_object = &src->object_type.component; + + memcpy(dest_comp_object, src_comp_object, sizeof(*dest_comp_object)); + break; + } + default: + break; + } + + /* copy its child objects */ + list_for_each(pos, &src->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + struct tplg_object *new_child = calloc(1, sizeof(*new_child)); + + ret = tplg_copy_object(tplg, child, new_child, &dest->object_list); + if (ret < 0) { + SNDERR("error copying child object %s\n", child->name); + return ret; + } + } + + /* create tplg elem of type SND_TPLG_TYPE_OBJECT */ + elem = tplg_elem_new_common(tplg, NULL, dest->name, SND_TPLG_TYPE_OBJECT); + if (!elem) + return -ENOMEM; + elem->object = dest; + dest->elem = elem; + + list_add_tail(&dest->list, list); + return 0; +} + +/* class definitions may have pre-defined objects. Copy these into the object */ +static int tplg_copy_child_objects(snd_tplg_t *tplg, struct tplg_class *class, + struct tplg_object *object) +{ + struct list_head *pos; + int ret; + + /* copy child objects */ + list_for_each(pos, &class->object_list) { + struct tplg_object *obj = list_entry(pos, struct tplg_object, list); + struct tplg_object *new_obj = calloc(1, sizeof(*obj)); + + ret = tplg_copy_object(tplg, obj, new_obj, &object->object_list); + if (ret < 0) { + free(new_obj); + return ret; + } + } + + return 0; +} + +/* + * Create an object of class "class" by copying the attribute list, number of arguments + * and default attribute values from the class definition. Objects can also be given + * new values during instantiation and these will override the default values set in + * the class definition + */ +struct tplg_object * +tplg_create_object(snd_tplg_t *tplg, snd_config_t *cfg, struct tplg_class *class, + struct tplg_object *parent ATTRIBUTE_UNUSED, struct list_head *list) +{ + struct tplg_object *object; + struct tplg_elem *elem; + struct list_head *pos; + const char *id; + char object_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int ret; + + if (!class) { + SNDERR("Invalid class elem for object\n"); + return NULL; + } + + /* get object index */ + if (snd_config_get_id(cfg, &id) < 0) + return NULL; + + ret = snprintf(object_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s.%s", class->name, id); + if (ret > SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + SNDERR("Warning: object name %s truncated to %d characters\n", object_name, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + /* create and initialize object type element */ + elem = tplg_elem_new_common(tplg, NULL, object_name, SND_TPLG_TYPE_OBJECT); + if (!elem) { + SNDERR("Failed to create tplg elem for %s\n", object_name); + return NULL; + } + + object = calloc(1, sizeof(*object)); + if (!object) { + return NULL; + } + + object->cfg = cfg; + object->elem = elem; + object->num_args = class->num_args; + snd_strlcpy(object->name, object_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + snd_strlcpy(object->class_name, class->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + object->type = class->type; + INIT_LIST_HEAD(&object->tuple_set_list); + INIT_LIST_HEAD(&object->attribute_list); + INIT_LIST_HEAD(&object->object_list); + elem->object = object; + + /* copy attributes from class */ + list_for_each(pos, &class->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + struct tplg_attribute *new_attr = calloc(1, sizeof(*attr)); + + if (!new_attr) + return NULL; + + ret = tplg_copy_attribute(new_attr, attr); + if (ret < 0) { + SNDERR("Error copying attribute %s\n", attr->name); + free(new_attr); + return NULL; + } + list_add_tail(&new_attr->list, &object->attribute_list); + } + + /* set unique attribute for object */ + ret = tplg_object_set_unique_attribute(object, cfg); + if (ret < 0) + return NULL; + + /* process object attribute values */ + ret = tplg_process_attributes(cfg, object); + if (ret < 0) { + SNDERR("Failed to process attributes for %s\n", object->name); + return NULL; + } + + /* sanity check */ + switch(object->type) { + case SND_TPLG_CLASS_TYPE_PIPELINE: + ret = tplg_create_pipeline_object(class, object); + if (ret < 0) { + SNDERR("Failed to create pipeline object for %s\n", object->name); + return NULL; + } + break; + case SND_TPLG_CLASS_TYPE_DAI: + ret = tplg_create_dai_object(class, object); + if (ret < 0) { + SNDERR("Failed to create DAI object for %s\n", object->name); + return NULL; + } + break; + case SND_TPLG_CLASS_TYPE_COMPONENT: + ret = tplg_create_component_object(object); + if (ret < 0) { + SNDERR("Failed to create component object for %s\n", object->name); + return NULL; + } + break; + default: + break; + } + + /* now copy child objects */ + ret = tplg_copy_child_objects(tplg, class, object); + if (ret < 0) { + SNDERR("Failed to create DAI object for %s\n", object->name); + return NULL; + } + + /* create child objects that are part of the object instance */ + ret = tplg_create_child_objects(tplg, cfg, object); + if (ret < 0) { + SNDERR("failed to create child objects for %s\n", object->name); + return NULL; + } + + /* pass down the object attributes to its child objects */ + ret = tplg_process_child_objects(object); + if (ret < 0) { + SNDERR("failed to create child objects for %s\n", object->name); + return NULL; + } + + /* process child object attributes set explictly in the parent object */ + ret = tplg_set_child_attributes(tplg, cfg, object); + if (ret < 0) { + SNDERR("failed to set child attributes for %s\n", object->name); + return NULL; + } + + /* update automatic attributes in object */ + ret = tplg_update_automatic_attributes(tplg, object, parent); + if (ret < 0) { + SNDERR("failed to update automatic attributes for %s\n", object->name); + return NULL; + } + + if (list) + list_add_tail(&object->list, list); + + return object; +} + +static int tplg2_get_bool(struct tplg_attribute *attr) +{ + if (attr->type != SND_CONFIG_TYPE_INTEGER) + return -EINVAL; + + if (attr->value.integer != 0 && attr->value.integer != 1) + return -EINVAL; + + return attr->value.integer; +} + +static int tplg_get_object_tuple_set(struct tplg_object *object, struct tplg_tuple_set **out, + const char *token_ref) +{ + struct list_head *pos, *_pos; + struct tplg_tuple_set *set; + const char *type; + char *tokenref_str; + int len, set_type; + int size; + + /* get tuple set type */ + type = strchr(token_ref, '.'); + if (!type) { + SNDERR("No type given for tuple set: '%s' in object: '%s'\n", + token_ref, object->name); + return -EINVAL; + } + + set_type = get_tuple_type(type + 1); + if (set_type < 0) { + SNDERR("Invalid type for tuple set: '%s' in object: '%s'\n", + token_ref, object->name); + return -EINVAL; + } + + /* get tuple token ref name */ + len = strlen(token_ref) - strlen(type) + 1; + tokenref_str = calloc(1, len); + snd_strlcpy(tokenref_str, token_ref, len); + + /* realloc set if set is found */ + list_for_each_safe(pos, _pos, &object->tuple_set_list) { + struct tplg_tuple_set *set2; + + set = list_entry(pos, struct tplg_tuple_set, list); + + if (set->type == (unsigned int)set_type && !(strcmp(set->token_ref, tokenref_str))) { + + set->num_tuples++; + size = sizeof(*set) + set->num_tuples * sizeof(struct tplg_tuple); + set2 = realloc(set, size); + if (!set2) + return -ENOMEM; + list_del(&set->list); + + set = set2; + list_add_tail(&set->list, &object->tuple_set_list); + *out = set; + return 0; + } + } + + /* else create a new set and add it to the object's tuple_set_list */ + size = sizeof(*set) + sizeof(struct tplg_tuple); + set = calloc(1, size); + set->num_tuples = 1; + set->type = set_type; + snd_strlcpy(set->token_ref, tokenref_str, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + list_add_tail(&set->list, &object->tuple_set_list); + *out = set; + + return 0; +} + +static int tplg_build_object_tuple_set_from_attributes(struct tplg_object *object, + struct tplg_attribute *attr) +{ + struct tplg_tuple_set *set; + struct tplg_tuple *tuple; + struct list_head *pos; + int ret; + + /* get tuple set if it exists already or create one */ + ret = tplg_get_object_tuple_set(object, &set, attr->token_ref); + if (ret < 0) { + SNDERR("Invalid tuple set for '%s'\n", object->name); + return ret; + } + + /* update set with new tuple */ + tuple = &set->tuple[set->num_tuples - 1]; + snd_strlcpy(tuple->token, attr->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + switch (set->type) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + { + const char *value; + + if (snd_config_get_string(attr->cfg, &value) < 0) + break; + if (get_uuid(value, tuple->uuid) < 0) { + SNDERR("failed to get uuid from string %s\n", value); + return -EINVAL; + } + tplg_dbg("\t\tuuid string %s ", value); + tplg_dbg("\t\t%s = 0x%x", tuple->token, tuple->uuid); + break; + } + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + snd_strlcpy(tuple->string, attr->value.string, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + tplg_dbg("\t\t%s = %s", tuple->token, tuple->string); + break; + + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + { + int ret = tplg2_get_bool(attr); + + if (ret < 0) { + SNDERR("Invalid value for tuple %s\n", tuple->token); + return tuple->value; + } + tuple->value = ret; + tplg_dbg("\t\t%s = %d", tuple->token, tuple->value); + break; + } + case SND_SOC_TPLG_TUPLE_TYPE_BYTE: + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + { + unsigned int tuple_val = 0; + + switch(attr->type) { + case SND_CONFIG_TYPE_STRING: + if (!attr->constraint.value_ref) { + SNDERR("Invalid tuple value type for %s\n", tuple->token); + return -EINVAL; + } + + /* convert attribute string values to corresponding integer value */ + list_for_each(pos, &attr->constraint.value_list) { + struct tplg_attribute_ref *v; + + v = list_entry(pos, struct tplg_attribute_ref, list); + if (!strcmp(attr->value.string, v->string)) + if (v->value != -EINVAL) + tuple_val = v->value; + } + break; + case SND_CONFIG_TYPE_INTEGER: + tuple_val = attr->value.integer; + break; + case SND_CONFIG_TYPE_INTEGER64: + tuple_val = attr->value.integer64; + break; + default: + SNDERR("Invalid value type %d for tuple %s for object %s \n", attr->type, + tuple->token, object->name); + return -EINVAL; + } + + if ((set->type == SND_SOC_TPLG_TUPLE_TYPE_WORD + && tuple_val > UINT_MAX) + || (set->type == SND_SOC_TPLG_TUPLE_TYPE_SHORT + && tuple_val > USHRT_MAX) + || (set->type == SND_SOC_TPLG_TUPLE_TYPE_BYTE + && tuple_val > UCHAR_MAX)) { + SNDERR("tuple %s: invalid value", tuple->token); + return -EINVAL; + } + + tuple->value = tuple_val; + tplg_dbg("\t\t%s = 0x%x", tuple->token, tuple->value); + break; + } + default: + break; + } + + return 0; +} + +/* Build tuple sets from object attributes */ +int tplg_build_object_tuple_sets(struct tplg_object *object) +{ + struct list_head *pos; + int ret = 0; + + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (attr->constraint.mask & TPLG_CLASS_ATTRIBUTE_MASK_DEPRECATED) { + if (attr->found) + SNDERR("Warning: attibute %s decprecated\n", attr->name); + continue; + } + + if (strcmp(attr->token_ref, "")) { + if (!attr->found) + continue; + + ret = tplg_build_object_tuple_set_from_attributes(object, attr); + if (ret < 0) + return ret; + } + } + + return ret; +} + +int tplg_build_private_data(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct snd_soc_tplg_private *priv; + struct tplg_elem *data_elem; + struct list_head *pos; + int ret; + + /* build tuple sets for object */ + ret = tplg_build_object_tuple_sets(object); + if (ret < 0) + return ret; + + data_elem = tplg_elem_lookup(&tplg->pdata_list, object->name, + SND_TPLG_TYPE_DATA, SND_TPLG_INDEX_ALL); + if(!data_elem) + return 0; + + priv = data_elem->data; + + /* build link private data from tuple sets */ + list_for_each(pos, &object->tuple_set_list) { + struct tplg_tuple_set *set = list_entry(pos, struct tplg_tuple_set, list); + struct tplg_elem *token_elem; + + if (!set->token_ref) { + SNDERR("No valid token ref for tuple set type %d\n", set->type); + return -EINVAL; + } + + /* get reference token elem */ + token_elem = tplg_elem_lookup(&tplg->token_list, set->token_ref, + SND_TPLG_TYPE_TOKEN, SND_TPLG_INDEX_ALL); + if (!token_elem) { + SNDERR("No valid tokens for ref %s\n", set->token_ref); + return -EINVAL; + } + + ret = scan_tuple_set(data_elem, set, token_elem->tokens, priv ? priv->size : 0); + if (ret < 0) + return ret; + + /* priv gets modified while scanning new sets */ + priv = data_elem->data; + } + + tplg_dbg("Object %s built", object->name); + + return 0; +} + +static int tplg_build_manifest_object(snd_tplg_t *tplg, struct tplg_object *object) { + struct snd_soc_tplg_manifest *manifest; + struct tplg_elem *m_elem; + struct list_head *pos; + int ret; + + if (!list_empty(&tplg->manifest_list)) { + SNDERR("Manifest data already exists"); + return -EINVAL; + } + + m_elem = tplg_elem_new_common(tplg, NULL, object->name, SND_TPLG_TYPE_MANIFEST); + if (!m_elem) + return -ENOMEM; + + manifest = m_elem->manifest; + manifest->size = m_elem->size; + + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + + if (!object->cfg) + continue; + + if (!strcmp(child->class_name, "data")) { + struct tplg_attribute *name; + + name = tplg_get_attribute_by_name(&object->attribute_list, "name"); + + ret = tplg_ref_add(m_elem, SND_TPLG_TYPE_DATA, name->value.string); + if (ret < 0) { + SNDERR("failed to add data elem %s to manifest elem %s\n", + name->value.string, m_elem->id); + return ret; + } + } + } + + tplg_dbg(" Manifest: %s", m_elem->id); + + return 0; +} + +static int tplg_build_data_object(snd_tplg_t *tplg, struct tplg_object *object) { + struct tplg_attribute *bytes, *name; + struct tplg_elem *data_elem; + int ret; + + name = tplg_get_attribute_by_name(&object->attribute_list, "name"); + if (!name) { + SNDERR("invalid name for data object: %s", object->name); + return -EINVAL; + } + + /* check if data elem exists already */ + data_elem = tplg_elem_lookup(&tplg->widget_list, name->value.string, + SND_TPLG_TYPE_DATA, SND_TPLG_INDEX_ALL); + if (!data_elem) { + /* create data elem */ + data_elem = tplg_elem_new_common(tplg, NULL, name->value.string, + SND_TPLG_TYPE_DATA); + if (!data_elem) { + SNDERR("failed to create data elem for %s\n", object->name); + return -EINVAL; + } + } + + bytes = tplg_get_attribute_by_name(&object->attribute_list, "bytes"); + + if (!bytes || !bytes->cfg) + return 0; + + ret = tplg_parse_data_hex(bytes->cfg, data_elem, 1); + if (ret < 0) + SNDERR("failed to parse byte for data: %s", object->name); + + tplg_dbg("data: %s", name->value.string); + + return ret; +} + + +static int tplg_build_base_object(snd_tplg_t *tplg, struct tplg_object *object) +{ + if (!strcmp(object->class_name, "manifest")) + return tplg_build_manifest_object(tplg, object); + + if (!strcmp(object->class_name, "data")) + return tplg_build_data_object(tplg, object); + + if (!strcmp(object->class_name, "connection")) + return tplg_build_dapm_route(tplg, object); + + + return 0; +} + +static int tplg_build_object(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct list_head *pos; + int ret; + + switch (object->type) { + case SND_TPLG_CLASS_TYPE_COMPONENT: + { + ret = tplg_build_comp_object(tplg, object); + if (ret < 0) { + SNDERR("Failed to build comp object %s\n", object->name); + return ret; + } + break; + } + case SND_TPLG_CLASS_TYPE_DAI: + ret = tplg_build_dai_object(tplg, object); + if (ret < 0) { + SNDERR("Failed to build DAI object %s\n", object->name); + return ret; + } + break; + case SND_TPLG_CLASS_TYPE_PCM: + ret = tplg_build_pcm_type_object(tplg, object); + if (ret < 0) { + SNDERR("Failed to build PCM class object %s\n", object->name); + return ret; + } + break; + case SND_TPLG_CLASS_TYPE_BASE: + ret = tplg_build_base_object(tplg, object); + if (ret < 0) { + SNDERR("Failed to build object %s\n", object->name); + return ret; + } + break; + } + + /* build child objects */ + list_for_each(pos, &object->object_list) { + struct tplg_object *child = list_entry(pos, struct tplg_object, list); + + ret = tplg_build_object(tplg, child); + if (ret < 0) { + SNDERR("Failed to build object\n", child->name); + return ret; + } + } + + return 0; +} + + +int tplg_create_new_object(snd_tplg_t *tplg, snd_config_t *cfg, struct tplg_elem *class_elem) +{ + snd_config_iterator_t i, next; + struct tplg_class *class = class_elem->class; + struct tplg_object *object; + snd_config_t *n; + const char *id; + int ret; + + /* create all objects of the same class type */ + snd_config_for_each(i, next, cfg) { + + n = snd_config_iterator_entry(i); + if (snd_config_get_id(n, &id) < 0) + continue; + + /* + * Create object by duplicating the attributes and child objects from the class + * definiion + */ + object = tplg_create_object(tplg, n, class_elem->class, NULL, NULL); + if (!object) { + SNDERR("Error creating object for class %s\n", class->name); + return -EINVAL; + } + + /* check if all mandatory values are present and translate string values to integer */ + ret = tplg_object_attributes_sanity_check(object); + if (ret < 0) { + SNDERR("Object %s failed sanity check\n", object->name); + return -EINVAL; + } + + /* Build the object by creating the topology element */ + ret = tplg_build_object(tplg, object); + if (ret < 0) { + SNDERR("Error creating object for class %s\n", class->name); + return -EINVAL; + } + } + + return 0; +} + +/* create top-level topology objects */ +int tplg_create_objects(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) +{ + struct tplg_elem *class_elem; + const char *id; + + if (snd_config_get_id(cfg, &id) < 0) + return -EINVAL; + + /* look up class elem */ + class_elem = tplg_elem_lookup(&tplg->class_list, id, + SND_TPLG_TYPE_CLASS, SND_TPLG_INDEX_ALL); + if (!class_elem) { + SNDERR("No class elem found for %s\n", id); + return -EINVAL; + } + + return tplg_create_new_object(tplg, cfg, class_elem); +} + +void tplg2_free_elem_object(struct tplg_elem *elem) +{ + struct tplg_object *object = elem->object; + struct list_head *pos, *npos; + struct tplg_attribute *attr; + struct tplg_tuple_set *set; + + /* + * free args, attributes and tuples. Child objects will be freed when + * tplg->object_list is freed + */ + list_for_each_safe(pos, npos, &object->attribute_list) { + attr = list_entry(pos, struct tplg_attribute, list); + list_del(&attr->list); + free(attr); + } + + list_for_each_safe(pos, npos, &object->tuple_set_list) { + set = list_entry(pos, struct tplg_tuple_set, list); + list_del(&set->list); + free(set); + } +} diff --git a/src/topology/parser.c b/src/topology/parser.c index f34de01bd..9732ef588 100644 --- a/src/topology/parser.c +++ b/src/topology/parser.c @@ -323,29 +323,29 @@ int snd_tplg_build_file(snd_tplg_t *tplg, return snd_tplg_build(tplg, outfile); } -int snd_tplg_add_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int snd_tplg_add_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { switch (t->type) { case SND_TPLG_TYPE_MIXER: - return tplg_add_mixer_object(tplg, t); + return tplg_add_mixer_element(tplg, t); case SND_TPLG_TYPE_ENUM: - return tplg_add_enum_object(tplg, t); + return tplg_add_enum_element(tplg, t); case SND_TPLG_TYPE_BYTES: - return tplg_add_bytes_object(tplg, t); + return tplg_add_bytes_element(tplg, t); case SND_TPLG_TYPE_DAPM_WIDGET: - return tplg_add_widget_object(tplg, t); + return tplg_add_widget_element(tplg, t); case SND_TPLG_TYPE_DAPM_GRAPH: - return tplg_add_graph_object(tplg, t); + return tplg_add_graph_element(tplg, t); case SND_TPLG_TYPE_PCM: - return tplg_add_pcm_object(tplg, t); + return tplg_add_pcm_element(tplg, t); case SND_TPLG_TYPE_DAI: - return tplg_add_dai_object(tplg, t); + return tplg_add_dai_element(tplg, t); case SND_TPLG_TYPE_LINK: case SND_TPLG_TYPE_BE: case SND_TPLG_TYPE_CC: - return tplg_add_link_object(tplg, t); + return tplg_add_link_element(tplg, t); default: - SNDERR("invalid object type %d", t->type); + SNDERR("invalid element type %d", t->type); return -EINVAL; }; } @@ -469,6 +469,8 @@ snd_tplg_t *snd_tplg_create(int flags) INIT_LIST_HEAD(&tplg->token_list); INIT_LIST_HEAD(&tplg->tuple_list); INIT_LIST_HEAD(&tplg->hw_cfg_list); + INIT_LIST_HEAD(&tplg->class_list); + INIT_LIST_HEAD(&tplg->object_list); return tplg; } @@ -483,6 +485,8 @@ void snd_tplg_free(snd_tplg_t *tplg) free(tplg->bin); free(tplg->manifest_pdata); + tplg_elem_free_list(&tplg->object_list); + tplg_elem_free_list(&tplg->class_list); tplg_elem_free_list(&tplg->tlv_list); tplg_elem_free_list(&tplg->widget_list); tplg_elem_free_list(&tplg->pcm_list); diff --git a/src/topology/pcm-object.c b/src/topology/pcm-object.c new file mode 100644 index 000000000..74a26f778 --- /dev/null +++ b/src/topology/pcm-object.c @@ -0,0 +1,232 @@ +/* + Copyright(c) 2020-2021 Intel Corporation + All rights reserved. + + 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.1 of + the License, or (at your option) any later version. + + This program 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. + + Author: Ranjani Sridharan +*/ + +#include "list.h" +#include "local.h" +#include "tplg_local.h" +#include "tplg2_local.h" +#include + +int tplg_build_pcm_caps_object(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct snd_soc_tplg_stream_caps *sc; + struct tplg_elem *elem; + struct list_head *pos; + char *pcm_caps_name; + int ret; + + /* drop the class name from the object name to extract the pcm caps name */ + pcm_caps_name = strchr(object->name, '.') + 1; + elem = tplg_elem_new_common(tplg, NULL, pcm_caps_name, SND_TPLG_TYPE_STREAM_CAPS); + if (!elem) + return -ENOMEM; + + tplg_dbg("PCM caps elem: %s", elem->id); + + sc = elem->stream_caps; + sc->size = elem->size; + snd_strlcpy(sc->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!strcmp(attr->name, "rate_min")) { + sc->rate_min = attr->value.integer; + continue; + } + + if (!strcmp(attr->name, "rate_max")) { + sc->rate_max = attr->value.integer; + continue; + } + + if (!strcmp(attr->name, "channels_min")) { + sc->channels_min = attr->value.integer; + continue; + } + + if (!strcmp(attr->name, "channels_max")) { + sc->channels_max = attr->value.integer; + continue; + } + + if (!attr->cfg) + continue; + + + ret = tplg_parse_stream_caps_param(attr->cfg, sc); + if (ret < 0) { + SNDERR("Failed to parse PCM caps %s\n", object->name); + return ret; + } + } + + return 0; +} + +static int tplg2_get_unsigned_attribute(struct tplg_attribute *arg, unsigned int *val, int base) +{ + const char *str; + long lval; + unsigned long uval; + + if (arg->type == SND_CONFIG_TYPE_INTEGER) { + lval = arg->value.integer; + if (lval < 0 && lval >= INT_MIN) + lval = UINT_MAX + lval + 1; + if (lval < 0 || lval > UINT_MAX) + return -ERANGE; + *val = lval; + return 0; + } + + if (arg->type == SND_CONFIG_TYPE_STRING) { + SNDERR("Invalid type for %s\n", arg->name); + return -EINVAL; + } + + str = strdup(arg->value.string); + + uval = strtoul(str, NULL, base); + if (errno == ERANGE && uval == ULONG_MAX) + return -ERANGE; + if (errno && uval == 0) + return -EINVAL; + if (uval > UINT_MAX) + return -ERANGE; + *val = uval; + + return 0; +} + +static struct tplg_elem* tplg2_lookup_pcm_by_name(snd_tplg_t *tplg, char *pcm_name) +{ + struct snd_soc_tplg_pcm *pcm; + struct list_head *pos; + + list_for_each(pos, &tplg->pcm_list) { + struct tplg_elem *elem = list_entry(pos, struct tplg_elem, list); + pcm = elem->pcm; + + if (!strcmp(pcm->pcm_name, pcm_name)) { + return elem; + } + } + + return NULL; +} + +static int tplg_build_pcm_object(snd_tplg_t *tplg, struct tplg_object *object) +{ + struct tplg_attribute *pcm_id; + struct tplg_attribute *name; + struct tplg_attribute *dir; + struct snd_soc_tplg_stream_caps *caps; + struct snd_soc_tplg_pcm *pcm; + struct tplg_elem *elem; + struct list_head *pos; + char *dai_name; + char *caps_name; + unsigned int dai_id; + int ret; + + dir = tplg_get_attribute_by_name(&object->attribute_list, "direction"); + name = tplg_get_attribute_by_name(&object->attribute_list, "pcm_name"); + pcm_id = tplg_get_attribute_by_name(&object->attribute_list, "pcm_id"); + caps_name = strchr(object->name, '.') + 1; + dai_name = strdup(name->value.string); + + /* check if pcm elem exists already */ + elem = tplg2_lookup_pcm_by_name(tplg, name->value.string); + if (!elem) { + elem = tplg_elem_new_common(tplg, NULL, name->value.string, SND_TPLG_TYPE_PCM); + if (!elem) + return -ENOMEM; + + pcm = elem->pcm; + pcm->size = elem->size; + + /* set PCM name */ + snd_strlcpy(pcm->pcm_name, name->value.string, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + } else { + pcm = elem->pcm; + } + + ret = tplg2_get_unsigned_attribute(pcm_id, &dai_id, 0); + if (ret < 0) { + SNDERR("Invalid value for PCM DAI ID"); + return ret; + } + + /*TODO: check if pcm_id and dai_id are always the same */ + pcm->pcm_id = dai_id; + unaligned_put32(&pcm->dai_id, dai_id); + + /* set dai name */ + snprintf(pcm->dai_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %ld", dai_name, + pcm_id->value.integer); + free(dai_name); + + list_for_each(pos, &object->attribute_list) { + struct tplg_attribute *attr = list_entry(pos, struct tplg_attribute, list); + + if (!attr->cfg) + continue; + + ret = tplg_parse_pcm_param(tplg, attr->cfg, elem); + if (ret < 0) { + SNDERR("Failed to parse PCM %s\n", object->name); + return -EINVAL; + } + } + + caps = pcm->caps; + if (!strcmp(dir->value.string, "playback")) { + if (strcmp(caps[SND_SOC_TPLG_STREAM_PLAYBACK].name, "")) { + SNDERR("PCM Playback capabilities already set for %s\n", object->name); + return -EINVAL; + } + + unaligned_put32(&pcm->playback, 1); + snd_strlcpy(caps[SND_SOC_TPLG_STREAM_PLAYBACK].name, caps_name, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + } else { + if (strcmp(caps[SND_SOC_TPLG_STREAM_CAPTURE].name, "")) { + SNDERR("PCM Capture capabilities already set for %s\n", object->name); + return -EINVAL; + } + + snd_strlcpy(caps[SND_SOC_TPLG_STREAM_CAPTURE].name, caps_name, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + unaligned_put32(&pcm->capture, 1); + } + + tplg_dbg(" PCM: %s ID: %d dai_name: %s", pcm->pcm_name, pcm->dai_id, pcm->dai_name); + + return tplg_build_private_data(tplg, object); +} + +int tplg_build_pcm_type_object(snd_tplg_t *tplg, struct tplg_object *object) +{ + if (!strcmp(object->class_name, "pcm")) + return tplg_build_pcm_object(tplg, object); + + if (!strcmp(object->class_name, "pcm_caps")) + return tplg_build_pcm_caps_object(tplg, object); + + return 0; +} diff --git a/src/topology/pcm.c b/src/topology/pcm.c index a473b59b4..7e8185ba6 100644 --- a/src/topology/pcm.c +++ b/src/topology/pcm.c @@ -394,142 +394,153 @@ static int parse_unsigned(snd_config_t *n, void *dst) return 0; } -/* Parse pcm stream capabilities */ -int tplg_parse_stream_caps(snd_tplg_t *tplg, - snd_config_t *cfg, - void *private ATTRIBUTE_UNUSED) +int tplg_parse_stream_caps_param(snd_config_t *n, struct snd_soc_tplg_stream_caps *sc) { - struct snd_soc_tplg_stream_caps *sc; - struct tplg_elem *elem; - snd_config_iterator_t i, next; - snd_config_t *n; const char *id, *val; char *s; int err; - elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_STREAM_CAPS); - if (!elem) - return -ENOMEM; + if (snd_config_get_id(n, &id) < 0) + return 0; - sc = elem->stream_caps; - sc->size = elem->size; - snd_strlcpy(sc->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + /* skip comments */ + if (strcmp(id, "comment") == 0) + return 0; + if (id[0] == '#') + return 0; - tplg_dbg(" PCM Capabilities: %s", elem->id); + if (strcmp(id, "formats") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - snd_config_for_each(i, next, cfg) { - n = snd_config_iterator_entry(i); - if (snd_config_get_id(n, &id) < 0) - continue; + s = strdup(val); + if (s == NULL) + return -ENOMEM; - /* skip comments */ - if (strcmp(id, "comment") == 0) - continue; - if (id[0] == '#') - continue; + err = split_format(sc, s); + free(s); - if (strcmp(id, "formats") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + if (err < 0) + return err; - s = strdup(val); - if (s == NULL) - return -ENOMEM; + tplg_dbg("\t\t%s: %s", id, val); + return 0; + } - err = split_format(sc, s); - free(s); + if (strcmp(id, "rates") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - if (err < 0) - return err; + s = strdup(val); + if (!s) + return -ENOMEM; - tplg_dbg("\t\t%s: %s", id, val); - continue; - } + err = split_rate(sc, s); + free(s); - if (strcmp(id, "rates") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + if (err < 0) + return err; - s = strdup(val); - if (!s) - return -ENOMEM; + tplg_dbg("\t\t%s: %s", id, val); + return 0; + } - err = split_rate(sc, s); - free(s); + if (strcmp(id, "rate_min") == 0) { + if (parse_unsigned(n, &sc->rate_min)) + return -EINVAL; + return 0; + } - if (err < 0) - return err; + if (strcmp(id, "rate_max") == 0) { + if (parse_unsigned(n, &sc->rate_max)) + return -EINVAL; + return 0; + } - tplg_dbg("\t\t%s: %s", id, val); - continue; - } + if (strcmp(id, "channels_min") == 0) { + if (parse_unsigned(n, &sc->channels_min)) + return -EINVAL; + return 0; + } - if (strcmp(id, "rate_min") == 0) { - if (parse_unsigned(n, &sc->rate_min)) - return -EINVAL; - continue; - } + if (strcmp(id, "channels_max") == 0) { + if (parse_unsigned(n, &sc->channels_max)) + return -EINVAL; + return 0; + } - if (strcmp(id, "rate_max") == 0) { - if (parse_unsigned(n, &sc->rate_max)) - return -EINVAL; - continue; - } + if (strcmp(id, "periods_min") == 0) { + if (parse_unsigned(n, &sc->periods_min)) + return -EINVAL; + return 0; + } - if (strcmp(id, "channels_min") == 0) { - if (parse_unsigned(n, &sc->channels_min)) - return -EINVAL; - continue; - } + if (strcmp(id, "periods_max") == 0) { + if (parse_unsigned(n, &sc->periods_max)) + return -EINVAL; + return 0; + } - if (strcmp(id, "channels_max") == 0) { - if (parse_unsigned(n, &sc->channels_max)) - return -EINVAL; - continue; - } + if (strcmp(id, "period_size_min") == 0) { + if (parse_unsigned(n, &sc->period_size_min)) + return -EINVAL; + return 0; + } - if (strcmp(id, "periods_min") == 0) { - if (parse_unsigned(n, &sc->periods_min)) - return -EINVAL; - continue; - } + if (strcmp(id, "period_size_max") == 0) { + if (parse_unsigned(n, &sc->period_size_max)) + return -EINVAL; + return 0; + } - if (strcmp(id, "periods_max") == 0) { - if (parse_unsigned(n, &sc->periods_max)) - return -EINVAL; - continue; - } + if (strcmp(id, "buffer_size_min") == 0) { + if (parse_unsigned(n, &sc->buffer_size_min)) + return -EINVAL; + return 0; + } - if (strcmp(id, "period_size_min") == 0) { - if (parse_unsigned(n, &sc->period_size_min)) - return -EINVAL; - continue; - } + if (strcmp(id, "buffer_size_max") == 0) { + if (parse_unsigned(n, &sc->buffer_size_max)) + return -EINVAL; + return 0; + } - if (strcmp(id, "period_size_max") == 0) { - if (parse_unsigned(n, &sc->period_size_max)) - return -EINVAL; - continue; - } + if (strcmp(id, "sig_bits") == 0) { + if (parse_unsigned(n, &sc->sig_bits)) + return -EINVAL; + return 0; + } - if (strcmp(id, "buffer_size_min") == 0) { - if (parse_unsigned(n, &sc->buffer_size_min)) - return -EINVAL; - continue; - } + return 0; +} - if (strcmp(id, "buffer_size_max") == 0) { - if (parse_unsigned(n, &sc->buffer_size_max)) - return -EINVAL; - continue; - } +/* Parse pcm stream capabilities */ +int tplg_parse_stream_caps(snd_tplg_t *tplg, + snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_stream_caps *sc; + struct tplg_elem *elem; + snd_config_iterator_t i, next; + snd_config_t *n; + int err; - if (strcmp(id, "sig_bits") == 0) { - if (parse_unsigned(n, &sc->sig_bits)) - return -EINVAL; - continue; - } + elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_STREAM_CAPS); + if (!elem) + return -ENOMEM; + sc = elem->stream_caps; + sc->size = elem->size; + snd_strlcpy(sc->name, elem->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + + tplg_dbg(" PCM Capabilities: %s", elem->id); + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + + err = tplg_parse_stream_caps_param(n, sc); + if (err < 0) + return err; } return 0; @@ -835,6 +846,94 @@ static int save_flags(unsigned int flags, unsigned int mask, return err; } +int tplg_parse_pcm_param(snd_tplg_t *tplg, snd_config_t *n, struct tplg_elem *elem) +{ + struct snd_soc_tplg_pcm *pcm = elem->pcm; + const char *id; + int err, ival; + + if (snd_config_get_id(n, &id) < 0) + return 0; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + return 0; + + if (id[0] == '#') + return 0; + + if (strcmp(id, "id") == 0) { + if (parse_unsigned(n, &pcm->pcm_id)) + return -EINVAL; + return 0; + } + + if (strcmp(id, "pcm") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_streams, elem); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "compress") == 0) { + ival = snd_config_get_bool(n); + if (ival < 0) + return -EINVAL; + + pcm->compress = ival; + + tplg_dbg("\t%s: %d", id, ival); + return 0; + } + + if (strcmp(id, "dai") == 0) { + err = tplg_parse_compound(tplg, n, + tplg_parse_fe_dai, elem); + if (err < 0) + return err; + return 0; + } + + /* flags */ + if (strcmp(id, "symmetric_rates") == 0) { + err = parse_flag(n, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES, + &pcm->flag_mask, &pcm->flags); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "symmetric_channels") == 0) { + err = parse_flag(n, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS, + &pcm->flag_mask, &pcm->flags); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "symmetric_sample_bits") == 0) { + err = parse_flag(n, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS, + &pcm->flag_mask, &pcm->flags); + if (err < 0) + return err; + return 0; + } + + /* private data */ + if (strcmp(id, "data") == 0) { + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); + if (err < 0) + return err; + return 0; + } + + return 0; +} + /* Parse PCM (for front end DAI & DAI link) in text conf file */ int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) @@ -843,8 +942,7 @@ int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg, struct tplg_elem *elem; snd_config_iterator_t i, next; snd_config_t *n; - const char *id; - int err, ival; + int err; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_PCM); if (!elem) @@ -859,121 +957,47 @@ int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg, snd_config_for_each(i, next, cfg) { n = snd_config_iterator_entry(i); - if (snd_config_get_id(n, &id) < 0) - continue; - - /* skip comments */ - if (strcmp(id, "comment") == 0) - continue; - if (id[0] == '#') - continue; - - if (strcmp(id, "id") == 0) { - if (parse_unsigned(n, &pcm->pcm_id)) - return -EINVAL; - continue; - } + err = tplg_parse_pcm_param(tplg, n, elem); + if (err < 0) + return err; + } - if (strcmp(id, "pcm") == 0) { - err = tplg_parse_compound(tplg, n, - tplg_parse_streams, elem); - if (err < 0) - return err; - continue; - } + return 0; +} - if (strcmp(id, "compress") == 0) { - ival = snd_config_get_bool(n); - if (ival < 0) - return -EINVAL; +/* save PCM */ +int tplg_save_pcm(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + struct tplg_buf *dst, const char *pfx) +{ + struct snd_soc_tplg_pcm *pcm = elem->pcm; + char pfx2[16]; + int err; - pcm->compress = ival; - - tplg_dbg("\t%s: %d", id, ival); - continue; - } - - if (strcmp(id, "dai") == 0) { - err = tplg_parse_compound(tplg, n, - tplg_parse_fe_dai, elem); - if (err < 0) - return err; - continue; - } - - /* flags */ - if (strcmp(id, "symmetric_rates") == 0) { - err = parse_flag(n, - SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES, - &pcm->flag_mask, &pcm->flags); - if (err < 0) - return err; - continue; - } - - if (strcmp(id, "symmetric_channels") == 0) { - err = parse_flag(n, - SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS, - &pcm->flag_mask, &pcm->flags); - if (err < 0) - return err; - continue; - } - - if (strcmp(id, "symmetric_sample_bits") == 0) { - err = parse_flag(n, - SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS, - &pcm->flag_mask, &pcm->flags); - if (err < 0) - return err; - continue; - } - - /* private data */ - if (strcmp(id, "data") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); - if (err < 0) - return err; - continue; - } - } - - return 0; -} - -/* save PCM */ -int tplg_save_pcm(snd_tplg_t *tplg ATTRIBUTE_UNUSED, - struct tplg_elem *elem, - struct tplg_buf *dst, const char *pfx) -{ - struct snd_soc_tplg_pcm *pcm = elem->pcm; - char pfx2[16]; - int err; - - snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); - err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); - if (err >= 0 && elem->index) - err = tplg_save_printf(dst, pfx, "\tindex %u\n", - elem->index); - if (err >= 0 && pcm->pcm_id) - err = tplg_save_printf(dst, pfx, "\tid %u\n", - pcm->pcm_id); - if (err >= 0 && pcm->compress) - err = tplg_save_printf(dst, pfx, "\tcompress 1\n"); - snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); - if (err >= 0) - err = tplg_save_fe_dai(tplg, elem, dst, pfx2); - if (err >= 0) - err = tplg_save_streams(tplg, elem, dst, pfx2); - if (err >= 0) - err = save_flags(pcm->flags, pcm->flag_mask, dst, pfx); - if (err >= 0) - err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA, - "data", dst, pfx2); - if (err >= 0) - err = tplg_save_printf(dst, pfx, "}\n"); - return err; -} + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err >= 0 && elem->index) + err = tplg_save_printf(dst, pfx, "\tindex %u\n", + elem->index); + if (err >= 0 && pcm->pcm_id) + err = tplg_save_printf(dst, pfx, "\tid %u\n", + pcm->pcm_id); + if (err >= 0 && pcm->compress) + err = tplg_save_printf(dst, pfx, "\tcompress 1\n"); + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + if (err >= 0) + err = tplg_save_fe_dai(tplg, elem, dst, pfx2); + if (err >= 0) + err = tplg_save_streams(tplg, elem, dst, pfx2); + if (err >= 0) + err = save_flags(pcm->flags, pcm->flag_mask, dst, pfx); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_DATA, + "data", dst, pfx2); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} /* Parse physical DAI */ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg, @@ -1130,6 +1154,94 @@ static int parse_hw_config_refs(snd_tplg_t *tplg ATTRIBUTE_UNUSED, return 0; } +int tplg_parse_link_param(snd_tplg_t *tplg, snd_config_t *n, + struct snd_soc_tplg_link_config *link, struct tplg_elem *elem) +{ + const char *id, *val = NULL; + int err; + + if (snd_config_get_id(n, &id) < 0) + return 0; + + /* skip comments */ + if (strcmp(id, "comment") == 0) + return 0; + if (id[0] == '#') + return 0; + + if (strcmp(id, "id") == 0) { + if (parse_unsigned(n, &link->id)) + return -EINVAL; + return 0; + } + + if (strcmp(id, "stream_name") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; + + snd_strlcpy(link->stream_name, val, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + tplg_dbg("\t%s: %s", id, val); + return 0; + } + + if (strcmp(id, "hw_configs") == 0) { + if (!elem) + return 0; + + err = parse_hw_config_refs(tplg, n, elem); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "default_hw_conf_id") == 0) { + if (parse_unsigned(n, &link->default_hw_config_id)) + return -EINVAL; + return 0; + } + + /* flags */ + if (strcmp(id, "symmetric_rates") == 0) { + err = parse_flag(n, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES, + &link->flag_mask, &link->flags); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "symmetric_channels") == 0) { + err = parse_flag(n, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS, + &link->flag_mask, &link->flags); + if (err < 0) + return err; + return 0; + } + + if (strcmp(id, "symmetric_sample_bits") == 0) { + err = parse_flag(n, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS, + &link->flag_mask, &link->flags); + if (err < 0) + return err; + return 0; + } + + /* private data */ + if (strcmp(id, "data") == 0) { + if (!elem) + return 0; + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); + if (err < 0) + return err; + return 0; + } + + return 0; +} + /* Parse a physical link element in text conf file */ int tplg_parse_link(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) @@ -1138,8 +1250,7 @@ int tplg_parse_link(snd_tplg_t *tplg, snd_config_t *cfg, struct tplg_elem *elem; snd_config_iterator_t i, next; snd_config_t *n; - const char *id, *val = NULL; - int err; + int ret; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_BE); if (!elem) @@ -1152,81 +1263,10 @@ int tplg_parse_link(snd_tplg_t *tplg, snd_config_t *cfg, tplg_dbg(" Link: %s", elem->id); snd_config_for_each(i, next, cfg) { - n = snd_config_iterator_entry(i); - if (snd_config_get_id(n, &id) < 0) - continue; - - /* skip comments */ - if (strcmp(id, "comment") == 0) - continue; - if (id[0] == '#') - continue; - - if (strcmp(id, "id") == 0) { - if (parse_unsigned(n, &link->id)) - return -EINVAL; - continue; - } - - if (strcmp(id, "stream_name") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; - - snd_strlcpy(link->stream_name, val, - SNDRV_CTL_ELEM_ID_NAME_MAXLEN); - tplg_dbg("\t%s: %s", id, val); - continue; - } - - if (strcmp(id, "hw_configs") == 0) { - err = parse_hw_config_refs(tplg, n, elem); - if (err < 0) - return err; - continue; - } - - if (strcmp(id, "default_hw_conf_id") == 0) { - if (parse_unsigned(n, &link->default_hw_config_id)) - return -EINVAL; - continue; - } - - /* flags */ - if (strcmp(id, "symmetric_rates") == 0) { - err = parse_flag(n, - SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES, - &link->flag_mask, &link->flags); - if (err < 0) - return err; - continue; - } - - if (strcmp(id, "symmetric_channels") == 0) { - err = parse_flag(n, - SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS, - &link->flag_mask, &link->flags); - if (err < 0) - return err; - continue; - } - - if (strcmp(id, "symmetric_sample_bits") == 0) { - err = parse_flag(n, - SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS, - &link->flag_mask, &link->flags); - if (err < 0) - return err; - continue; - } - - /* private data */ - if (strcmp(id, "data") == 0) { - err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); - if (err < 0) - return err; - continue; - } + ret = tplg_parse_link_param(tplg, n, link, elem); + if (ret < 0) + return ret; } return 0; @@ -1401,244 +1441,258 @@ static const char *get_audio_hw_format_name(unsigned int type) return NULL; } -int tplg_parse_hw_config(snd_tplg_t *tplg, snd_config_t *cfg, - void *private ATTRIBUTE_UNUSED) +int tplg_set_hw_config_param(snd_config_t *n, struct snd_soc_tplg_hw_config *hw_cfg) { - - struct snd_soc_tplg_hw_config *hw_cfg; - struct tplg_elem *elem; - snd_config_iterator_t i, next; - snd_config_t *n; const char *id, *val = NULL; int ret, ival; bool provider_legacy; - elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_HW_CONFIG); - if (!elem) - return -ENOMEM; + if (snd_config_get_id(n, &id) < 0) + return 0; - hw_cfg = elem->hw_cfg; - hw_cfg->size = elem->size; + /* skip comments */ + if (strcmp(id, "comment") == 0) + return 0; + if (id[0] == '#') + return 0; - tplg_dbg(" Link HW config: %s", elem->id); + if (strcmp(id, "id") == 0) { + if (parse_unsigned(n, &hw_cfg->id)) + return -EINVAL; + return 0; + } - snd_config_for_each(i, next, cfg) { + if (strcmp(id, "format") == 0 || + strcmp(id, "fmt") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - n = snd_config_iterator_entry(i); - if (snd_config_get_id(n, &id) < 0) - continue; + ret = get_audio_hw_format(val); + if (ret < 0) + return ret; + hw_cfg->fmt = ret; + return 0; + } - /* skip comments */ - if (strcmp(id, "comment") == 0) - continue; - if (id[0] == '#') - continue; + provider_legacy = false; + if (strcmp(id, "bclk_master") == 0) { + SNDERR("deprecated option %s, please use 'bclk'\n", id); + provider_legacy = true; + } - if (strcmp(id, "id") == 0) { - if (parse_unsigned(n, &hw_cfg->id)) - return -EINVAL; - continue; - } + if (provider_legacy || + strcmp(id, "bclk") == 0) { - if (strcmp(id, "format") == 0 || - strcmp(id, "fmt") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - ret = get_audio_hw_format(val); - if (ret < 0) - return ret; - hw_cfg->fmt = ret; - continue; - } + if (!strcmp(val, "master")) { + /* For backwards capability, + * "master" == "codec is slave" + */ + SNDERR("deprecated bclk value '%s'", val); - provider_legacy = false; - if (strcmp(id, "bclk_master") == 0) { - SNDERR("deprecated option %s, please use 'bclk'\n", id); - provider_legacy = true; + hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC; + } else if (!strcmp(val, "codec_slave")) { + SNDERR("deprecated bclk value '%s', use 'codec_consumer'", val); + + hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC; + } else if (!strcmp(val, "codec_consumer")) { + hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC; + } else if (!strcmp(val, "codec_master")) { + SNDERR("deprecated bclk value '%s', use 'codec_provider", val); + + hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CP; + } else if (!strcmp(val, "codec_provider")) { + hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CP; } + return 0; + } - if (provider_legacy || - strcmp(id, "bclk") == 0) { + if (strcmp(id, "bclk_freq") == 0 || + strcmp(id, "bclk_rate") == 0) { + if (parse_unsigned(n, &hw_cfg->bclk_rate)) + return -EINVAL; + return 0; + } - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + if (strcmp(id, "bclk_invert") == 0 || + strcmp(id, "invert_bclk") == 0) { + ival = snd_config_get_bool(n); + if (ival < 0) + return -EINVAL; - if (!strcmp(val, "master")) { - /* For backwards capability, - * "master" == "codec is slave" - */ - SNDERR("deprecated bclk value '%s'", val); - - hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC; - } else if (!strcmp(val, "codec_slave")) { - SNDERR("deprecated bclk value '%s', use 'codec_consumer'", val); - - hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC; - } else if (!strcmp(val, "codec_consumer")) { - hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CC; - } else if (!strcmp(val, "codec_master")) { - SNDERR("deprecated bclk value '%s', use 'codec_provider", val); - - hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CP; - } else if (!strcmp(val, "codec_provider")) { - hw_cfg->bclk_provider = SND_SOC_TPLG_BCLK_CP; - } - continue; - } + hw_cfg->invert_bclk = ival; + return 0; + } - if (strcmp(id, "bclk_freq") == 0 || - strcmp(id, "bclk_rate") == 0) { - if (parse_unsigned(n, &hw_cfg->bclk_rate)) - return -EINVAL; - continue; - } + provider_legacy = false; + if (strcmp(id, "fsync_master") == 0) { + SNDERR("deprecated option %s, please use 'fsync'\n", id); + provider_legacy = true; + } - if (strcmp(id, "bclk_invert") == 0 || - strcmp(id, "invert_bclk") == 0) { - ival = snd_config_get_bool(n); - if (ival < 0) - return -EINVAL; + if (provider_legacy || + strcmp(id, "fsync") == 0) { - hw_cfg->invert_bclk = ival; - continue; - } + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - provider_legacy = false; - if (strcmp(id, "fsync_master") == 0) { - SNDERR("deprecated option %s, please use 'fsync'\n", id); - provider_legacy = true; - } + if (!strcmp(val, "master")) { + /* For backwards capability, + * "master" == "codec is slave" + */ + SNDERR("deprecated fsync value '%s'", val); - if (provider_legacy || - strcmp(id, "fsync") == 0) { + hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC; + } else if (!strcmp(val, "codec_slave")) { + SNDERR("deprecated fsync value '%s', use 'codec_consumer'", val); - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC; + } else if (!strcmp(val, "codec_consumer")) { + hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC; + } else if (!strcmp(val, "codec_master")) { + SNDERR("deprecated fsync value '%s', use 'codec_provider'", val); - if (!strcmp(val, "master")) { - /* For backwards capability, - * "master" == "codec is slave" - */ - SNDERR("deprecated fsync value '%s'", val); - - hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC; - } else if (!strcmp(val, "codec_slave")) { - SNDERR("deprecated fsync value '%s', use 'codec_consumer'", val); - - hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC; - } else if (!strcmp(val, "codec_consumer")) { - hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CC; - } else if (!strcmp(val, "codec_master")) { - SNDERR("deprecated fsync value '%s', use 'codec_provider'", val); - - hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CP; - } else if (!strcmp(val, "codec_provider")) { - hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CP; - } - continue; + hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CP; + } else if (!strcmp(val, "codec_provider")) { + hw_cfg->fsync_provider = SND_SOC_TPLG_FSYNC_CP; } + return 0; + } - if (strcmp(id, "fsync_invert") == 0 || - strcmp(id, "invert_fsync") == 0) { - ival = snd_config_get_bool(n); - if (ival < 0) - return -EINVAL; + if (strcmp(id, "fsync_invert") == 0 || + strcmp(id, "invert_fsync") == 0) { + ival = snd_config_get_bool(n); + if (ival < 0) + return -EINVAL; - hw_cfg->invert_fsync = ival; - continue; - } + hw_cfg->invert_fsync = ival; + return 0; + } - if (strcmp(id, "fsync_freq") == 0 || - strcmp(id, "fsync_rate") == 0) { - if (parse_unsigned(n, &hw_cfg->fsync_rate)) - return -EINVAL; - continue; - } + if (strcmp(id, "fsync_freq") == 0 || + strcmp(id, "fsync_rate") == 0) { + if (parse_unsigned(n, &hw_cfg->fsync_rate)) + return -EINVAL; + return 0; + } - if (strcmp(id, "mclk_freq") == 0 || - strcmp(id, "mclk_rate") == 0) { - if (parse_unsigned(n, &hw_cfg->mclk_rate)) - return -EINVAL; - continue; - } + if (strcmp(id, "mclk_freq") == 0 || + strcmp(id, "mclk_rate") == 0) { + if (parse_unsigned(n, &hw_cfg->mclk_rate)) + return -EINVAL; + return 0; + } - if (strcmp(id, "mclk") == 0 || - strcmp(id, "mclk_direction") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; + if (strcmp(id, "mclk") == 0 || + strcmp(id, "mclk_direction") == 0) { + if (snd_config_get_string(n, &val) < 0) + return -EINVAL; - if (!strcmp(val, "master")) { - /* For backwards capability, - * "master" == "for codec, mclk is input" - */ - SNDERR("deprecated mclk value '%s'", val); - - hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CI; - } else if (!strcmp(val, "codec_mclk_in")) { - hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CI; - } else if (!strcmp(val, "codec_mclk_out")) { - hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CO; - } - continue; + if (!strcmp(val, "master")) { + /* For backwards capability, + * "master" == "for codec, mclk is input" + */ + SNDERR("deprecated mclk value '%s'", val); + + hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CI; + } else if (!strcmp(val, "codec_mclk_in")) { + hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CI; + } else if (!strcmp(val, "codec_mclk_out")) { + hw_cfg->mclk_direction = SND_SOC_TPLG_MCLK_CO; } + return 0; + } - if (strcmp(id, "pm_gate_clocks") == 0 || - strcmp(id, "clock_gated") == 0) { - ival = snd_config_get_bool(n); - if (ival < 0) - return -EINVAL; + if (strcmp(id, "pm_gate_clocks") == 0 || + strcmp(id, "clock_gated") == 0) { + ival = snd_config_get_bool(n); + if (ival < 0) + return -EINVAL; - if (ival) - hw_cfg->clock_gated = - SND_SOC_TPLG_DAI_CLK_GATE_GATED; - else - hw_cfg->clock_gated = - SND_SOC_TPLG_DAI_CLK_GATE_CONT; - continue; - } + if (ival) + hw_cfg->clock_gated = + SND_SOC_TPLG_DAI_CLK_GATE_GATED; + else + hw_cfg->clock_gated = + SND_SOC_TPLG_DAI_CLK_GATE_CONT; + return 0; + } - if (strcmp(id, "tdm_slots") == 0) { - if (parse_unsigned(n, &hw_cfg->tdm_slots)) - return -EINVAL; - continue; - } + if (strcmp(id, "tdm_slots") == 0) { + if (parse_unsigned(n, &hw_cfg->tdm_slots)) + return -EINVAL; + return 0; + } - if (strcmp(id, "tdm_slot_width") == 0) { - if (parse_unsigned(n, &hw_cfg->tdm_slot_width)) - return -EINVAL; - continue; - } + if (strcmp(id, "tdm_slot_width") == 0) { + if (parse_unsigned(n, &hw_cfg->tdm_slot_width)) + return -EINVAL; + return 0; + } - if (strcmp(id, "tx_slots") == 0) { - if (parse_unsigned(n, &hw_cfg->tx_slots)) - return -EINVAL; - continue; - } + if (strcmp(id, "tx_slots") == 0) { + if (parse_unsigned(n, &hw_cfg->tx_slots)) + return -EINVAL; + return 0; + } - if (strcmp(id, "rx_slots") == 0) { - if (parse_unsigned(n, &hw_cfg->rx_slots)) - return -EINVAL; - continue; - } + if (strcmp(id, "rx_slots") == 0) { + if (parse_unsigned(n, &hw_cfg->rx_slots)) + return -EINVAL; + return 0; + } - if (strcmp(id, "tx_channels") == 0) { - if (parse_unsigned(n, &hw_cfg->tx_channels)) - return -EINVAL; - continue; - } + if (strcmp(id, "tx_channels") == 0) { + if (parse_unsigned(n, &hw_cfg->tx_channels)) + return -EINVAL; + return 0; + } - if (strcmp(id, "rx_channels") == 0) { - if (parse_unsigned(n, &hw_cfg->rx_channels)) - return -EINVAL; - continue; - } + if (strcmp(id, "rx_channels") == 0) { + if (parse_unsigned(n, &hw_cfg->rx_channels)) + return -EINVAL; + return 0; + } + return 0; +} + +static int tplg_set_hw_config(snd_config_t *cfg, struct snd_soc_tplg_hw_config *hw_cfg) +{ + snd_config_iterator_t i, next; + snd_config_t *n; + int ret; + + snd_config_for_each(i, next, cfg) { + n = snd_config_iterator_entry(i); + ret = tplg_set_hw_config_param(n, hw_cfg); + if (ret < 0) + return ret; } return 0; } +int tplg_parse_hw_config(snd_tplg_t *tplg, snd_config_t *cfg, + void *private ATTRIBUTE_UNUSED) +{ + struct snd_soc_tplg_hw_config *hw_cfg; + struct tplg_elem *elem; + + elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_HW_CONFIG); + if (!elem) + return -ENOMEM; + + hw_cfg = elem->hw_cfg; + hw_cfg->size = elem->size; + + tplg_dbg(" Link HW config: %s", elem->id); + + return tplg_set_hw_config(cfg, hw_cfg); +} + /* save hw config */ int tplg_save_hw_config(snd_tplg_t *tplg ATTRIBUTE_UNUSED, struct tplg_elem *elem, @@ -1704,8 +1758,8 @@ int tplg_save_hw_config(snd_tplg_t *tplg ATTRIBUTE_UNUSED, return err; } -/* copy stream object */ -static void tplg_add_stream_object(struct snd_soc_tplg_stream *strm, +/* copy stream config */ +static void tplg_add_stream_config(struct snd_soc_tplg_stream *strm, struct snd_tplg_stream_template *strm_tpl) { snd_strlcpy(strm->name, strm_tpl->name, @@ -1749,7 +1803,7 @@ static int tplg_add_stream_caps(snd_tplg_t *tplg, } /* Add a PCM element (FE DAI & DAI link) from C API */ -int tplg_add_pcm_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_pcm_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { struct snd_tplg_pcm_template *pcm_tpl = t->pcm; struct snd_soc_tplg_private *priv; @@ -1795,7 +1849,7 @@ int tplg_add_pcm_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) pcm->num_streams = pcm_tpl->num_streams; for (i = 0; i < pcm_tpl->num_streams; i++) - tplg_add_stream_object(&pcm->stream[i], &pcm_tpl->stream[i]); + tplg_add_stream_config(&pcm->stream[i], &pcm_tpl->stream[i]); /* private data */ priv = pcm_tpl->priv; @@ -1851,7 +1905,7 @@ static int set_link_hw_config(struct snd_soc_tplg_hw_config *cfg, } /* Add a physical DAI link element from C API */ -int tplg_add_link_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_link_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { struct snd_tplg_link_template *link_tpl = t->link; struct snd_soc_tplg_link_config *link; @@ -1885,7 +1939,7 @@ int tplg_add_link_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) return -EINVAL; link->num_streams = link_tpl->num_streams; for (i = 0; i < link->num_streams; i++) - tplg_add_stream_object(&link->stream[i], &link_tpl->stream[i]); + tplg_add_stream_config(&link->stream[i], &link_tpl->stream[i]); /* HW configs */ if (link_tpl->num_hw_configs > SND_SOC_TPLG_HW_CONFIG_MAX) @@ -1911,7 +1965,7 @@ int tplg_add_link_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) return 0; } -int tplg_add_dai_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) +int tplg_add_dai_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) { struct snd_tplg_dai_template *dai_tpl = t->dai; struct snd_soc_tplg_dai *dai; @@ -2080,7 +2134,7 @@ int tplg_decode_pcm(snd_tplg_t *tplg, pos += sizeof(*pcm) + pcm->priv.size; t.pcm = pt; - err = snd_tplg_add_object(tplg, &t); + err = snd_tplg_add_element(tplg, &t); if (err < 0) return err; @@ -2241,7 +2295,7 @@ int tplg_decode_link(snd_tplg_t *tplg, pos += sizeof(*link) + link->priv.size; t.link = < - err = snd_tplg_add_object(tplg, &t); + err = snd_tplg_add_element(tplg, &t); if (err < 0) return err; diff --git a/src/topology/tplg2_local.h b/src/topology/tplg2_local.h new file mode 100644 index 000000000..764d2aa96 --- /dev/null +++ b/src/topology/tplg2_local.h @@ -0,0 +1,141 @@ +/* + * 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. + */ +#include +#include +#include + +#include "local.h" +#include "list.h" +#include "bswap.h" +#include "topology.h" + +#include +#include +#include +#include + +#define TPLG_CLASS_ATTRIBUTE_MASK_MANDATORY 1 << 1 +#define TPLG_CLASS_ATTRIBUTE_MASK_IMMUTABLE 1 << 2 +#define TPLG_CLASS_ATTRIBUTE_MASK_DEPRECATED 1 << 3 +#define TPLG_CLASS_ATTRIBUTE_MASK_AUTOMATIC 1 << 4 +#define TPLG_CLASS_ATTRIBUTE_MASK_UNIQUE 1 << 5 + +struct tplg_attribute_ref { + const char *string; + int value; + struct list_head list; /* item in attribute constraint value_list */ +}; + +struct attribute_constraint { + struct list_head value_list; /* list of valid values */ + const char *value_ref; + int mask; + int min; + int max; +}; + +enum tplg_class_param_type { + TPLG_CLASS_PARAM_TYPE_ARGUMENT, + TPLG_CLASS_PARAM_TYPE_ATTRIBUTE, +}; + +struct tplg_attribute { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + snd_config_type_t type; + enum tplg_class_param_type param_type; + char token_ref[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char value_ref[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + bool found; + snd_config_t *cfg; + struct attribute_constraint constraint; + struct list_head list; /* item in attribute list */ + union { + long integer; + long long integer64; + double d; + char string[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + }value; +}; + +struct tplg_dai_object { + struct tplg_elem *link_elem; + int num_hw_configs; +}; + +struct tplg_pipeline_object { + struct tplg_object *pipe_widget_object; +}; + +struct tplg_comp_object { + struct tplg_elem *widget_elem; + int widget_id; +}; + +struct tplg_object { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char class_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int num_args; + int num_tuple_sets; + struct list_head attribute_list; + struct list_head tuple_set_list; + struct list_head object_list; + struct tplg_elem *elem; + snd_config_t *cfg; + int type; + struct list_head list; /* item in parent object list */ + union { + struct tplg_comp_object component; + struct tplg_dai_object dai; + struct tplg_pipeline_object pipeline; + }object_type; +}; + +/* class types */ +#define SND_TPLG_CLASS_TYPE_BASE 0 +#define SND_TPLG_CLASS_TYPE_COMPONENT 1 +#define SND_TPLG_CLASS_TYPE_PIPELINE 2 +#define SND_TPLG_CLASS_TYPE_DAI 3 +#define SND_TPLG_CLASS_TYPE_CONTROL 4 +#define SND_TPLG_CLASS_TYPE_PCM 5 + +struct tplg_class { + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int num_args; + struct list_head attribute_list; /* list of attributes */ + struct list_head object_list; /* list of child objects */ + int type; +}; + +int tplg_define_classes(snd_tplg_t *tplg, snd_config_t *cfg, void *priv); +void tplg2_free_elem_class(struct tplg_elem *elem); +int tplg_create_objects(snd_tplg_t *tplg, snd_config_t *cfg, void *priv); +int tplg_parse_attribute_value(snd_config_t *cfg, struct list_head *list, bool override); +struct tplg_object * +tplg_create_object(snd_tplg_t *tplg, snd_config_t *cfg, struct tplg_class *class, + struct tplg_object *parent, struct list_head *list); +struct tplg_attribute *tplg_get_attribute_by_name(struct list_head *list, const char *name); +int tplg_build_private_data(snd_tplg_t *tplg, struct tplg_object *object); +int tplg_build_comp_object(snd_tplg_t *tplg, struct tplg_object *object); +int tplg_build_dai_object(snd_tplg_t *tplg, struct tplg_object *object); +int tplg_build_object_tuple_sets(struct tplg_object *object); +int tplg_create_dai_object(struct tplg_class *class, struct tplg_object *object); +int tplg_create_component_object(struct tplg_object *object); +void tplg2_free_elem_object(struct tplg_elem *elem); +struct tplg_object *tplg_object_elem_lookup(snd_tplg_t *tplg, const char *class_name, + char *input); +struct tplg_object *tplg_object_lookup_in_list(struct list_head *list, const char *class_name, + char *input); +int tplg_build_pcm_type_object(snd_tplg_t *tplg, struct tplg_object *object); +int tplg_create_pipeline_object(struct tplg_class *class, struct tplg_object *object); +int tplg_build_dapm_route(snd_tplg_t *tplg, struct tplg_object *object); +int tplg_update_automatic_attributes(snd_tplg_t *tplg, struct tplg_object *object, + struct tplg_object *parent); diff --git a/src/topology/tplg_local.h b/src/topology/tplg_local.h index 1cb8b694f..fabd3b0bc 100644 --- a/src/topology/tplg_local.h +++ b/src/topology/tplg_local.h @@ -94,6 +94,8 @@ struct snd_tplg { struct list_head pcm_config_list; struct list_head pcm_caps_list; struct list_head hw_cfg_list; + struct list_head class_list; + struct list_head object_list; /* type-specific control lists */ struct list_head mixer_list; @@ -138,6 +140,8 @@ struct tplg_tuple { struct tplg_tuple_set { unsigned int type; /* uuid, bool, byte, short, word, string*/ unsigned int num_tuples; + char token_ref[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct list_head list; /* item in tuple_set_list */ struct tplg_tuple tuple[0]; }; @@ -157,7 +161,7 @@ struct tplg_elem { int index; enum snd_tplg_type type; - int size; /* total size of this object inc pdata and ref objects */ + int size; /* total size of this element inc pdata and ref objects */ int compound_elem; /* dont write this element as individual elem */ int vendor_type; /* vendor type for private data */ @@ -183,6 +187,8 @@ struct tplg_elem { struct tplg_vendor_tokens *tokens; struct tplg_vendor_tuples *tuples; struct snd_soc_tplg_manifest *manifest; + struct tplg_class *class; + struct tplg_object *object; }; /* an element may refer to other elements: @@ -288,7 +294,6 @@ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg, void *priv); int tplg_parse_link(snd_tplg_t *tplg, snd_config_t *cfg, void *priv); int tplg_parse_cc(snd_tplg_t *tplg, snd_config_t *cfg, void *priv); int tplg_parse_hw_config(snd_tplg_t *tplg, snd_config_t *cfg, void *priv); - unsigned int tplg_get_tuple_size(int type); void tplg_free_tuples(void *obj); @@ -341,11 +346,11 @@ int tplg_add_data(snd_tplg_t *tplg, struct tplg_elem *parent, const void *bin, size_t size); int tplg_add_data_bytes(snd_tplg_t *tplg, struct tplg_elem *parent, const char *suffix, const void *bin, size_t size); -int tplg_add_mixer_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); -int tplg_add_enum_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); -int tplg_add_bytes_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); -int tplg_add_widget_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); -int tplg_add_graph_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_mixer_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_enum_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_bytes_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_widget_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_graph_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); int tplg_add_mixer(snd_tplg_t *tplg, struct snd_tplg_mixer_template *mixer, struct tplg_elem **e); @@ -357,9 +362,9 @@ int tplg_add_bytes(snd_tplg_t *tplg, struct snd_tplg_bytes_template *bytes_ctl, int tplg_build_pcms(snd_tplg_t *tplg, unsigned int type); int tplg_build_dais(snd_tplg_t *tplg, unsigned int type); int tplg_build_links(snd_tplg_t *tplg, unsigned int type); -int tplg_add_link_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); -int tplg_add_pcm_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); -int tplg_add_dai_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_link_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_pcm_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); +int tplg_add_dai_element(snd_tplg_t *tplg, snd_tplg_obj_template_t *t); int tplg_nice_value_format(char *dst, size_t dst_size, unsigned int value); @@ -458,3 +463,27 @@ int tplg_decode_pcm(snd_tplg_t *tplg, size_t pos, int tplg_decode_dai(snd_tplg_t *tplg, size_t pos, struct snd_soc_tplg_hdr *hdr, void *bin, size_t size); +int lookup_channel(const char *c); +int tplg_set_hw_config_param(snd_config_t *n, struct snd_soc_tplg_hw_config *hw_cfg); +int tplg_parse_stream_caps_param(snd_config_t *n, struct snd_soc_tplg_stream_caps *sc); +int tplg_parse_pcm_param(snd_tplg_t *tplg, snd_config_t *n, struct tplg_elem *elem); +int tplg_parse_link_param(snd_tplg_t *tplg, snd_config_t *n, + struct snd_soc_tplg_link_config *link, struct tplg_elem *elem); +int lookup_widget(const char *w); +int tplg_parse_control_mixer_param(snd_tplg_t *tplg, snd_config_t *n, + struct snd_soc_tplg_mixer_control *mc, + struct tplg_elem *elem); +int tplg_parse_control_bytes_param(snd_tplg_t *tplg, snd_config_t *n, + struct snd_soc_tplg_bytes_control *be, + struct tplg_elem *elem); +int parse_access_values(snd_config_t *cfg, struct snd_soc_tplg_ctl_hdr *hdr); +int tplg_parse_tlv_dbscale_param(snd_config_t *n, struct snd_soc_tplg_tlv_dbscale *scale); +int scan_tuple_set(struct tplg_elem *elem, struct tplg_tuple_set *tuple_set, + struct tplg_vendor_tokens *tokens, int size); +int get_uuid(const char *str, unsigned char *uuid_le); +int get_tuple_type(const char *name); +int get_token_value(const char *token_id, struct tplg_vendor_tokens *tokens); +struct tplg_elem *tplg_elem_new_route(snd_tplg_t *tplg, int index); +int tplg_parse_data_hex(snd_config_t *cfg, struct tplg_elem *elem, int width); +int tplg_parse_dapm_widget_param(snd_config_t *n, struct snd_soc_tplg_dapm_widget *widget, + struct tplg_elem *elem);