From: Jaroslav Kysela Date: Mon, 16 Dec 2019 13:26:31 +0000 (+0100) Subject: topology: add snd_tplg_save() X-Git-Tag: v1.2.2~36 X-Git-Url: https://git.alsa-project.org/?a=commitdiff_plain;h=aa1bac2d04bd1fb4ccae96a1136e60454298a710;p=alsa-lib.git topology: add snd_tplg_save() Signed-off-by: Jaroslav Kysela --- diff --git a/include/topology.h b/include/topology.h index c9f4ffea..69aa5ed7 100644 --- a/include/topology.h +++ b/include/topology.h @@ -1124,6 +1124,21 @@ int snd_tplg_set_manifest_data(snd_tplg_t *tplg, const void *data, int len); */ int snd_tplg_set_version(snd_tplg_t *tplg, unsigned int version); +/* + * Flags for the snd_tplg_save() + */ +#define SND_TPLG_SAVE_SORT (1<<0) /*!< sort identifiers */ +#define SND_TPLG_SAVE_GROUPS (1<<1) /*!< create the structure by group index */ +#define SND_TPLG_SAVE_NOCHECK (1<<16) /*!< unchecked output for debugging */ + +/** + * \brief Save the topology to the text configuration string. + * \param tplg Topology instance. + * \param dst A pointer to string with result (malloc). + * \return Zero on success, otherwise a negative error code + */ +int snd_tplg_save(snd_tplg_t *tplg, char **dst, int flags); + /* \} */ #ifdef __cplusplus diff --git a/src/conf.c b/src/conf.c index 3e753b26..c4db9f21 100644 --- a/src/conf.c +++ b/src/conf.c @@ -874,6 +874,21 @@ static int get_nonwhite(input_t *input) } } +static inline int get_hexachar(input_t *input) +{ + int c, num = 0; + + c = get_char(input); + if (c >= '0' && c <= '9') num |= (c - '0') << 4; + else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 4; + else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 4; + c = get_char(input); + if (c >= '0' && c <= '9') num |= (c - '0') << 0; + else if (c >= 'a' && c <= 'f') num |= (c - 'a') << 0; + else if (c >= 'A' && c <= 'F') num |= (c - 'A') << 0; + return c; +} + static int get_quotedchar(input_t *input) { int c; @@ -891,6 +906,8 @@ static int get_quotedchar(input_t *input) return '\r'; case 'f': return '\f'; + case 'x': + return get_hexachar(input); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { diff --git a/src/topology/Makefile.am b/src/topology/Makefile.am index 9dc472d6..a850ec4c 100644 --- a/src/topology/Makefile.am +++ b/src/topology/Makefile.am @@ -27,7 +27,8 @@ libatopology_la_SOURCES =\ text.c \ channel.c \ ops.c \ - elem.c + elem.c \ + save.c noinst_HEADERS = tplg_local.h diff --git a/src/topology/channel.c b/src/topology/channel.c index 4569eb31..b54a10c8 100644 --- a/src/topology/channel.c +++ b/src/topology/channel.c @@ -73,6 +73,18 @@ static int lookup_channel(const char *c) return -EINVAL; } +const char *tplg_channel_name(int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(channel_map); i++) { + if (channel_map[i].id == type) + return channel_map[i].name; + } + + return NULL; +} + /* Parse a channel mapping. */ int tplg_parse_channel(snd_tplg_t *tplg, snd_config_t *cfg, void *private) @@ -123,3 +135,36 @@ int tplg_parse_channel(snd_tplg_t *tplg, snd_config_t *cfg, tplg->channel_idx++; return 0; } + +int tplg_save_channels(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct snd_soc_tplg_channel *channel, + unsigned int count, char **dst, const char *pfx) +{ + struct snd_soc_tplg_channel *c; + const char *s; + unsigned int index; + int err; + + if (count == 0) + return 0; + err = tplg_save_printf(dst, pfx, "channel {\n"); + for (index = 0; err >= 0 && index < count; index++) { + c = channel + index; + s = tplg_channel_name(c->id); + if (s == NULL) + err = tplg_save_printf(dst, pfx, "\t%u", c->id); + else + err = tplg_save_printf(dst, pfx, "\t%s", s); + if (err >= 0) + err = tplg_save_printf(dst, NULL, " {\n"); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\t\treg %d\n", c->reg); + if (err >= 0 && c->shift > 0) + err = tplg_save_printf(dst, pfx, "\t\tshift %u\n", c->shift); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\t}\n"); + } + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} diff --git a/src/topology/ctl.c b/src/topology/ctl.c index 539329cd..979cc1b0 100644 --- a/src/topology/ctl.c +++ b/src/topology/ctl.c @@ -28,15 +28,16 @@ struct ctl_access_elem { }; /* CTL access strings and codes */ +/* place the multi-bit values on top - like read_write - for save */ static const struct ctl_access_elem ctl_access[] = { + {"read_write", SNDRV_CTL_ELEM_ACCESS_READWRITE}, + {"tlv_read_write", SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE}, {"read", SNDRV_CTL_ELEM_ACCESS_READ}, {"write", SNDRV_CTL_ELEM_ACCESS_WRITE}, - {"read_write", SNDRV_CTL_ELEM_ACCESS_READWRITE}, {"volatile", SNDRV_CTL_ELEM_ACCESS_VOLATILE}, {"timestamp", SNDRV_CTL_ELEM_ACCESS_TIMESTAMP}, {"tlv_read", SNDRV_CTL_ELEM_ACCESS_TLV_READ}, {"tlv_write", SNDRV_CTL_ELEM_ACCESS_TLV_WRITE}, - {"tlv_read_write", SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE}, {"tlv_command", SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND}, {"inactive", SNDRV_CTL_ELEM_ACCESS_INACTIVE}, {"lock", SNDRV_CTL_ELEM_ACCESS_LOCK}, @@ -103,6 +104,46 @@ int parse_access(snd_config_t *cfg, return err; } +/* Save Access */ +static int tplg_save_access(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct snd_soc_tplg_ctl_hdr *hdr, char **dst, + const char *pfx) +{ + const char *last; + unsigned int j, count, access, cval; + int err; + + if (hdr->access == 0) + return 0; + + access = hdr->access; + for (j = 0, count = 0, last = NULL; j < ARRAY_SIZE(ctl_access); j++) { + cval = ctl_access[j].value; + if ((access & cval) == cval) { + access &= ~cval; + last = ctl_access[j].name; + count++; + } + } + if (count == 1) + return tplg_save_printf(dst, pfx, "access.0 %s\n", last); + err = tplg_save_printf(dst, pfx, "access [\n"); + if (err < 0) + return err; + access = hdr->access; + for (j = 0; j < ARRAY_SIZE(ctl_access); j++) { + cval = ctl_access[j].value; + if ((access & cval) == cval) { + err = tplg_save_printf(dst, pfx, "\t%s\n", + ctl_access[j].name); + if (err < 0) + return err; + access &= ~cval; + } + } + return tplg_save_printf(dst, pfx, "]\n"); +} + /* copy referenced TLV to the mixer control */ static int copy_tlv(struct tplg_elem *elem, struct tplg_elem *ref) { @@ -358,6 +399,37 @@ int tplg_parse_tlv(snd_tplg_t *tplg, snd_config_t *cfg, return err; } +/* save TLV data */ +int tplg_save_tlv(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_ctl_tlv *tlv = elem->tlv; + struct snd_soc_tplg_tlv_dbscale *scale; + int err; + + if (tlv->type != SNDRV_CTL_TLVT_DB_SCALE) { + SNDERR("unknown TLV type"); + return -EINVAL; + } + + scale = &tlv->scale; + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\tscale {\n"); + if (err >= 0 && scale->min) + err = tplg_save_printf(dst, pfx, "\t\tmin %i\n", scale->min); + if (err >= 0 && scale->step > 0) + err = tplg_save_printf(dst, pfx, "\t\tstep %i\n", scale->step); + if (err >= 0 && scale->mute > 0) + err = tplg_save_printf(dst, pfx, "\t\tmute %i\n", scale->mute); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\t}\n"); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} + /* Parse Control Bytes */ int tplg_parse_control_bytes(snd_tplg_t *tplg, snd_config_t *cfg, @@ -430,7 +502,7 @@ int tplg_parse_control_bytes(snd_tplg_t *tplg, } if (strcmp(id, "data") == 0) { - err = tplg_parse_data_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); if (err < 0) return err; continue; @@ -485,6 +557,49 @@ int tplg_parse_control_bytes(snd_tplg_t *tplg, return 0; } +/* save control bytes */ +int tplg_save_control_bytes(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_bytes_control *be = elem->bytes_ext; + char pfx2[16]; + int err; + + if (!be) + return 0; + + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err < 0) + return err; + if (err >= 0 && elem->index > 0) + err = tplg_save_printf(dst, pfx, "\tindex %u\n", elem->index); + if (err >= 0 && be->base > 0) + err = tplg_save_printf(dst, pfx, "\tbase %u\n", be->base); + if (err >= 0 && be->num_regs > 0) + err = tplg_save_printf(dst, pfx, "\tnum_regs %u\n", be->num_regs); + if (err >= 0 && be->max > 0) + err = tplg_save_printf(dst, pfx, "\tmax %u\n", be->max); + if (err >= 0 && be->mask > 0) + err = tplg_save_printf(dst, pfx, "\tmask %u\n", be->mask); + if (err >= 0) + err = tplg_save_ops(tplg, &be->hdr, dst, pfx2); + if (err >= 0) + err = tplg_save_ext_ops(tplg, be, dst, pfx2); + if (err >= 0) + err = tplg_save_access(tplg, &be->hdr, dst, pfx2); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TLV, + "tlv", dst, pfx2); + 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 Control Enums. */ int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) @@ -559,7 +674,7 @@ int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg, } if (strcmp(id, "data") == 0) { - err = tplg_parse_data_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); if (err < 0) return err; continue; @@ -582,6 +697,42 @@ int tplg_parse_control_enum(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save control eunm */ +int tplg_save_control_enum(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_enum_control *ec = elem->enum_ctrl; + char pfx2[16]; + int err; + + if (!ec) + return 0; + + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err < 0) + return err; + if (err >= 0 && elem->index > 0) + err = tplg_save_printf(dst, pfx, "\tindex %u\n", elem->index); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TEXT, + "texts", dst, pfx2); + if (err >= 0) + err = tplg_save_channels(tplg, ec->channel, ec->num_channels, + dst, pfx2); + if (err >= 0) + err = tplg_save_ops(tplg, &ec->hdr, dst, pfx2); + if (err >= 0) + err = tplg_save_access(tplg, &ec->hdr, dst, pfx2); + 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 Controls. * * Mixer control. Supports multiple channels. @@ -683,7 +834,7 @@ int tplg_parse_control_mixer(snd_tplg_t *tplg, } if (strcmp(id, "data") == 0) { - err = tplg_parse_data_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); if (err < 0) return err; continue; @@ -709,6 +860,46 @@ int tplg_parse_control_mixer(snd_tplg_t *tplg, return 0; } +int tplg_save_control_mixer(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, char **dst, + const char *pfx) +{ + struct snd_soc_tplg_mixer_control *mc = elem->mixer_ctrl; + char pfx2[16]; + int err; + + if (!mc) + return 0; + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err < 0) + return err; + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + if (err >= 0 && elem->index > 0) + err = tplg_save_printf(dst, pfx, "\tindex %u\n", elem->index); + if (err >= 0) + err = tplg_save_channels(tplg, mc->channel, mc->num_channels, + dst, pfx2); + if (err >= 0 && mc->max > 0) + err = tplg_save_printf(dst, pfx, "\tmax %u\n", mc->max); + if (err >= 0 && mc->invert > 0) + err = tplg_save_printf(dst, pfx, "\tinvert 1\n", mc->max); + if (err >= 0 && mc->invert > 0) + err = tplg_save_printf(dst, pfx, "\tinvert 1\n", mc->max); + if (err >= 0) + err = tplg_save_ops(tplg, &mc->hdr, dst, pfx2); + if (err >= 0) + err = tplg_save_access(tplg, &mc->hdr, dst, pfx2); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TLV, + "tlv", dst, pfx2); + 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; +} + static int init_ctl_hdr(struct snd_soc_tplg_ctl_hdr *hdr, struct snd_tplg_ctl_template *t) { diff --git a/src/topology/dapm.c b/src/topology/dapm.c index ad709210..2bdacedc 100644 --- a/src/topology/dapm.c +++ b/src/topology/dapm.c @@ -43,7 +43,6 @@ static const struct map_elem widget_map[] = { {"effect", SND_SOC_TPLG_DAPM_EFFECT}, {"siggen", SND_SOC_TPLG_DAPM_SIGGEN}, {"src", SND_SOC_TPLG_DAPM_SRC}, - {"asrc", SND_SOC_TPLG_DAPM_ASRC}, {"encoder", SND_SOC_TPLG_DAPM_ENCODER}, {"decoder", SND_SOC_TPLG_DAPM_DECODER}, }; @@ -60,70 +59,16 @@ static int lookup_widget(const char *w) return -EINVAL; } -static int tplg_parse_dapm_mixers(snd_config_t *cfg, struct tplg_elem *elem) +static const char *get_widget_name(unsigned int type) { - snd_config_iterator_t i, next; - snd_config_t *n; - const char *value = NULL; - - tplg_dbg(" DAPM Mixer Controls: %s\n", elem->id); - - snd_config_for_each(i, next, cfg) { - n = snd_config_iterator_entry(i); - - /* get value */ - if (snd_config_get_string(n, &value) < 0) - continue; - - tplg_ref_add(elem, SND_TPLG_TYPE_MIXER, value); - tplg_dbg("\t\t %s\n", value); - } - - return 0; -} - -static int tplg_parse_dapm_enums(snd_config_t *cfg, struct tplg_elem *elem) -{ - snd_config_iterator_t i, next; - snd_config_t *n; - const char *value = NULL; - - tplg_dbg(" DAPM Enum Controls: %s\n", elem->id); - - snd_config_for_each(i, next, cfg) { - n = snd_config_iterator_entry(i); - - /* get value */ - if (snd_config_get_string(n, &value) < 0) - continue; - - tplg_ref_add(elem, SND_TPLG_TYPE_ENUM, value); - tplg_dbg("\t\t %s\n", value); - } - - return 0; -} - -static int tplg_parse_dapm_bytes(snd_config_t *cfg, struct tplg_elem *elem) -{ - snd_config_iterator_t i, next; - snd_config_t *n; - const char *value = NULL; - - tplg_dbg(" DAPM Bytes Controls: %s\n", elem->id); - - snd_config_for_each(i, next, cfg) { - n = snd_config_iterator_entry(i); - - /* get value */ - if (snd_config_get_string(n, &value) < 0) - continue; + unsigned int i; - tplg_ref_add(elem, SND_TPLG_TYPE_BYTES, value); - tplg_dbg("\t\t %s\n", value); + for (i = 0; i < ARRAY_SIZE(widget_map); i++) { + if ((unsigned int)widget_map[i].id == type) + return widget_map[i].name; } - return 0; + return NULL; } /* move referenced controls to the widget */ @@ -340,7 +285,7 @@ struct tplg_elem *tplg_elem_new_route(snd_tplg_t *tplg, int index) #define LINE_SIZE 1024 -/* line is defined as '"source, control, sink"' */ +/* line is defined as '"sink, control, source"' */ static int tplg_parse_line(const char *text, struct snd_soc_tplg_dapm_graph_elem *line) { @@ -470,6 +415,77 @@ int tplg_parse_dapm_graph(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save DAPM graph */ +int tplg_save_dapm_graph(snd_tplg_t *tplg, int index, char **dst, const char *pfx) +{ + struct snd_soc_tplg_dapm_graph_elem *route; + struct list_head *pos; + struct tplg_elem *elem; + int err, first = 1, old_index = -1; + unsigned block = -1, count = 0; + + list_for_each(pos, &tplg->route_list) { + elem = list_entry(pos, struct tplg_elem, list); + if (!elem->route || elem->type != SND_TPLG_TYPE_DAPM_GRAPH) + continue; + if (index >= 0 && elem->index != index) + continue; + count++; + } + if (count == 0) + return 0; + err = tplg_save_printf(dst, pfx, "SectionGraph {\n"); + list_for_each(pos, &tplg->route_list) { + elem = list_entry(pos, struct tplg_elem, list); + if (!elem->route || elem->type != SND_TPLG_TYPE_DAPM_GRAPH) + continue; + if (index >= 0 && elem->index != index) + continue; + if (old_index != elem->index) { + if (old_index >= 0) { + err = tplg_save_printf(dst, pfx, "\t\t]\n"); + if (err < 0) + return err; + err = tplg_save_printf(dst, pfx, "\t}\n"); + if (err < 0) + return err; + } + old_index = elem->index; + block++; + first = 1; + err = tplg_save_printf(dst, pfx, "\tset%u {\n", block); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\t\tindex %u\n", + elem->index); + if (err < 0) + return err; + } + if (first) { + first = 0; + err = tplg_save_printf(dst, pfx, "\t\tlines [\n", elem->index); + if (err < 0) + return err; + } + route = elem->route; + err = tplg_save_printf(dst, pfx, "\t\t\t'%s, %s, %s'\n", + route->sink, route->control, + route->source); + if (err < 0) + return err; + } + + if (!first) { + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\t\t]\n"); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\t}\n"); + } + + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} + /* DAPM Widget */ int tplg_parse_dapm_widget(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) @@ -595,7 +611,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg, } if (strcmp(id, "enum") == 0) { - err = tplg_parse_dapm_enums(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_ENUM); if (err < 0) return err; @@ -603,7 +619,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg, } if (strcmp(id, "mixer") == 0) { - err = tplg_parse_dapm_mixers(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_MIXER); if (err < 0) return err; @@ -611,7 +627,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg, } if (strcmp(id, "bytes") == 0) { - err = tplg_parse_dapm_bytes(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_BYTES); if (err < 0) return err; @@ -619,7 +635,7 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg, } if (strcmp(id, "data") == 0) { - err = tplg_parse_data_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); if (err < 0) return err; continue; @@ -629,6 +645,66 @@ int tplg_parse_dapm_widget(snd_tplg_t *tplg, return 0; } +/* save DAPM widget */ +int tplg_save_dapm_widget(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_dapm_widget *widget = elem->widget; + const char *s; + char pfx2[16]; + int err; + + 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) { + s = get_widget_name(widget->id); + if (s) + err = tplg_save_printf(dst, pfx, "\ttype %s\n", s); + else + err = tplg_save_printf(dst, pfx, "\ttype %u\n", + widget->id); + } + if (err >= 0 && widget->sname[0]) + err = tplg_save_printf(dst, pfx, "\tstream_name '%s'\n", + widget->sname); + if (err >= 0 && widget->reg) + err = tplg_save_printf(dst, pfx, "\tno_pm 1\n"); + if (err >= 0 && widget->shift) + err = tplg_save_printf(dst, pfx, "\tshift %u\n", + widget->shift); + if (err >= 0 && widget->invert) + err = tplg_save_printf(dst, pfx, "\tinvert %u\n", + widget->invert); + if (err >= 0 && widget->subseq) + err = tplg_save_printf(dst, pfx, "\tsubseq %u\n", + widget->subseq); + if (err >= 0 && widget->event_type) + err = tplg_save_printf(dst, pfx, "\tevent_type %u\n", + widget->event_type); + if (err >= 0 && widget->event_flags) + err = tplg_save_printf(dst, pfx, "\tevent_flags %u\n", + widget->event_flags); + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_ENUM, + "enum", dst, pfx2); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_MIXER, + "mixer", dst, pfx2); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_BYTES, + "bytes", dst, pfx2); + 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; +} + int tplg_add_route(snd_tplg_t *tplg, struct snd_tplg_graph_elem *t, int index) { struct tplg_elem *elem; @@ -744,7 +820,6 @@ int tplg_add_widget_object(snd_tplg_t *tplg, snd_tplg_obj_template_t *t) default: SNDERR("error: widget %s: invalid type %d for ctl %d\n", wt->name, ct->type, i); - ret = -EINVAL; break; } diff --git a/src/topology/data.c b/src/topology/data.c index 9807445e..11cd73f5 100644 --- a/src/topology/data.c +++ b/src/topology/data.c @@ -21,6 +21,10 @@ #include "tplg_local.h" #include +#define UUID_FORMAT "\ +0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, \ +0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x" + /* Get private data buffer of an element */ struct snd_soc_tplg_private *get_priv_data(struct tplg_elem *elem) { @@ -64,6 +68,96 @@ struct snd_soc_tplg_private *get_priv_data(struct tplg_elem *elem) return priv; } +/* Parse references for the element, either a single data section + * or a list of data sections. + */ +int tplg_parse_refs(snd_config_t *cfg, struct tplg_elem *elem, + unsigned int type) +{ + snd_config_type_t cfg_type; + snd_config_iterator_t i, next; + snd_config_t *n; + const char *val = NULL; + int err, count; + + cfg_type = snd_config_get_type(cfg); + + /* refer to a single data section */ + if (cfg_type == SND_CONFIG_TYPE_STRING) { + if (snd_config_get_string(cfg, &val) < 0) + return -EINVAL; + + tplg_dbg("\tref data: %s\n", val); + err = tplg_ref_add(elem, type, val); + if (err < 0) + return err; + return 1; + } + + if (cfg_type != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("error: compound type expected for %s", elem->id); + return -EINVAL; + } + + /* refer to a list of data sections */ + count = 0; + 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("\tref data: %s\n", val); + err = tplg_ref_add(elem, type, val); + if (err < 0) + return err; + count++; + } + + return count; +} + +/* save references */ +int tplg_save_refs(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, unsigned int type, + const char *id, char **dst, const char *pfx) +{ + struct tplg_ref *ref, *last; + struct list_head *pos; + int err, count; + + count = 0; + last = NULL; + list_for_each(pos, &elem->ref_list) { + ref = list_entry(pos, struct tplg_ref, list); + if (ref->type == type) { + last = ref; + count++; + } + } + + if (count == 0) + return 0; + + if (count == 1) + return tplg_save_printf(dst, pfx, "%s '%s'\n", id, last->id); + + err = tplg_save_printf(dst, pfx, "%s [\n", id); + if (err < 0) + return err; + list_for_each(pos, &elem->ref_list) { + ref = list_entry(pos, struct tplg_ref, list); + if (ref->type == type) { + err = tplg_save_printf(dst, pfx, "\t'%s'\n", ref->id); + if (err < 0) + return err; + } + } + + return tplg_save_printf(dst, pfx, "]\n"); +} + /* Get Private data from a file. */ static int tplg_parse_data_file(snd_config_t *cfg, struct tplg_elem *elem) { @@ -140,58 +234,98 @@ err: static void dump_priv_data(struct tplg_elem *elem) { struct snd_soc_tplg_private *priv = elem->data; - unsigned int i, j = 0; + unsigned int i; 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) + if (i > 0 && (i % 16) == 0) tplg_dbg("\n"); - tplg_dbg(" 0x%x", *p++); + tplg_dbg(" %02x:", *p++); } tplg_dbg("\n\n"); } +static inline int check_nibble(unsigned char c) +{ + return (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + /* 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; + int delims, values, len = strlen(str); + const char *s, *end = str + len; + + /* check "aa:bb:00" syntax */ + s = str; + delims = values = 0; + while (s < end) { + /* skip white space */ + if (isspace(*s)) { + s++; + continue; + } + /* find delimeters */ + if (*s == ':') { + delims++; + s++; + continue; + } + /* check 00 hexadecimal value */ + if (s + 1 <= end) { + if (check_nibble(s[0]) && check_nibble(s[1])) { + values++; + } else { + goto format2; + } + s++; + } + s++; + } + goto end; +format2: /* we expect "0x0, 0x0, 0x0" */ - while (str < end) { + s = str; + delims = values = 0; + while (s < end) { /* skip white space */ - if (isspace(*str)) { - str++; + if (isspace(*s)) { + s++; continue; } /* find delimeters */ - if (*str == ',') { - commas++; - str++; + if (*s == ',') { + delims++; + s++; continue; } /* find 0x[0-9] values */ - if (*str == '0' && str + 2 <= end) { - if (str[1] == 'x' && str[2] >= '0' && str[2] <= 'f') { + if (*s == '0' && s + 2 <= end) { + if (s[1] == 'x' && check_nibble(s[2])) { + if (check_nibble(s[3])) + s++; values++; - str += 3; - } else { - str++; + s += 2; } + s++; } - str++; + s++; } +end: /* there should always be one less comma than value */ - if (values -1 != commas) + if (values - 1 != delims) return -EINVAL; return values; @@ -547,6 +681,71 @@ static int build_tuples(snd_tplg_t *tplg, struct tplg_elem *elem) return 0; } +struct tuple_type { + unsigned int type; + const char *name; + unsigned int size; +}; + +static struct tuple_type tuple_types[] = { + { + .type = SND_SOC_TPLG_TUPLE_TYPE_UUID, + .name = "uuid", + .size = 4, + }, + { + .type = SND_SOC_TPLG_TUPLE_TYPE_STRING, + .name = "string", + .size = 6, + }, + { + .type = SND_SOC_TPLG_TUPLE_TYPE_BOOL, + .name = "bool", + .size = 4, + }, + { + .type = SND_SOC_TPLG_TUPLE_TYPE_BYTE, + .name = "byte", + .size = 4, + }, + { + .type = SND_SOC_TPLG_TUPLE_TYPE_SHORT, + .name = "short", + .size = 5, + }, + { + .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, + .name = "word", + .size = 4 + }, +}; + +static int get_tuple_type(const char *name) +{ + struct tuple_type *t; + unsigned int i; + + /* skip initial index for sorting */ + while ((*name >= '0' && *name <= '9') || *name == '_') + name++; + for (i = 0; i < ARRAY_SIZE(tuple_types); i++) { + t = &tuple_types[i]; + if (strncasecmp(t->name, name, t->size) == 0) + return t->type; + } + return -EINVAL; +} + +static const char *get_tuple_type_name(unsigned int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tuple_types); i++) + if (tuple_types[i].type == type) + return tuple_types[i].name; + return NULL; +} + static int parse_tuple_set(snd_config_t *cfg, struct tplg_tuple_set **s) { @@ -554,28 +753,17 @@ static int parse_tuple_set(snd_config_t *cfg, snd_config_t *n; const char *id, *value; struct tplg_tuple_set *set; - unsigned int type, num_tuples = 0; + unsigned int num_tuples = 0; struct tplg_tuple *tuple; unsigned int tuple_val; - int ival; + int type, ival; 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; + type = get_tuple_type(id); + if (type < 0) { + SNDERR("error: invalid tuple type '%s'", id); + return type; } snd_config_for_each(i, next, cfg) @@ -664,6 +852,84 @@ err: return -EINVAL; } +/* save tuple set */ +static int tplg_save_tuple_set(struct tplg_vendor_tuples *tuples, + unsigned int set_index, + char **dst, const char *pfx) +{ + struct tplg_tuple_set *set; + struct tplg_tuple *tuple; + const char *s, *fmt; + char buf[32]; + unsigned int i; + int err; + + set = tuples->set[set_index]; + if (set->num_tuples == 0) + return 0; + s = get_tuple_type_name(set->type); + if (s == NULL) + return -EINVAL; + if (tuples->num_sets < 10) + fmt = "%u_"; + else if (tuples->num_sets < 100) + fmt = "%02u_"; + else if (tuples->num_sets < 1000) + fmt = "%03u_"; + else + return -EINVAL; + if (set->num_tuples > 1) { + snprintf(buf, sizeof(buf), "tuples.%s%%s {\n", fmt); + err = tplg_save_printf(dst, NULL, buf, set_index, s); + if (err < 0) + return err; + } + for (i = 0; i < set->num_tuples; i++) { + tuple = &set->tuple[i]; + if (set->num_tuples == 1) { + snprintf(buf, sizeof(buf), "tuples.%s%%s.'%%s' ", fmt); + err = tplg_save_printf(dst, NULL, buf, + set_index, s, tuple->token); + } else { + err = tplg_save_printf(dst, pfx, "\t'%s' ", + tuple->token); + } + switch (set->type) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + err = tplg_save_printf(dst, NULL, "'" UUID_FORMAT "'\n", + tuple->uuid[0], tuple->uuid[1], + tuple->uuid[2], tuple->uuid[3], + tuple->uuid[4], tuple->uuid[5], + tuple->uuid[6], tuple->uuid[7], + tuple->uuid[8], tuple->uuid[9], + tuple->uuid[10], tuple->uuid[11], + tuple->uuid[12], tuple->uuid[13], + tuple->uuid[14], tuple->uuid[15]); + break; + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + err = tplg_save_printf(dst, NULL, "'%s'\n", + tuple->string); + break; + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + case SND_SOC_TPLG_TUPLE_TYPE_BYTE: + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + err = tplg_save_printf(dst, NULL, "%u\n", tuple->value); + break; + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + tplg_nice_value_format(buf, sizeof(buf), tuple->value); + err = tplg_save_printf(dst, NULL, "%s\n", buf); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + } + if (set->num_tuples > 1) + return tplg_save_printf(dst, pfx, "}\n"); + return 0; +} + static int parse_tuple_sets(snd_config_t *cfg, struct tplg_vendor_tuples *tuples) { @@ -710,87 +976,24 @@ static int parse_tuple_sets(snd_config_t *cfg, return 0; } -/* Parse tuples references for a data element, either a single tuples section - * or a list of tuples sections. - */ -static int parse_tuples_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 tuples section */ - if (type == SND_CONFIG_TYPE_STRING) { - if (snd_config_get_string(cfg, &val) < 0) - return -EINVAL; - tplg_dbg("\ttuples: %s\n", val); - return tplg_ref_add(elem, SND_TPLG_TYPE_TUPLE, 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("\ttuples: %s\n", val); - tplg_ref_add(elem, SND_TPLG_TYPE_TUPLE, val); - } - - 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) +/* save tuple sets */ +int tplg_save_tuple_sets(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) { - 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; + struct tplg_vendor_tuples *tuples = elem->tuples; + unsigned int i; + int err = 0; - tplg_dbg("\tdata: %s\n", val); - tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val); + for (i = 0; i < tuples->num_sets; i++) { + err = tplg_save_printf(dst, pfx, ""); + if (err < 0) + break; + err = tplg_save_tuple_set(tuples, i, dst, pfx); + if (err < 0) + break; } - - return 0; + return err; } /* Parse vendor tokens @@ -844,6 +1047,31 @@ int tplg_parse_tokens(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save vendor tokens */ +int tplg_save_tokens(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct tplg_vendor_tokens *tokens = elem->tokens; + unsigned int i; + int err; + + if (!tokens || tokens->num_tokens == 0) + return 0; + + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err < 0) + return err; + for (i = 0; err >= 0 && i < tokens->num_tokens; i++) + err = tplg_save_printf(dst, pfx, "\t'%s' %u\n", + tokens->token[i].id, + tokens->token[i].value); + err = tplg_save_printf(dst, pfx, "}\n"); + if (err < 0) + return err; + return 0; +} + /* Parse vendor tuples. */ int tplg_parse_tuples(snd_tplg_t *tplg, snd_config_t *cfg, @@ -890,6 +1118,29 @@ int tplg_parse_tuples(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save vendor tuples */ +int tplg_save_tuples(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + char pfx2[16]; + int err; + + if (!elem->tuples) + return 0; + + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TOKEN, + "tokens", dst, pfx2); + if (err >= 0) + err = tplg_save_tuple_sets(tplg, elem, dst, pfx2); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return 0; +} + /* Free handler of tuples */ void tplg_free_tuples(void *obj) { @@ -944,7 +1195,7 @@ int tplg_parse_manifest_data(snd_tplg_t *tplg, snd_config_t *cfg, if (strcmp(id, "data") == 0) { - err = tplg_parse_data_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); if (err < 0) return err; continue; @@ -954,6 +1205,51 @@ int tplg_parse_manifest_data(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save manifest data */ +int tplg_save_manifest_data(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, char **dst, + const char *pfx) +{ + struct list_head *pos; + struct tplg_ref *ref; + int err, index, count; + + /* for each ref in this manifest elem */ + count = 0; + list_for_each(pos, &elem->ref_list) { + ref = list_entry(pos, struct tplg_ref, list); + if (ref->type != SND_TPLG_TYPE_DATA) + continue; + count++; + } + if (count > 1) { + err = tplg_save_printf(dst, NULL, "'%s'.data [\n", elem->id); + if (err < 0) + return err; + } + index = 0; + list_for_each(pos, &elem->ref_list) { + ref = list_entry(pos, struct tplg_ref, list); + if (ref->type != SND_TPLG_TYPE_DATA) + continue; + if (count == 1) { + err = tplg_save_printf(dst, NULL, "'%s'.data.%u '%s'\n", + elem->id, index, ref->id); + } else { + err = tplg_save_printf(dst, pfx, "\t'%s'\n", ref->id); + if (err < 0) + return err; + } + index++; + } + if (count > 1) { + err = tplg_save_printf(dst, pfx, "]\n"); + if (err < 0) + return err; + } + return 0; +} + /* merge private data of manifest */ int tplg_build_manifest_data(snd_tplg_t *tplg) { @@ -1064,7 +1360,7 @@ int tplg_parse_data(snd_tplg_t *tplg, snd_config_t *cfg, } if (strcmp(id, "tuples") == 0) { - err = parse_tuples_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_TUPLE); if (err < 0) return err; continue; @@ -1083,6 +1379,81 @@ int tplg_parse_data(snd_tplg_t *tplg, snd_config_t *cfg, return err; } +/* save data element */ +int tplg_save_data(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_private *priv = elem->data; + struct list_head *pos; + struct tplg_ref *ref; + char pfx2[16]; + unsigned int i, count; + int err; + + count = 0; + if (priv && priv->size > 0) + count++; + list_for_each(pos, &elem->ref_list) { + ref = list_entry(pos, struct tplg_ref, list); + if (ref->type == SND_TPLG_TYPE_TUPLE) + count++; + } + if (elem->vendor_type > 0) + count++; + + if (count > 1) { + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err >= 0) + err = tplg_save_printf(dst, NULL, ""); + } else { + err = tplg_save_printf(dst, NULL, "'%s'.", elem->id); + } + if (err >= 0 && priv && priv->size > 0) { + if (count > 1) { + err = tplg_save_printf(dst, pfx, ""); + if (err < 0) + return err; + } + if (priv->size > 8) { + err = tplg_save_printf(dst, NULL, "bytes\n"); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "\t'"); + } else { + err = tplg_save_printf(dst, NULL, "bytes '"); + } + if (err < 0) + return err; + for (i = 0; i < priv->size; i++) { + if (i > 0 && (i % 8) == 0) { + err = tplg_save_printf(dst, NULL, ":\n"); + if (err < 0) + return err; + err = tplg_save_printf(dst, pfx, "\t "); + if (err < 0) + return err; + } + err = tplg_save_printf(dst, NULL, "%s%02x", + (i % 8) == 0 ? "" : ":", + (unsigned char)priv->data[i]); + if (err < 0) + return err; + } + err = tplg_save_printf(dst, NULL, "'\n"); + } + snprintf(pfx2, sizeof(pfx2), "%s\t", pfx ?: ""); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_TUPLE, + "tuples", dst, + count > 1 ? pfx2 : NULL); + if (err >= 0 && elem->vendor_type > 0) + err = tplg_save_printf(dst, pfx, "type %u", + elem->vendor_type); + if (err >= 0 && count > 1) + err = tplg_save_printf(dst, pfx, "}\n"); + 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 diff --git a/src/topology/elem.c b/src/topology/elem.c index e79a68b7..89aed1fc 100644 --- a/src/topology/elem.c +++ b/src/topology/elem.c @@ -30,6 +30,7 @@ struct tplg_table tplg_table[] = { .size = sizeof(struct snd_soc_tplg_manifest), .enew = 1, .parse = tplg_parse_manifest_data, + .save = tplg_save_manifest_data, }, { .name = "control mixer", @@ -41,6 +42,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_control_mixer, + .save = tplg_save_control_mixer, }, { .name = "control enum", @@ -52,6 +54,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_control_enum, + .save = tplg_save_control_enum, }, { .name = "control extended (bytes)", @@ -63,6 +66,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_control_bytes, + .save = tplg_save_control_bytes, }, { .name = "dapm widget", @@ -74,6 +78,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_dapm_widget, + .save = tplg_save_dapm_widget, }, { .name = "pcm", @@ -85,6 +90,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_pcm, + .save = tplg_save_pcm, }, { .name = "physical dai", @@ -96,6 +102,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_dai, + .save = tplg_save_dai, }, { .name = "be", @@ -108,6 +115,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_link, + .save = tplg_save_link, }, { .name = "cc", @@ -119,6 +127,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_cc, + .save = tplg_save_cc, }, { .name = "route (dapm graph)", @@ -128,6 +137,7 @@ struct tplg_table tplg_table[] = { .tsoc = SND_SOC_TPLG_TYPE_DAPM_GRAPH, .build = 1, .parse = tplg_parse_dapm_graph, + .gsave = tplg_save_dapm_graph, }, { .name = "private data", @@ -138,6 +148,7 @@ struct tplg_table tplg_table[] = { .build = 1, .enew = 1, .parse = tplg_parse_data, + .save = tplg_save_data, }, { .name = "text", @@ -147,6 +158,7 @@ struct tplg_table tplg_table[] = { .size = sizeof(struct tplg_texts), .enew = 1, .parse = tplg_parse_text, + .save = tplg_save_text, }, { .name = "tlv", @@ -156,6 +168,7 @@ struct tplg_table tplg_table[] = { .size = sizeof(struct snd_soc_tplg_ctl_tlv), .enew = 1, .parse = tplg_parse_tlv, + .save = tplg_save_tlv, }, { .name = "stream config", @@ -172,6 +185,7 @@ struct tplg_table tplg_table[] = { .size = sizeof(struct snd_soc_tplg_stream_caps), .enew = 1, .parse = tplg_parse_stream_caps, + .save = tplg_save_stream_caps, }, { .name = "token", @@ -180,6 +194,7 @@ struct tplg_table tplg_table[] = { .type = SND_TPLG_TYPE_TOKEN, .enew = 1, .parse = tplg_parse_tokens, + .save = tplg_save_tokens, }, { .name = "tuple", @@ -189,6 +204,7 @@ struct tplg_table tplg_table[] = { .free = tplg_free_tuples, .enew = 1, .parse = tplg_parse_tuples, + .save = tplg_save_tuples, }, { .name = "hw config", @@ -198,6 +214,7 @@ struct tplg_table tplg_table[] = { .size = sizeof(struct snd_soc_tplg_hw_config), .enew = 1, .parse = tplg_parse_hw_config, + .save = tplg_save_hw_config, } }; @@ -394,6 +411,7 @@ struct tplg_elem* tplg_elem_new_common(snd_tplg_t *tplg, tplg_elem_insert(elem, list); obj_size = tptr->size; elem->free = tptr->free; + elem->table = tptr; /* create new object too if required */ if (obj_size > 0) { diff --git a/src/topology/ops.c b/src/topology/ops.c index 073acdcb..ad72ef1b 100644 --- a/src/topology/ops.c +++ b/src/topology/ops.c @@ -45,6 +45,18 @@ static int lookup_ops(const char *c) return strtol(c, NULL, 0); } +const char *tplg_ops_name(int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(control_map); i++) { + if (control_map[i].id == type) + return control_map[i].name; + } + + return NULL; +} + /* Parse Control operations. Ops can come from standard names above or * bespoke driver controls with numbers >= 256 */ @@ -84,6 +96,46 @@ int tplg_parse_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, snd_config_t *cfg, return 0; } +/* save control operations */ +int tplg_save_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct snd_soc_tplg_ctl_hdr *hdr, char **dst, + const char *pfx) +{ + const char *s; + int err; + + if (hdr->ops.info + hdr->ops.get + hdr->ops.put == 0) + return 0; + err = tplg_save_printf(dst, pfx, "ops.0 {\n"); + if (err >= 0 && hdr->ops.info > 0) { + s = tplg_ops_name(hdr->ops.info); + if (s == NULL) + err = tplg_save_printf(dst, pfx, "\tinfo %u\n", + hdr->ops.info); + else + err = tplg_save_printf(dst, pfx, "\tinfo %s\n", s); + } + if (err >= 0 && hdr->ops.get > 0) { + s = tplg_ops_name(hdr->ops.get); + if (s == NULL) + err = tplg_save_printf(dst, pfx, "\tget %u\n", + hdr->ops.get); + else + err = tplg_save_printf(dst, pfx, "\tget %s\n", s); + } + if (err >= 0 && hdr->ops.put > 0) { + s = tplg_ops_name(hdr->ops.put); + if (s == NULL) + err = tplg_save_printf(dst, pfx, "\tput %u\n", + hdr->ops.put); + else + err = tplg_save_printf(dst, pfx, "\tput %s\n", s); + } + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} + /* Parse External Control operations. Ops can come from standard names above or * bespoke driver controls with numbers >= 256 */ @@ -121,3 +173,43 @@ int tplg_parse_ext_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, return 0; } + +/* save external control operations */ +int tplg_save_ext_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct snd_soc_tplg_bytes_control *be, + char **dst, const char *pfx) +{ + const char *s; + int err; + + if (be->ext_ops.info + be->ext_ops.get + be->ext_ops.put == 0) + return 0; + err = tplg_save_printf(dst, pfx, "extops.0 {\n"); + if (err >= 0 && be->ext_ops.info > 0) { + s = tplg_ops_name(be->ext_ops.info); + if (s == NULL) + err = tplg_save_printf(dst, pfx, "\tinfo %u\n", + be->ext_ops.info); + else + err = tplg_save_printf(dst, pfx, "\tinfo %s\n", s); + } + if (err >= 0 && be->ext_ops.get > 0) { + s = tplg_ops_name(be->ext_ops.get); + if (s == NULL) + err = tplg_save_printf(dst, pfx, "\tget %u\n", + be->ext_ops.get); + else + err = tplg_save_printf(dst, pfx, "\tget %s\n", s); + } + if (err >= 0 && be->ext_ops.put > 0) { + s = tplg_ops_name(be->ext_ops.put); + if (s == NULL) + err = tplg_save_printf(dst, pfx, "\tput %u\n", + be->ext_ops.put); + else + err = tplg_save_printf(dst, pfx, "\tput %s\n", s); + } + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} diff --git a/src/topology/parser.c b/src/topology/parser.c index 11202769..de5edd1b 100644 --- a/src/topology/parser.c +++ b/src/topology/parser.c @@ -71,6 +71,8 @@ int tplg_get_unsigned(snd_config_t *n, unsigned *val, int base) err = snd_config_get_integer(n, &lval); if (err < 0) return err; + if (lval < 0 && lval >= INT_MIN) + lval = UINT_MAX + lval + 1; if (lval < 0 || lval > UINT_MAX) return -ERANGE; *val = lval; @@ -79,6 +81,8 @@ int tplg_get_unsigned(snd_config_t *n, unsigned *val, int base) err = snd_config_get_integer64(n, &llval); if (err < 0) return err; + if (llval < 0 && llval >= INT_MIN) + llval = UINT_MAX + llval + 1; if (llval < 0 || llval > UINT_MAX) return -ERANGE; *val = llval; diff --git a/src/topology/pcm.c b/src/topology/pcm.c index 9b87549c..d09fbe42 100644 --- a/src/topology/pcm.c +++ b/src/topology/pcm.c @@ -345,6 +345,13 @@ static int get_rate_value(const char* name) return SND_PCM_RATE_UNKNOWN; } +static const char *get_rate_name(int rate) +{ + if (rate >= 0 && rate <= SND_PCM_RATE_LAST) + return snd_pcm_rate_names[rate]; + return NULL; +} + static int split_rate(struct snd_soc_tplg_stream_caps *caps, char *str) { char *s = NULL; @@ -527,6 +534,80 @@ int tplg_parse_stream_caps(snd_tplg_t *tplg, return 0; } +/* save stream caps */ +int tplg_save_stream_caps(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_stream_caps *sc = elem->stream_caps; + const char *s; + unsigned int i; + int err, first; + + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err >= 0 && sc->formats) { + err = tplg_save_printf(dst, pfx, "\tformats '"); + first = 1; + for (i = 0; err >= 0 && i < SND_PCM_FORMAT_LAST; i++) { + if (sc->formats & (1ULL << i)) { + s = snd_pcm_format_name(i); + err = tplg_save_printf(dst, NULL, "%s%s", + !first ? ", " : "", s); + first = 0; + } + } + if (err >= 0) + err = tplg_save_printf(dst, NULL, "'\n"); + } + if (err >= 0 && sc->rates) { + err = tplg_save_printf(dst, pfx, "\trates '"); + first = 1; + for (i = 0; err >= 0 && i < SND_PCM_RATE_LAST; i++) { + if (sc->rates & (1ULL << i)) { + s = get_rate_name(i); + err = tplg_save_printf(dst, NULL, "%s%s", + !first ? ", " : "", s); + first = 0; + } + } + if (err >= 0) + err = tplg_save_printf(dst, NULL, "'\n"); + } + if (err >= 0 && sc->rate_min) + err = tplg_save_printf(dst, pfx, "\trate_min %u\n", + sc->rate_min); + if (err >= 0 && sc->rate_max) + err = tplg_save_printf(dst, pfx, "\trate_max %u\n", + sc->rate_max); + if (err >= 0 && sc->channels_min) + err = tplg_save_printf(dst, pfx, "\tchannels_min %u\n", + sc->channels_min); + if (err >= 0 && sc->channels_max) + err = tplg_save_printf(dst, pfx, "\tchannels_max %u\n", + sc->channels_max); + if (err >= 0 && sc->periods_min) + err = tplg_save_printf(dst, pfx, "\tperiods_min %u\n", + sc->periods_min); + if (err >= 0 && sc->periods_max) + err = tplg_save_printf(dst, pfx, "\tperiods_max %u\n", + sc->periods_max); + if (err >= 0 && sc->period_size_min) + err = tplg_save_printf(dst, pfx, "\tperiod_size_min %u\n", + sc->period_size_min); + if (err >= 0 && sc->period_size_max) + err = tplg_save_printf(dst, pfx, "\tperiod_size_max %u\n", + sc->period_size_max); + if (err >= 0 && sc->buffer_size_min) + err = tplg_save_printf(dst, pfx, "\tbuffer_size_min %u\n", + sc->buffer_size_min); + if (err >= 0 && sc->buffer_size_max) + err = tplg_save_printf(dst, pfx, "\tbuffer_size_max %u\n", + sc->buffer_size_max); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} + /* Parse the caps and config of a pcm stream */ static int tplg_parse_streams(snd_tplg_t *tplg ATTRIBUTE_UNUSED, snd_config_t *cfg, void *private) @@ -598,6 +679,61 @@ static int tplg_parse_streams(snd_tplg_t *tplg ATTRIBUTE_UNUSED, return 0; } +/* Save the caps and config of a pcm stream */ +int tplg_save_streams(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + static const char *stream_ids[2] = { + "playback", + "capture" + }; + static unsigned int stream_types[2] = { + SND_SOC_TPLG_STREAM_PLAYBACK, + SND_SOC_TPLG_STREAM_CAPTURE + }; + struct snd_soc_tplg_stream_caps *caps; + unsigned int streams[2], stream; + const char *s; + int err; + + switch (elem->type) { + case SND_TPLG_TYPE_PCM: + streams[0] = elem->pcm->playback; + streams[1] = elem->pcm->capture; + caps = elem->pcm->caps; + break; + case SND_TPLG_TYPE_DAI: + streams[0] = elem->dai->playback; + streams[1] = elem->dai->capture; + caps = elem->dai->caps; + break; + default: + return -EINVAL; + } + + for (stream = 0; stream < 2; stream++) { + if (streams[stream] == 0) + continue; + if (!caps) + continue; + s = caps[stream_types[stream]].name; + if (s[0] == '\0') + continue; + err = tplg_save_printf(dst, pfx, "pcm.%s {\n", stream_ids[stream]); + if (err < 0) + return err; + err = tplg_save_printf(dst, pfx, "\tcapabilities '%s'\n", s); + if (err < 0) + return err; + err = tplg_save_printf(dst, pfx, "}\n"); + if (err < 0) + return err; + } + + return 0; +} + /* Parse name and id of a front-end DAI (ie. cpu dai of a FE DAI link) */ static int tplg_parse_fe_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED, snd_config_t *cfg, void *private) @@ -633,6 +769,19 @@ static int tplg_parse_fe_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED, return 0; } +/* Save the caps and config of a pcm stream */ +int tplg_save_fe_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_pcm *pcm = elem->pcm; + int err = 0; + + if (pcm->dai_id > 0) + err = tplg_save_printf(dst, pfx, "dai.0.id %u\n", pcm->dai_id); + return err; +} + /* parse a flag bit of the given mask */ static int parse_flag(snd_config_t *n, unsigned int mask_in, unsigned int *mask, unsigned int *flags) @@ -652,6 +801,32 @@ static int parse_flag(snd_config_t *n, unsigned int mask_in, return 0; } +static int save_flags(unsigned int flags, unsigned int mask, + char **dst, const char *pfx) +{ + static unsigned int flag_masks[3] = { + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS, + SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS, + }; + static const char *flag_ids[3] = { + "symmetric_rates", + "symmetric_channels", + "symmetric_sample_bits", + }; + unsigned int i; + int err = 0; + + for (i = 0; err >= 0 && i < ARRAY_SIZE(flag_masks); i++) { + if (mask & flag_masks[i]) { + unsigned int v = (flags & flag_masks[i]) ? 1 : 0; + err = tplg_save_printf(dst, pfx, "%s %u\n", + flag_ids[i], v); + } + } + return err; +} + /* 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) @@ -748,7 +923,7 @@ int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg, /* private data */ if (strcmp(id, "data") == 0) { - err = tplg_parse_data_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); if (err < 0) return err; continue; @@ -758,6 +933,40 @@ int tplg_parse_pcm(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save PCM */ +int tplg_save_pcm(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **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; +} + /* Parse physical DAI */ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) @@ -766,7 +975,7 @@ int tplg_parse_dai(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; + const char *id; int err; elem = tplg_elem_new_common(tplg, cfg, NULL, SND_TPLG_TYPE_DAI); @@ -851,11 +1060,9 @@ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg, /* private data */ if (strcmp(id, "data") == 0) { - if (snd_config_get_string(n, &val) < 0) - return -EINVAL; - - tplg_ref_add(elem, SND_TPLG_TYPE_DATA, val); - tplg_dbg("\t%s: %s\n", id, val); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); + if (err < 0) + return err; continue; } } @@ -863,55 +1070,55 @@ int tplg_parse_dai(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save DAI */ +int tplg_save_dai(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_dai *dai = elem->dai; + char pfx2[16]; + int err; + + if (!dai) + return 0; + 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 && dai->dai_id) + err = tplg_save_printf(dst, pfx, "\tid %u\n", + dai->dai_id); + if (err >= 0 && dai->playback) + err = tplg_save_printf(dst, pfx, "\tplayback %u\n", + dai->playback); + if (err >= 0 && dai->capture) + err = tplg_save_printf(dst, pfx, "\tcapture %u\n", + dai->capture); + if (err >= 0) + err = tplg_save_streams(tplg, elem, dst, pfx2); + if (err >= 0) + err = save_flags(dai->flags, dai->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 link runtime supported HW configs in text conf file */ static int parse_hw_config_refs(snd_tplg_t *tplg ATTRIBUTE_UNUSED, snd_config_t *cfg, struct tplg_elem *elem) { struct snd_soc_tplg_link_config *link = elem->link; - snd_config_type_t type; - snd_config_iterator_t i, next; - snd_config_t *n; - const char *id, *val = NULL; - - if (snd_config_get_id(cfg, &id) < 0) - return -EINVAL; - type = snd_config_get_type(cfg); - - /* refer to a single HW config */ - if (type == SND_CONFIG_TYPE_STRING) { - if (snd_config_get_string(cfg, &val) < 0) - return -EINVAL; - - link->num_hw_configs = 1; - return tplg_ref_add(elem, SND_TPLG_TYPE_HW_CONFIG, val); - } - - if (type != SND_CONFIG_TYPE_COMPOUND) { - SNDERR("error: compound type expected for %s", id); - return -EINVAL; - } - - /* refer to a list of HW configs */ - snd_config_for_each(i, next, cfg) { - const char *val; - int err; - - n = snd_config_iterator_entry(i); - if (snd_config_get_string(n, &val) < 0) - continue; - - if (link->num_hw_configs >= SND_SOC_TPLG_HW_CONFIG_MAX) { - SNDERR("error: exceed max hw configs for link %s", id); - return -EINVAL; - } - - link->num_hw_configs++; - err = tplg_ref_add(elem, SND_TPLG_TYPE_HW_CONFIG, val); - if (err < 0) - return err; - } + int err; + err = tplg_parse_refs(cfg, elem, SND_TPLG_TYPE_HW_CONFIG); + if (err < 0) + return err; + link->num_hw_configs = err; return 0; } @@ -1007,7 +1214,7 @@ int tplg_parse_link(snd_tplg_t *tplg, /* private data */ if (strcmp(id, "data") == 0) { - err = tplg_parse_data_refs(n, elem); + err = tplg_parse_refs(n, elem, SND_TPLG_TYPE_DATA); if (err < 0) return err; continue; @@ -1017,6 +1224,44 @@ int tplg_parse_link(snd_tplg_t *tplg, return 0; } +/* save physical link */ +int tplg_save_link(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_link_config *link = elem->link; + char pfx2[16]; + int err; + + if (!link) + return 0; + 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 && link->id) + err = tplg_save_printf(dst, pfx, "\tid %u\n", + link->id); + if (err >= 0 && link->stream_name[0]) + err = tplg_save_printf(dst, pfx, "\tstream_name '%s'\n", + link->stream_name); + if (err >= 0 && link->default_hw_config_id) + err = tplg_save_printf(dst, pfx, "\tdefault_hw_conf_id %u\n", + link->default_hw_config_id); + if (err >= 0) + err = save_flags(link->flags, link->flag_mask, dst, pfx); + if (err >= 0) + err = tplg_save_refs(tplg, elem, SND_TPLG_TYPE_HW_CONFIG, + "hw_configs", dst, pfx2); + 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 cc */ int tplg_parse_cc(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) @@ -1059,36 +1304,95 @@ int tplg_parse_cc(snd_tplg_t *tplg, return 0; } -static int get_audio_hw_format(const char *val) +/* save CC */ +int tplg_save_cc(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) { - if (!strlen(val)) - return -EINVAL; - - if (!strcmp(val, "I2S")) - return SND_SOC_DAI_FORMAT_I2S; + struct snd_soc_tplg_link_config *link = elem->link; + char pfx2[16]; + int err; - if (!strcmp(val, "RIGHT_J")) - return SND_SOC_DAI_FORMAT_RIGHT_J; + if (!link) + return 0; + 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 && link->id) + err = tplg_save_printf(dst, pfx, "\tid %u\n", + link->id); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} - if (!strcmp(val, "LEFT_J")) - return SND_SOC_DAI_FORMAT_LEFT_J; +struct audio_hw_format { + unsigned int type; + const char *name; +}; - if (!strcmp(val, "DSP_A")) - return SND_SOC_DAI_FORMAT_DSP_A; +static struct audio_hw_format audio_hw_formats[] = { + { + .type = SND_SOC_DAI_FORMAT_I2S, + .name = "I2S", + }, + { + .type = SND_SOC_DAI_FORMAT_RIGHT_J, + .name = "RIGHT_J", + }, + { + .type = SND_SOC_DAI_FORMAT_LEFT_J, + .name = "LEFT_J", + }, + { + .type = SND_SOC_DAI_FORMAT_DSP_A, + .name = "DSP_A", + }, + { + .type = SND_SOC_DAI_FORMAT_DSP_B, + .name = "DSP_B", + }, + { + .type = SND_SOC_DAI_FORMAT_AC97, + .name = "AC97", + }, + { + .type = SND_SOC_DAI_FORMAT_AC97, + .name = "AC97", + }, + { + .type = SND_SOC_DAI_FORMAT_PDM, + .name = "PDM", + }, +}; - if (!strcmp(val, "DSP_B")) - return SND_SOC_DAI_FORMAT_DSP_B; +static int get_audio_hw_format(const char *val) +{ + unsigned int i; - if (!strcmp(val, "AC97")) - return SND_SOC_DAI_FORMAT_AC97; + if (val[0] == '\0') + return -EINVAL; - if (!strcmp(val, "PDM")) - return SND_SOC_DAI_FORMAT_PDM; + for (i = 0; i < ARRAY_SIZE(audio_hw_formats); i++) + if (strcasecmp(audio_hw_formats[i].name, val) == 0) + return audio_hw_formats[i].type; SNDERR("error: invalid audio HW format %s\n", val); return -EINVAL; } +static const char *get_audio_hw_format_name(unsigned int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(audio_hw_formats); i++) + if (audio_hw_formats[i].type == type) + return audio_hw_formats[i].name; + return NULL; +} + int tplg_parse_hw_config(snd_tplg_t *tplg, snd_config_t *cfg, void *private ATTRIBUTE_UNUSED) { @@ -1299,6 +1603,71 @@ int tplg_parse_hw_config(snd_tplg_t *tplg, snd_config_t *cfg, return 0; } +/* save hw config */ +int tplg_save_hw_config(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct snd_soc_tplg_hw_config *hc = elem->hw_cfg; + int err; + + err = tplg_save_printf(dst, NULL, "'%s' {\n", elem->id); + if (err >= 0 && hc->id) + err = tplg_save_printf(dst, pfx, "\tid %u\n", + hc->id); + if (err >= 0 && hc->fmt) + err = tplg_save_printf(dst, pfx, "\tformat '%s'\n", + get_audio_hw_format_name(hc->fmt)); + if (err >= 0 && hc->bclk_master) + err = tplg_save_printf(dst, pfx, "\tbclk '%s'\n", + hc->bclk_master == SND_SOC_TPLG_BCLK_CS ? + "codec_slave" : "codec_master"); + if (err >= 0 && hc->bclk_rate) + err = tplg_save_printf(dst, pfx, "\tbclk_freq %u\n", + hc->bclk_rate); + if (err >= 0 && hc->invert_bclk) + err = tplg_save_printf(dst, pfx, "\tbclk_invert 1\n"); + if (err >= 0 && hc->fsync_master) + err = tplg_save_printf(dst, pfx, "\tfsync_master '%s'\n", + hc->fsync_master == SND_SOC_TPLG_FSYNC_CS ? + "codec_slave" : "codec_master"); + if (err >= 0 && hc->fsync_rate) + err = tplg_save_printf(dst, pfx, "\tfsync_freq %u\n", + hc->fsync_rate); + if (err >= 0 && hc->invert_fsync) + err = tplg_save_printf(dst, pfx, "\tfsync_invert 1\n"); + if (err >= 0 && hc->mclk_rate) + err = tplg_save_printf(dst, pfx, "\tmclk_freq %u\n", + hc->mclk_rate); + if (err >= 0 && hc->mclk_direction) + err = tplg_save_printf(dst, pfx, "\tmclk '%s'\n", + hc->mclk_direction == SND_SOC_TPLG_MCLK_CI ? + "codec_mclk_in" : "codec_mclk_out"); + if (err >= 0 && hc->clock_gated) + err = tplg_save_printf(dst, pfx, "\tpm_gate_clocks 1\n"); + if (err >= 0 && hc->tdm_slots) + err = tplg_save_printf(dst, pfx, "\ttdm_slots %u\n", + hc->tdm_slots); + if (err >= 0 && hc->tdm_slot_width) + err = tplg_save_printf(dst, pfx, "\ttdm_slot_width %u\n", + hc->tdm_slot_width); + if (err >= 0 && hc->tx_slots) + err = tplg_save_printf(dst, pfx, "\ttx_slots %u\n", + hc->tx_slots); + if (err >= 0 && hc->rx_slots) + err = tplg_save_printf(dst, pfx, "\trx_slots %u\n", + hc->rx_slots); + if (err >= 0 && hc->tx_channels) + err = tplg_save_printf(dst, pfx, "\ttx_channels %u\n", + hc->tx_channels); + if (err >= 0 && hc->rx_channels) + err = tplg_save_printf(dst, pfx, "\trx_channels %u\n", + hc->rx_channels); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "}\n"); + return err; +} + /* copy stream object */ static void tplg_add_stream_object(struct snd_soc_tplg_stream *strm, struct snd_tplg_stream_template *strm_tpl) diff --git a/src/topology/save.c b/src/topology/save.c new file mode 100644 index 00000000..0498911f --- /dev/null +++ b/src/topology/save.c @@ -0,0 +1,632 @@ +/* + Copyright(c) 2019 Red Hat Inc. + 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: Jaroslav Kysela +*/ + +#include "list.h" +#include "tplg_local.h" + +#define SAVE_ALLOC_SHIFT (13) /* 8192 bytes */ + +int tplg_save_printf(char **dst, const char *pfx, const char *fmt, ...) +{ + va_list va; + char buf[1024], *s; + size_t n, l, t, pl; + + if (pfx == NULL) + pfx = ""; + + va_start(va, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + + if (n >= sizeof(buf)) + return -EOVERFLOW; + + pl = strlen(pfx); + l = *dst ? strlen(*dst) : 0; + t = l + pl + n + 1; + /* allocate chunks */ + if (*dst == NULL || + (l >> SAVE_ALLOC_SHIFT) != (t >> SAVE_ALLOC_SHIFT)) { + s = realloc(*dst, ((t >> SAVE_ALLOC_SHIFT) + 1) << + SAVE_ALLOC_SHIFT); + if (s == NULL) { + free(*dst); + *dst = NULL; + return -ENOMEM; + } + } else { + s = *dst; + } + + if (pl > 0) + strcpy(s + l, pfx); + strcpy(s + l + pl, buf); + *dst = s; + return 0; +} + +int tplg_nice_value_format(char *dst, size_t dst_size, unsigned int value) +{ + if ((value % 1000) != 0) { + if (value > 0xfffffff0) + return snprintf(dst, dst_size, "%d", (int)value); + if (value >= 0xffff0000) + return snprintf(dst, dst_size, "0x%x", value); + } + return snprintf(dst, dst_size, "%u", value); +} + +static int tplg_pprint_integer(snd_config_t *n, char **ret) +{ + long lval; + int err, type; + char buf[16]; + + type = snd_config_get_type(n); + if (type == SND_CONFIG_TYPE_INTEGER) { + err = snd_config_get_integer(n, &lval); + if (err < 0) + return err; + if (lval < INT_MIN || lval > UINT_MAX) + return snd_config_get_ascii(n, ret); + } else if (type == SND_CONFIG_TYPE_INTEGER64) { + long long llval; + err = snd_config_get_integer64(n, &llval); + if (err < 0) + return err; + if (llval < INT_MIN || llval > UINT_MAX) + return snd_config_get_ascii(n, ret); + lval = llval; + } + err = tplg_nice_value_format(buf, sizeof(buf), (unsigned int)lval); + if (err < 0) + return err; + *ret = strdup(buf); + if (*ret == NULL) + return -ENOMEM; + return 0; +} + +static int tplg_check_array_item(const char *id, int index) +{ + const char *p; + long int val; + + for (p = id; *p; p++) { + if (*p < '0' || *p > '9') + return 0; + } + + errno = 0; + val = strtol(id, NULL, 10); + return errno == 0 && val == index; +} + +static int _compar(const void *a, const void *b) +{ + const snd_config_t *c1 = *(snd_config_t **)a; + const snd_config_t *c2 = *(snd_config_t **)b; + const char *id1, *id2; + if (snd_config_get_id(c1, &id1)) return 0; + if (snd_config_get_id(c2, &id2)) return 0; + return strcmp(id1, id2); +} + +static snd_config_t *sort_config(const char *id, snd_config_t *src) +{ + snd_config_t *dst, **a; + snd_config_iterator_t i, next; + int index, array, count; + + if (snd_config_get_type(src) != SND_CONFIG_TYPE_COMPOUND) { + + if (snd_config_copy(&dst, src) >= 0) + return dst; + return NULL; + } + count = 0; + snd_config_for_each(i, next, src) + count++; + a = malloc(sizeof(dst) * count); + if (a == NULL) + return NULL; + index = array = 0; + snd_config_for_each(i, next, src) { + snd_config_t *s = snd_config_iterator_entry(i); + const char *id2; + a[index++] = s; + if (array < 0) + continue; + if (snd_config_get_id(s, &id2)) { + free(a); + return NULL; + } + if (array >= 0 && tplg_check_array_item(id2, array)) + array++; + else + array = -1; + } + if (array < 0) + qsort(a, count, sizeof(a[0]), _compar); + if (snd_config_make_compound(&dst, id, count == 1)) { + free(a); + return NULL; + } + for (index = 0; index < count; index++) { + snd_config_t *s = a[index]; + const char *id2; + if (snd_config_get_id(s, &id2)) { + snd_config_delete(dst); + free(a); + return NULL; + } + s = sort_config(id2, s); + if (s == NULL || snd_config_add(dst, s)) { + if (s) + snd_config_delete(s); + snd_config_delete(dst); + free(a); + return NULL; + } + } + free(a); + return dst; +} + +static int tplg_check_quoted(const unsigned char *p) +{ + for ( ; *p != '\0'; p++) { + switch (*p) { + case ' ': + case '=': + case ';': + case ',': + case '.': + case '{': + case '}': + case '\'': + case '"': + return 1; + default: + if (*p <= 31 || *p >= 127) + return 1; + + } + } + return 0; +} + +static int tplg_save_quoted(char **dst, const char *str) +{ + static const char nibble[16] = "0123456789abcdef"; + unsigned char *p, *d, *t; + int c; + + d = t = alloca(strlen(str) * 5 + 1 + 1); + for (p = (unsigned char *)str; *p != '\0'; p++) { + c = *p; + switch (c) { + case '\n': + *t++ = '\\'; + *t++ = 'n'; + break; + case '\t': + *t++ = '\\'; + *t++ = 't'; + break; + case '\v': + *t++ = '\\'; + *t++ = 'v'; + break; + case '\b': + *t++ = '\\'; + *t++ = 'b'; + break; + case '\r': + *t++ = '\\'; + *t++ = 'r'; + break; + case '\f': + *t++ = '\\'; + *t++ = 'f'; + break; + case '\'': + *t++ = '\\'; + *t++ = c; + break; + default: + if (c >= 32 && c <= 126) { + *t++ = c; + } else { + *t++ = '\\'; + *t++ = 'x'; + *t++ = nibble[(c >> 4) & 0x0f]; + *t++ = nibble[(c >> 0) & 0x0f]; + } + break; + } + } + *t = '\0'; + return tplg_save_printf(dst, NULL, "'%s'", d); +} + +static int tplg_save_string(char **dst, const char *str, int id) +{ + const unsigned char *p = (const unsigned char *)str; + + if (!p || !*p) + return tplg_save_printf(dst, NULL, "''"); + + if (!id && ((*p >= '0' && *p <= '9') || *p == '-')) + return tplg_save_quoted(dst, str); + + if (tplg_check_quoted(p)) + return tplg_save_quoted(dst, str); + + return tplg_save_printf(dst, NULL, "%s", str); +} + +static int save_config(char **dst, int level, const char *delim, snd_config_t *src) +{ + snd_config_iterator_t i, next; + snd_config_t *s; + const char *id; + char *pfx; + unsigned int count; + int type, err, quoted, array; + + if (delim == NULL) + delim = ""; + + type = snd_config_get_type(src); + if (type != SND_CONFIG_TYPE_COMPOUND) { + char *val; + if (type == SND_CONFIG_TYPE_INTEGER || + type == SND_CONFIG_TYPE_INTEGER64) { + err = tplg_pprint_integer(src, &val); + } else { + err = snd_config_get_ascii(src, &val); + } + if (err < 0) + return err; + if (type == SND_CONFIG_TYPE_STRING) { + /* hexa array pretty print */ + id = strchr(val, '\n'); + if (id) { + err = tplg_save_printf(dst, NULL, "\n"); + if (err < 0) + goto retval; + for (id++; *id == '\t'; id++) { + err = tplg_save_printf(dst, NULL, "\t"); + if (err < 0) + goto retval; + } + delim = ""; + } + err = tplg_save_printf(dst, NULL, "%s'%s'\n", delim, val); + } else { + err = tplg_save_printf(dst, NULL, "%s%s\n", delim, val); + } +retval: + free(val); + return err; + } + + count = 0; + quoted = 0; + array = 0; + s = NULL; + snd_config_for_each(i, next, src) { + s = snd_config_iterator_entry(i); + err = snd_config_get_id(s, &id); + if (err < 0) + return err; + if (!quoted && tplg_check_quoted((unsigned char *)id)) + quoted = 1; + if (array >= 0 && tplg_check_array_item(id, array)) + array++; + else + array = -1; + count++; + } + if (count == 0) + return 0; + + if (count == 1) { + err = snd_config_get_id(s, &id); + if (err >= 0 && level > 0) + err = tplg_save_printf(dst, NULL, "."); + if (err >= 0) + err = tplg_save_string(dst, id, 1); + if (err >= 0) + err = save_config(dst, level, " ", s); + return err; + } + + pfx = alloca(level + 1); + memset(pfx, '\t', level); + pfx[level] = '\0'; + + if (level > 0) { + err = tplg_save_printf(dst, NULL, "%s%s\n", delim, + array >= 0 ? "[" : "{"); + if (err < 0) + return err; + } + + snd_config_for_each(i, next, src) { + s = snd_config_iterator_entry(i); + const char *id; + err = snd_config_get_id(s, &id); + if (err < 0) + return err; + err = tplg_save_printf(dst, pfx, ""); + if (err < 0) + return err; + if (array < 0) { + delim = " "; + if (quoted) { + err = tplg_save_quoted(dst, id); + } else { + err = tplg_save_string(dst, id, 1); + if (err < 0) + return err; + } + } else { + delim = ""; + } + err = save_config(dst, level + 1, delim, s); + if (err < 0) + return err; + } + + if (level > 0) { + pfx[level - 1] = '\0'; + err = tplg_save_printf(dst, pfx, "%s\n", + array >= 0 ? "]" : "}"); + if (err < 0) + return err; + } + + return 0; +} + +static int tplg_save(snd_tplg_t *tplg, char **dst, int gindex, const char *prefix) +{ + struct tplg_table *tptr; + struct tplg_elem *elem; + struct list_head *list, *pos; + char pfx2[16]; + unsigned int index; + int err, count; + + snprintf(pfx2, sizeof(pfx2), "%s\t", prefix ?: ""); + + /* write all blocks */ + for (index = 0; index < tplg_table_items; index++) { + tptr = &tplg_table[index]; + list = (struct list_head *)((void *)tplg + tptr->loff); + + /* count elements */ + count = 0; + list_for_each(pos, list) { + elem = list_entry(pos, struct tplg_elem, list); + if (gindex >= 0 && elem->index != gindex) + continue; + if (tptr->save == NULL && tptr->gsave == NULL) { + SNDERR("unable to create %s block (no callback)", + tptr->id); + err = -ENXIO; + goto _err; + } + if (tptr->save) + count++; + } + + if (count == 0) + continue; + + if (count > 1) { + err = tplg_save_printf(dst, prefix, "%s {\n", + elem->table ? + elem->table->id : "_NOID_"); + } else { + err = tplg_save_printf(dst, prefix, "%s.", + elem->table ? + elem->table->id : "_NOID_"); + } + + if (err < 0) + goto _err; + + list_for_each(pos, list) { + elem = list_entry(pos, struct tplg_elem, list); + if (gindex >= 0 && elem->index != gindex) + continue; + if (count > 1) { + err = tplg_save_printf(dst, pfx2, ""); + if (err < 0) + goto _err; + } + err = tptr->save(tplg, elem, dst, count > 1 ? pfx2 : prefix); + if (err < 0) { + SNDERR("failed to save %s elements: %s", + tptr->id, snd_strerror(-err)); + goto _err; + } + } + if (count > 1) { + err = tplg_save_printf(dst, prefix, "}\n"); + if (err < 0) + goto _err; + } + } + + /* save globals */ + for (index = 0; index < tplg_table_items; index++) { + tptr = &tplg_table[index]; + if (tptr->gsave) { + err = tptr->gsave(tplg, gindex, dst, prefix); + if (err < 0) + goto _err; + } + } + + return 0; + +_err: + free(*dst); + *dst = NULL; + return err; +} + +static int tplg_index_compar(const void *a, const void *b) +{ + const int *a1 = a, *b1 = b; + return *a1 - *b1; +} + +static int tplg_index_groups(snd_tplg_t *tplg, int **indexes) +{ + struct tplg_table *tptr; + struct tplg_elem *elem; + struct list_head *list, *pos; + unsigned int index, j, count, size; + int *a, *b; + + count = 0; + size = 16; + a = malloc(size * sizeof(a[0])); + + for (index = 0; index < tplg_table_items; index++) { + tptr = &tplg_table[index]; + list = (struct list_head *)((void *)tplg + tptr->loff); + list_for_each(pos, list) { + elem = list_entry(pos, struct tplg_elem, list); + for (j = 0; j < count; j++) { + if (a[j] == elem->index) + break; + } + if (j < count) + continue; + if (count + 1 >= size) { + size += 8; + b = realloc(a, size * sizeof(a[0])); + if (b == NULL) { + free(a); + return -ENOMEM; + } + a = b; + } + a[count++] = elem->index; + } + } + a[count] = -1; + + qsort(a, count, sizeof(a[0]), tplg_index_compar); + + *indexes = a; + return 0; +} + +int snd_tplg_save(snd_tplg_t *tplg, char **dst, int flags) +{ + snd_input_t *in; + snd_config_t *top, *top2; + char *dst2; + int *indexes, *a; + int err; + + assert(tplg); + assert(dst); + *dst = NULL; + + if (flags & SND_TPLG_SAVE_GROUPS) { + err = tplg_index_groups(tplg, &indexes); + if (err < 0) + return err; + for (a = indexes; err >= 0 && *a >= 0; a++) { + err = tplg_save_printf(dst, NULL, + "IndexGroup.%d {\n", + *a); + if (err >= 0) + err = tplg_save(tplg, dst, *a, "\t"); + if (err >= 0) + err = tplg_save_printf(dst, NULL, "}\n"); + } + free(indexes); + } else { + err = tplg_save(tplg, dst, -1, NULL); + } + + if (err < 0) + goto _err; + + if (flags & SND_TPLG_SAVE_NOCHECK) + return 0; + + /* always load configuration - check */ + err = snd_input_buffer_open(&in, *dst, strlen(*dst)); + if (err < 0) { + SNDERR("could not create input buffer"); + goto _err; + } + + err = snd_config_top(&top); + if (err < 0) { + snd_input_close(in); + goto _err; + } + + err = snd_config_load(top, in); + snd_input_close(in); + if (err < 0) { + SNDERR("could not load configuration"); + snd_config_delete(top); + goto _err; + } + + if (flags & SND_TPLG_SAVE_SORT) { + top2 = sort_config(NULL, top); + if (top2 == NULL) { + SNDERR("could not sort configuration"); + snd_config_delete(top); + err = -EINVAL; + goto _err; + } + snd_config_delete(top); + top = top2; + } + + dst2 = NULL; + err = save_config(&dst2, 0, NULL, top); + snd_config_delete(top); + if (err < 0) { + SNDERR("could not save configuration"); + goto _err; + } + + free(*dst); + *dst = dst2; + return 0; + +_err: + free(*dst); + *dst = NULL; + return err; +} diff --git a/src/topology/text.c b/src/topology/text.c index f301a4de..e9386e7d 100644 --- a/src/topology/text.c +++ b/src/topology/text.c @@ -89,3 +89,22 @@ int tplg_parse_text(snd_tplg_t *tplg, snd_config_t *cfg, return err; } + +/* save text data */ +int tplg_save_text(snd_tplg_t *tplg ATTRIBUTE_UNUSED, + struct tplg_elem *elem, + char **dst, const char *pfx) +{ + struct tplg_texts *texts = elem->texts; + unsigned int i; + int err; + + if (!texts || texts->num_items == 0) + return 0; + err = tplg_save_printf(dst, pfx, "'%s'.values [\n", elem->id); + for (i = 0; err >= 0 && i < texts->num_items; i++) + err = tplg_save_printf(dst, pfx, "\t'%s'\n", texts->items[i][0]); + if (err >= 0) + err = tplg_save_printf(dst, pfx, "]\n"); + return err; +} diff --git a/src/topology/tplg_local.h b/src/topology/tplg_local.h index bea88ba3..42a3aa96 100644 --- a/src/topology/tplg_local.h +++ b/src/topology/tplg_local.h @@ -37,6 +37,7 @@ struct tplg_ref; struct tplg_elem; +struct tplg_table; typedef enum _snd_pcm_rates { SND_PCM_RATE_UNKNOWN = -1, @@ -147,6 +148,8 @@ struct tplg_vendor_tuples { /* topology element */ struct tplg_elem { + struct tplg_table *table; + char id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int index; @@ -209,6 +212,10 @@ struct tplg_table { unsigned enew: 1; void (*free)(void *); int (*parse)(snd_tplg_t *tplg, snd_config_t *cfg, void *priv); + int (*save)(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *prefix); + int (*gsave)(snd_tplg_t *tplg, int index, + char **dst, const char *prefix); }; extern struct tplg_table tplg_table[]; @@ -250,7 +257,8 @@ int tplg_build_pcm_dai(snd_tplg_t *tplg, unsigned int type); int tplg_copy_data(snd_tplg_t *tplg, struct tplg_elem *elem, struct tplg_ref *ref); -int tplg_parse_data_refs(snd_config_t *cfg, struct tplg_elem *elem); +int tplg_parse_refs(snd_config_t *cfg, struct tplg_elem *elem, + unsigned int type); int tplg_ref_add(struct tplg_elem *elem, int type, const char* id); int tplg_ref_add_elem(struct tplg_elem *elem, struct tplg_elem *elem_ref); @@ -269,9 +277,11 @@ struct tplg_elem* tplg_elem_new_common(snd_tplg_t *tplg, int tplg_get_integer(snd_config_t *n, int *val, int base); int tplg_get_unsigned(snd_config_t *n, unsigned *val, int base); +const char *tplg_channel_name(int type); int tplg_parse_channel(snd_tplg_t *tplg ATTRIBUTE_UNUSED, snd_config_t *cfg, void *private); +const char *tplg_ops_name(int type); int tplg_parse_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, snd_config_t *cfg, void *private); int tplg_parse_ext_ops(snd_tplg_t *tplg ATTRIBUTE_UNUSED, @@ -299,3 +309,49 @@ 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_nice_value_format(char *dst, size_t dst_size, unsigned int value); + +int tplg_save_printf(char **dst, const char *prefix, const char *fmt, ...); +int tplg_save_refs(snd_tplg_t *tplg, struct tplg_elem *elem, unsigned int type, + const char *id, char **dst, const char *pfx); +int tplg_save_channels(snd_tplg_t *tplg, struct snd_soc_tplg_channel *channel, + unsigned int channel_count, char **dst, const char *pfx); +int tplg_save_ops(snd_tplg_t *tplg, struct snd_soc_tplg_ctl_hdr *hdr, + char **dst, const char *pfx); +int tplg_save_ext_ops(snd_tplg_t *tplg, struct snd_soc_tplg_bytes_control *be, + char **dst, const char *pfx); +int tplg_save_manifest_data(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_control_mixer(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_control_enum(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_control_bytes(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_tlv(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_data(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_text(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_tokens(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_tuples(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_dapm_graph(snd_tplg_t *tplg, int index, + char **dst, const char *pfx); +int tplg_save_dapm_widget(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_link(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_cc(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_pcm(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_hw_config(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_stream_caps(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx); +int tplg_save_dai(snd_tplg_t *tplg, struct tplg_elem *elem, + char **dst, const char *pfx);