mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-10-31 22:25:35 -04:00
We can distinguish different use cases by the object index value, the default value is 0, meaning applicable for all use cases, defined by macro "SND_TOLG_INDEX_ALL". An element can only refer to other elements for "all" use cases or the same use cases, i.e. it can only refer to elements with index value "SND_TOLG_INDEX_ALL" or the same index value as itself. The object list has been sorted in ascending order of index, so when we look up a reference for an object, we traverse the object list it depends on, if we have not found the reference until index of the object list is greater than index of the object, we give up and return an error. Signed-off-by: Fuwei Tang <fuweix.tang@intel.com> Reviewed-by: Mengdong Lin <mengdong.lin@linux.intel.com> Signed-off-by: Takashi Iwai <tiwai@suse.de>
1099 lines
23 KiB
C
1099 lines
23 KiB
C
/*
|
|
Copyright(c) 2014-2015 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.
|
|
|
|
Authors: Mengdong Lin <mengdong.lin@intel.com>
|
|
Yao Jin <yao.jin@intel.com>
|
|
Liam Girdwood <liam.r.girdwood@linux.intel.com>
|
|
*/
|
|
|
|
#include "list.h"
|
|
#include "tplg_local.h"
|
|
#include <ctype.h>
|
|
|
|
/* Get private data buffer of an element */
|
|
struct snd_soc_tplg_private *get_priv_data(struct tplg_elem *elem)
|
|
{
|
|
struct snd_soc_tplg_private *priv = NULL;
|
|
|
|
switch (elem->type) {
|
|
case SND_TPLG_TYPE_MANIFEST:
|
|
priv = &elem->manifest->priv;
|
|
break;
|
|
|
|
case SND_TPLG_TYPE_MIXER:
|
|
priv = &elem->mixer_ctrl->priv;
|
|
break;
|
|
|
|
case SND_TPLG_TYPE_ENUM:
|
|
priv = &elem->enum_ctrl->priv;
|
|
break;
|
|
|
|
case SND_TPLG_TYPE_BYTES:
|
|
priv = &elem->bytes_ext->priv;
|
|
break;
|
|
|
|
case SND_TPLG_TYPE_DAPM_WIDGET:
|
|
priv = &elem->widget->priv;
|
|
break;
|
|
|
|
case SND_TPLG_TYPE_DAI:
|
|
priv = &elem->dai->priv;
|
|
break;
|
|
|
|
default:
|
|
SNDERR("error: '%s': no support for private data for type %d\n",
|
|
elem->id, elem->type);
|
|
}
|
|
|
|
return priv;
|
|
}
|
|
|
|
/* Get Private data from a file. */
|
|
static int tplg_parse_data_file(snd_config_t *cfg, struct tplg_elem *elem)
|
|
{
|
|
struct snd_soc_tplg_private *priv = NULL;
|
|
const char *value = NULL;
|
|
char filename[MAX_FILE];
|
|
char *env = getenv(ALSA_CONFIG_TPLG_VAR);
|
|
FILE *fp;
|
|
size_t size, bytes_read;
|
|
int ret = 0;
|
|
|
|
tplg_dbg("data DataFile: %s\n", elem->id);
|
|
|
|
if (snd_config_get_string(cfg, &value) < 0)
|
|
return -EINVAL;
|
|
|
|
/* prepend alsa config directory to path */
|
|
snprintf(filename, sizeof(filename), "%s/%s",
|
|
env ? env : ALSA_TPLG_DIR, value);
|
|
|
|
fp = fopen(filename, "r");
|
|
if (fp == NULL) {
|
|
SNDERR("error: invalid data file path '%s'\n",
|
|
filename);
|
|
ret = -errno;
|
|
goto err;
|
|
}
|
|
|
|
fseek(fp, 0L, SEEK_END);
|
|
size = ftell(fp);
|
|
fseek(fp, 0L, SEEK_SET);
|
|
if (size <= 0) {
|
|
SNDERR("error: invalid data file size %zu\n", size);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
if (size > TPLG_MAX_PRIV_SIZE) {
|
|
SNDERR("error: data file too big %zu\n", size);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
priv = calloc(1, sizeof(*priv) + size);
|
|
if (!priv) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
bytes_read = fread(&priv->data, 1, size, fp);
|
|
if (bytes_read != size) {
|
|
ret = -errno;
|
|
goto err;
|
|
}
|
|
|
|
elem->data = priv;
|
|
priv->size = size;
|
|
elem->size = sizeof(*priv) + size;
|
|
return 0;
|
|
|
|
err:
|
|
if (priv)
|
|
free(priv);
|
|
return ret;
|
|
}
|
|
|
|
static void dump_priv_data(struct tplg_elem *elem)
|
|
{
|
|
struct snd_soc_tplg_private *priv = elem->data;
|
|
unsigned char *p = (unsigned char *)priv->data;
|
|
unsigned int i, j = 0;
|
|
|
|
tplg_dbg(" elem size = %d, priv data size = %d\n",
|
|
elem->size, priv->size);
|
|
|
|
for (i = 0; i < priv->size; i++) {
|
|
if (j++ % 8 == 0)
|
|
tplg_dbg("\n");
|
|
|
|
tplg_dbg(" 0x%x", *p++);
|
|
}
|
|
|
|
tplg_dbg("\n\n");
|
|
}
|
|
|
|
/* get number of hex value elements in CSV list */
|
|
static int get_hex_num(const char *str)
|
|
{
|
|
int commas = 0, values = 0, len = strlen(str);
|
|
const char *end = str + len;
|
|
|
|
/* we expect "0x0, 0x0, 0x0" */
|
|
while (str < end) {
|
|
|
|
/* skip white space */
|
|
if (isspace(*str)) {
|
|
str++;
|
|
continue;
|
|
}
|
|
|
|
/* find delimeters */
|
|
if (*str == ',') {
|
|
commas++;
|
|
str++;
|
|
continue;
|
|
}
|
|
|
|
/* find 0x[0-9] values */
|
|
if (*str == '0' && str + 2 <= end) {
|
|
if (str[1] == 'x' && str[2] >= '0' && str[2] <= 'f') {
|
|
values++;
|
|
str += 3;
|
|
} else {
|
|
str++;
|
|
}
|
|
}
|
|
|
|
str++;
|
|
}
|
|
|
|
/* there should always be one less comma than value */
|
|
if (values -1 != commas)
|
|
return -EINVAL;
|
|
|
|
return values;
|
|
}
|
|
|
|
/* get uuid from a string made by 16 characters separated by commas */
|
|
static int get_uuid(const char *str, unsigned char *uuid_le)
|
|
{
|
|
unsigned long int val;
|
|
char *tmp, *s = NULL;
|
|
int values = 0, ret = 0;
|
|
|
|
tmp = strdup(str);
|
|
if (tmp == NULL)
|
|
return -ENOMEM;
|
|
|
|
s = strtok(tmp, ",");
|
|
|
|
while (s != NULL) {
|
|
errno = 0;
|
|
val = strtoul(s, NULL, 0);
|
|
if ((errno == ERANGE && val == ULONG_MAX)
|
|
|| (errno != 0 && val == 0)
|
|
|| (val > UCHAR_MAX)) {
|
|
SNDERR("error: invalid value for uuid\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
*(uuid_le + values) = (unsigned char)val;
|
|
|
|
values++;
|
|
if (values >= 16)
|
|
break;
|
|
|
|
s = strtok(NULL, ",");
|
|
}
|
|
|
|
if (values < 16) {
|
|
SNDERR("error: less than 16 integers for uuid\n");
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
free(tmp);
|
|
return ret;
|
|
}
|
|
|
|
static int write_hex(char *buf, char *str, int width)
|
|
{
|
|
long val;
|
|
void *p = &val;
|
|
|
|
errno = 0;
|
|
val = strtol(str, NULL, 16);
|
|
|
|
if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
|
|
|| (errno != 0 && val == 0)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (width) {
|
|
case 1:
|
|
*(unsigned char *)buf = *(unsigned char *)p;
|
|
break;
|
|
case 2:
|
|
*(unsigned short *)buf = *(unsigned short *)p;
|
|
break;
|
|
case 4:
|
|
*(unsigned int *)buf = *(unsigned int *)p;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_data_hex(char *data, int off, const char *str, int width)
|
|
{
|
|
char *tmp, *s = NULL, *p = data;
|
|
int ret;
|
|
|
|
tmp = strdup(str);
|
|
if (tmp == NULL)
|
|
return -ENOMEM;
|
|
|
|
p += off;
|
|
s = strtok(tmp, ",");
|
|
|
|
while (s != NULL) {
|
|
ret = write_hex(p, s, width);
|
|
if (ret < 0) {
|
|
free(tmp);
|
|
return ret;
|
|
}
|
|
|
|
s = strtok(NULL, ",");
|
|
p += width;
|
|
}
|
|
|
|
free(tmp);
|
|
return 0;
|
|
}
|
|
|
|
static 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;
|
|
int size, esize, off, num;
|
|
int ret;
|
|
|
|
tplg_dbg(" data: %s\n", elem->id);
|
|
|
|
if (snd_config_get_string(cfg, &value) < 0)
|
|
return -EINVAL;
|
|
|
|
num = get_hex_num(value);
|
|
if (num <= 0) {
|
|
SNDERR("error: malformed hex variable list %s\n", value);
|
|
return -EINVAL;
|
|
}
|
|
|
|
size = num * width;
|
|
priv = elem->data;
|
|
|
|
if (size > TPLG_MAX_PRIV_SIZE) {
|
|
SNDERR("error: data too big %d\n", size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (priv != NULL) {
|
|
off = priv->size;
|
|
esize = elem->size + size;
|
|
priv = realloc(priv, esize);
|
|
} else {
|
|
off = 0;
|
|
esize = sizeof(*priv) + size;
|
|
priv = calloc(1, esize);
|
|
}
|
|
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
elem->data = priv;
|
|
priv->size += size;
|
|
elem->size = esize;
|
|
|
|
ret = copy_data_hex(priv->data, off, value, width);
|
|
|
|
dump_priv_data(elem);
|
|
return ret;
|
|
}
|
|
|
|
/* get the token integer value from its id */
|
|
static int get_token_value(const char *token_id,
|
|
struct tplg_vendor_tokens *tokens)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < tokens->num_tokens; i++) {
|
|
if (strcmp(token_id, tokens->token[i].id) == 0)
|
|
return tokens->token[i].value;
|
|
}
|
|
|
|
SNDERR("error: cannot find token id '%s'\n", token_id);
|
|
return -1;
|
|
}
|
|
|
|
/* get the vendor tokens referred by the vendor tuples */
|
|
static struct tplg_elem *get_tokens(snd_tplg_t *tplg, struct tplg_elem *elem)
|
|
{
|
|
struct tplg_ref *ref;
|
|
struct list_head *base, *pos;
|
|
|
|
base = &elem->ref_list;
|
|
list_for_each(pos, base) {
|
|
|
|
ref = list_entry(pos, struct tplg_ref, list);
|
|
|
|
if (!ref->id || ref->type != SND_TPLG_TYPE_TOKEN)
|
|
continue;
|
|
|
|
if (!ref->elem) {
|
|
ref->elem = tplg_elem_lookup(&tplg->token_list,
|
|
ref->id, SND_TPLG_TYPE_TOKEN, elem->index);
|
|
}
|
|
|
|
return ref->elem;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* check if a data element has tuples */
|
|
static bool has_tuples(struct tplg_elem *elem)
|
|
{
|
|
struct tplg_ref *ref;
|
|
struct list_head *base, *pos;
|
|
|
|
base = &elem->ref_list;
|
|
list_for_each(pos, base) {
|
|
|
|
ref = list_entry(pos, struct tplg_ref, list);
|
|
if (ref->id && ref->type == SND_TPLG_TYPE_TUPLE)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* get size of a tuple element from its type */
|
|
static unsigned int get_tuple_size(int type)
|
|
{
|
|
switch (type) {
|
|
|
|
case SND_SOC_TPLG_TUPLE_TYPE_UUID:
|
|
return sizeof(struct snd_soc_tplg_vendor_uuid_elem);
|
|
|
|
case SND_SOC_TPLG_TUPLE_TYPE_STRING:
|
|
return sizeof(struct snd_soc_tplg_vendor_string_elem);
|
|
|
|
default:
|
|
return sizeof(struct snd_soc_tplg_vendor_value_elem);
|
|
}
|
|
}
|
|
|
|
/* fill a data element's private buffer with its tuples */
|
|
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;
|
|
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;
|
|
int token_val;
|
|
|
|
if (priv) {
|
|
SNDERR("error: %s has more data than tuples\n", elem->id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
size = 0;
|
|
for (i = 0; i < tuples->num_sets ; i++) {
|
|
tuple_set = tuples->set[i];
|
|
set_size = sizeof(struct snd_soc_tplg_vendor_array)
|
|
+ get_tuple_size(tuple_set->type)
|
|
* tuple_set->num_tuples;
|
|
size += set_size;
|
|
if (size > TPLG_MAX_PRIV_SIZE) {
|
|
SNDERR("error: data too big %d\n", size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (priv != NULL)
|
|
priv = realloc(priv, sizeof(*priv) + size);
|
|
else
|
|
priv = calloc(1, sizeof(*priv) + size);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
off = priv->size;
|
|
priv->size = size;
|
|
|
|
array = (struct snd_soc_tplg_vendor_array *)(priv->data + off);
|
|
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;
|
|
elem_copy_text(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
elem->data = priv;
|
|
return 0;
|
|
}
|
|
|
|
/* build a data element from its tuples */
|
|
static int build_tuples(snd_tplg_t *tplg, struct tplg_elem *elem)
|
|
{
|
|
struct tplg_ref *ref;
|
|
struct list_head *base, *pos;
|
|
struct tplg_elem *tuples, *tokens;
|
|
|
|
base = &elem->ref_list;
|
|
list_for_each(pos, base) {
|
|
|
|
ref = list_entry(pos, struct tplg_ref, list);
|
|
|
|
if (!ref->id || ref->type != SND_TPLG_TYPE_TUPLE)
|
|
continue;
|
|
|
|
tplg_dbg("look up tuples %s\n", ref->id);
|
|
|
|
if (!ref->elem)
|
|
ref->elem = tplg_elem_lookup(&tplg->tuple_list,
|
|
ref->id, SND_TPLG_TYPE_TUPLE, elem->index);
|
|
tuples = ref->elem;
|
|
if (!tuples)
|
|
return -EINVAL;
|
|
|
|
tplg_dbg("found tuples %s\n", tuples->id);
|
|
tokens = get_tokens(tplg, tuples);
|
|
if (!tokens)
|
|
return -EINVAL;
|
|
|
|
tplg_dbg("found tokens %s\n", tokens->id);
|
|
/* a data object can only have one tuples object */
|
|
return copy_tuples(elem, tuples->tuples, tokens->tokens);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_tuple_set(snd_config_t *cfg,
|
|
struct tplg_tuple_set **s)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *id, *value;
|
|
struct tplg_tuple_set *set;
|
|
unsigned int type, num_tuples = 0;
|
|
struct tplg_tuple *tuple;
|
|
unsigned long int tuple_val;
|
|
|
|
snd_config_get_id(cfg, &id);
|
|
|
|
if (strncmp(id, "uuid", 4) == 0)
|
|
type = SND_SOC_TPLG_TUPLE_TYPE_UUID;
|
|
else if (strncmp(id, "string", 5) == 0)
|
|
type = SND_SOC_TPLG_TUPLE_TYPE_STRING;
|
|
else if (strncmp(id, "bool", 4) == 0)
|
|
type = SND_SOC_TPLG_TUPLE_TYPE_BOOL;
|
|
else if (strncmp(id, "byte", 4) == 0)
|
|
type = SND_SOC_TPLG_TUPLE_TYPE_BYTE;
|
|
else if (strncmp(id, "short", 5) == 0)
|
|
type = SND_SOC_TPLG_TUPLE_TYPE_SHORT;
|
|
else if (strncmp(id, "word", 4) == 0)
|
|
type = SND_SOC_TPLG_TUPLE_TYPE_WORD;
|
|
else {
|
|
SNDERR("error: invalid tuple type '%s'\n", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_config_for_each(i, next, cfg)
|
|
num_tuples++;
|
|
if (!num_tuples)
|
|
return 0;
|
|
|
|
tplg_dbg("\t %d %s tuples:\n", num_tuples, id);
|
|
set = calloc(1, sizeof(*set) + num_tuples * sizeof(struct tplg_tuple));
|
|
if (!set)
|
|
return -ENOMEM;
|
|
|
|
set->type = type;
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
|
|
/* get id */
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
continue;
|
|
|
|
/* get value */
|
|
if (snd_config_get_string(n, &value) < 0)
|
|
continue;
|
|
|
|
tuple = &set->tuple[set->num_tuples];
|
|
elem_copy_text(tuple->token, id,
|
|
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
|
|
|
|
switch (type) {
|
|
case SND_SOC_TPLG_TUPLE_TYPE_UUID:
|
|
if (get_uuid(value, tuple->uuid) < 0)
|
|
goto err;
|
|
break;
|
|
|
|
case SND_SOC_TPLG_TUPLE_TYPE_STRING:
|
|
elem_copy_text(tuple->string, value,
|
|
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
|
|
tplg_dbg("\t\t%s = %s\n", tuple->token, tuple->string);
|
|
break;
|
|
|
|
case SND_SOC_TPLG_TUPLE_TYPE_BOOL:
|
|
if (strcmp(value, "true") == 0)
|
|
tuple->value = 1;
|
|
tplg_dbg("\t\t%s = %d\n", 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:
|
|
errno = 0;
|
|
/* no support for negative value */
|
|
tuple_val = strtoul(value, NULL, 0);
|
|
if ((errno == ERANGE && tuple_val == ULONG_MAX)
|
|
|| (errno != 0 && tuple_val == 0)) {
|
|
SNDERR("error: tuple %s:strtoul fail\n", id);
|
|
goto err;
|
|
}
|
|
|
|
if ((type == SND_SOC_TPLG_TUPLE_TYPE_WORD
|
|
&& tuple_val > UINT_MAX)
|
|
|| (type == SND_SOC_TPLG_TUPLE_TYPE_SHORT
|
|
&& tuple_val > USHRT_MAX)
|
|
|| (type == SND_SOC_TPLG_TUPLE_TYPE_BYTE
|
|
&& tuple_val > UCHAR_MAX)) {
|
|
SNDERR("error: tuple %s: invalid value\n", id);
|
|
goto err;
|
|
}
|
|
|
|
tuple->value = (unsigned int) tuple_val;
|
|
tplg_dbg("\t\t%s = 0x%x\n", tuple->token, tuple->value);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
set->num_tuples++;
|
|
}
|
|
|
|
*s = set;
|
|
return 0;
|
|
|
|
err:
|
|
free(set);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int parse_tuple_sets(snd_config_t *cfg,
|
|
struct tplg_vendor_tuples *tuples)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *id;
|
|
unsigned int num_tuple_sets = 0;
|
|
int err;
|
|
|
|
if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
|
|
if (snd_config_get_id(cfg, &id) >= 0)
|
|
SNDERR("error: compound type expected for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
num_tuple_sets++;
|
|
}
|
|
|
|
if (!num_tuple_sets)
|
|
return 0;
|
|
|
|
tuples->set = calloc(1, num_tuple_sets * sizeof(void *));
|
|
if (!tuples->set)
|
|
return -ENOMEM;
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
n = snd_config_iterator_entry(i);
|
|
if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("error: compound type expected for %s, is %d",
|
|
id, snd_config_get_type(n));
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = parse_tuple_set(n, &tuples->set[tuples->num_sets]);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* overlook empty tuple sets */
|
|
if (tuples->set[tuples->num_sets])
|
|
tuples->num_sets++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Parse private data references for the element, either a single data section
|
|
* or a list of data sections.
|
|
*/
|
|
int tplg_parse_data_refs(snd_config_t *cfg,
|
|
struct tplg_elem *elem)
|
|
{
|
|
snd_config_type_t type;
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *val = NULL;
|
|
|
|
type = snd_config_get_type(cfg);
|
|
|
|
/* refer to a single data section */
|
|
if (type == SND_CONFIG_TYPE_STRING) {
|
|
if (snd_config_get_string(cfg, &val) < 0)
|
|
return -EINVAL;
|
|
|
|
tplg_dbg("\tdata: %s\n", val);
|
|
return tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val);
|
|
}
|
|
|
|
if (type != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("error: compound type expected for %s", elem->id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* refer to a list of data sections */
|
|
snd_config_for_each(i, next, cfg) {
|
|
const char *val;
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
if (snd_config_get_string(n, &val) < 0)
|
|
continue;
|
|
|
|
tplg_dbg("\tdata: %s\n", val);
|
|
tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Parse vendor tokens
|
|
*/
|
|
int tplg_parse_tokens(snd_tplg_t *tplg, snd_config_t *cfg,
|
|
void *private ATTRIBUTE_UNUSED)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *id, *value;
|
|
struct tplg_elem *elem;
|
|
struct tplg_vendor_tokens *tokens;
|
|
int num_tokens = 0;
|
|
|
|
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_TOKEN);
|
|
if (!elem)
|
|
return -ENOMEM;
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
num_tokens++;
|
|
}
|
|
|
|
if (!num_tokens)
|
|
return 0;
|
|
|
|
tplg_dbg(" Vendor tokens: %s, %d tokens\n", elem->id, num_tokens);
|
|
|
|
tokens = calloc(1, sizeof(*tokens)
|
|
+ num_tokens * sizeof(struct tplg_token));
|
|
if (!tokens)
|
|
return -ENOMEM;
|
|
elem->tokens = tokens;
|
|
|
|
snd_config_for_each(i, next, cfg) {
|
|
|
|
n = snd_config_iterator_entry(i);
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
continue;
|
|
|
|
if (snd_config_get_string(n, &value) < 0)
|
|
continue;
|
|
|
|
elem_copy_text(tokens->token[tokens->num_tokens].id, id,
|
|
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
|
|
tokens->token[tokens->num_tokens].value = atoi(value);
|
|
tplg_dbg("\t\t %s : %d\n", tokens->token[tokens->num_tokens].id,
|
|
tokens->token[tokens->num_tokens].value);
|
|
tokens->num_tokens++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Parse vendor tuples.
|
|
*/
|
|
int tplg_parse_tuples(snd_tplg_t *tplg, snd_config_t *cfg,
|
|
void *private ATTRIBUTE_UNUSED)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *id, *value;
|
|
struct tplg_elem *elem;
|
|
struct tplg_vendor_tuples *tuples;
|
|
int err;
|
|
|
|
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_TUPLE);
|
|
if (!elem)
|
|
return -ENOMEM;
|
|
|
|
tplg_dbg(" Vendor Tuples: %s\n", elem->id);
|
|
|
|
tuples = calloc(1, sizeof(*tuples));
|
|
if (!tuples)
|
|
return -ENOMEM;
|
|
elem->tuples = tuples;
|
|
|
|
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, "tokens") == 0) {
|
|
if (snd_config_get_string(n, &value) < 0)
|
|
return -EINVAL;
|
|
tplg_ref_add(elem, SND_TPLG_TYPE_TOKEN, value);
|
|
tplg_dbg("\t refer to vendor tokens: %s\n", value);
|
|
}
|
|
|
|
if (strcmp(id, "tuples") == 0) {
|
|
err = parse_tuple_sets(n, tuples);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Free handler of tuples */
|
|
void tplg_free_tuples(void *obj)
|
|
{
|
|
struct tplg_vendor_tuples *tuples = (struct tplg_vendor_tuples *)obj;
|
|
unsigned int i;
|
|
|
|
if (!tuples || !tuples->set)
|
|
return;
|
|
|
|
for (i = 0; i < tuples->num_sets; i++)
|
|
free(tuples->set[i]);
|
|
|
|
free(tuples->set);
|
|
}
|
|
|
|
/* Parse manifest's data references
|
|
*/
|
|
int tplg_parse_manifest_data(snd_tplg_t *tplg, snd_config_t *cfg,
|
|
void *private ATTRIBUTE_UNUSED)
|
|
{
|
|
struct snd_soc_tplg_manifest *manifest;
|
|
struct tplg_elem *elem;
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *id;
|
|
int err;
|
|
|
|
if (!list_empty(&tplg->manifest_list)) {
|
|
SNDERR("error: already has manifest data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_MANIFEST);
|
|
if (!elem)
|
|
return -ENOMEM;
|
|
|
|
manifest = elem->manifest;
|
|
manifest->size = elem->size;
|
|
|
|
tplg_dbg(" Manifest: %s\n", 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, "data") == 0) {
|
|
err = tplg_parse_data_refs(n, elem);
|
|
if (err < 0)
|
|
return err;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* merge private data of manifest */
|
|
int tplg_build_manifest_data(snd_tplg_t *tplg)
|
|
{
|
|
struct list_head *base, *pos;
|
|
struct tplg_elem *elem = NULL;
|
|
struct tplg_ref *ref;
|
|
struct snd_soc_tplg_manifest *manifest;
|
|
int err = 0;
|
|
|
|
base = &tplg->manifest_list;
|
|
list_for_each(pos, base) {
|
|
|
|
elem = list_entry(pos, struct tplg_elem, list);
|
|
break;
|
|
}
|
|
|
|
if (!elem) /* no manifest data */
|
|
return 0;
|
|
|
|
base = &elem->ref_list;
|
|
|
|
/* for each ref in this manifest elem */
|
|
list_for_each(pos, base) {
|
|
|
|
ref = list_entry(pos, struct tplg_ref, list);
|
|
if (ref->elem)
|
|
continue;
|
|
|
|
if (ref->type == SND_TPLG_TYPE_DATA) {
|
|
err = tplg_copy_data(tplg, elem, ref);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
manifest = elem->manifest;
|
|
if (!manifest->priv.size) /* no manifest data */
|
|
return 0;
|
|
|
|
tplg->manifest_pdata = malloc(manifest->priv.size);
|
|
if (!tplg->manifest_pdata)
|
|
return -ENOMEM;
|
|
|
|
tplg->manifest.priv.size = manifest->priv.size;
|
|
memcpy(tplg->manifest_pdata, manifest->priv.data, manifest->priv.size);
|
|
return 0;
|
|
}
|
|
|
|
/* Parse Private data.
|
|
*
|
|
* Object private data can either be from file or defined as bytes, shorts,
|
|
* words, tuples.
|
|
*/
|
|
int tplg_parse_data(snd_tplg_t *tplg, snd_config_t *cfg,
|
|
void *private ATTRIBUTE_UNUSED)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_config_t *n;
|
|
const char *id, *val = NULL;
|
|
int err = 0;
|
|
struct tplg_elem *elem;
|
|
|
|
elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_DATA);
|
|
if (!elem)
|
|
return -ENOMEM;
|
|
|
|
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, "file") == 0) {
|
|
err = tplg_parse_data_file(n, elem);
|
|
if (err < 0) {
|
|
SNDERR("error: failed to parse data file\n");
|
|
return err;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(id, "bytes") == 0) {
|
|
err = tplg_parse_data_hex(n, elem, 1);
|
|
if (err < 0) {
|
|
SNDERR("error: failed to parse data bytes\n");
|
|
return err;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(id, "shorts") == 0) {
|
|
err = tplg_parse_data_hex(n, elem, 2);
|
|
if (err < 0) {
|
|
SNDERR("error: failed to parse data shorts\n");
|
|
return err;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(id, "words") == 0) {
|
|
err = tplg_parse_data_hex(n, elem, 4);
|
|
if (err < 0) {
|
|
SNDERR("error: failed to parse data words\n");
|
|
return err;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(id, "tuples") == 0) {
|
|
if (snd_config_get_string(n, &val) < 0)
|
|
return -EINVAL;
|
|
tplg_dbg(" Data: %s\n", val);
|
|
tplg_ref_add(elem, SND_TPLG_TYPE_TUPLE, val);
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(id, "type") == 0) {
|
|
if (snd_config_get_string(n, &val) < 0)
|
|
return -EINVAL;
|
|
|
|
elem->vendor_type = atoi(val);
|
|
tplg_dbg("\t%s: %d\n", id, elem->index);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Find a referenced data element and copy its data to the parent
|
|
* element's private data buffer.
|
|
* An element can refer to multiple data sections. Data of these sections
|
|
* will be merged in the their reference order.
|
|
*/
|
|
int tplg_copy_data(snd_tplg_t *tplg, struct tplg_elem *elem,
|
|
struct tplg_ref *ref)
|
|
{
|
|
struct tplg_elem *ref_elem;
|
|
struct snd_soc_tplg_private *priv, *old_priv;
|
|
int priv_data_size, old_priv_data_size;
|
|
void *obj;
|
|
|
|
ref_elem = tplg_elem_lookup(&tplg->pdata_list,
|
|
ref->id, SND_TPLG_TYPE_DATA, elem->index);
|
|
if (!ref_elem) {
|
|
SNDERR("error: cannot find data '%s' referenced by"
|
|
" element '%s'\n", ref->id, elem->id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tplg_dbg("Data '%s' used by '%s'\n", ref->id, elem->id);
|
|
/* overlook empty private data */
|
|
if (!ref_elem->data || !ref_elem->data->size) {
|
|
ref->elem = ref_elem;
|
|
return 0;
|
|
}
|
|
|
|
old_priv = get_priv_data(elem);
|
|
if (!old_priv)
|
|
return -EINVAL;
|
|
old_priv_data_size = old_priv->size;
|
|
|
|
priv_data_size = ref_elem->data->size;
|
|
obj = realloc(elem->obj,
|
|
elem->size + priv_data_size);
|
|
if (!obj)
|
|
return -ENOMEM;
|
|
elem->obj = obj;
|
|
|
|
priv = get_priv_data(elem);
|
|
if (!priv)
|
|
return -EINVAL;
|
|
|
|
/* merge the new data block */
|
|
elem->size += priv_data_size;
|
|
priv->size = priv_data_size + old_priv_data_size;
|
|
ref_elem->compound_elem = 1;
|
|
memcpy(priv->data + old_priv_data_size,
|
|
ref_elem->data->data, priv_data_size);
|
|
|
|
ref->elem = ref_elem;
|
|
return 0;
|
|
}
|
|
|
|
/* check data objects and build those with tuples */
|
|
int tplg_build_data(snd_tplg_t *tplg)
|
|
{
|
|
struct list_head *base, *pos;
|
|
struct tplg_elem *elem;
|
|
int err = 0;
|
|
|
|
base = &tplg->pdata_list;
|
|
list_for_each(pos, base) {
|
|
|
|
elem = list_entry(pos, struct tplg_elem, list);
|
|
if (has_tuples(elem)) {
|
|
err = build_tuples(tplg, elem);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|