diff --git a/tools/topology/topology2/doc/CMakeLists.txt b/tools/topology/topology2/doc/CMakeLists.txt new file mode 100644 index 000000000000..303d06da3d15 --- /dev/null +++ b/tools/topology/topology2/doc/CMakeLists.txt @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.13) + +project(SOF_TOPOLOGY2_DOC NONE) + +set(SOF_ROOT_SOURCE_DIRECTORY "${PROJECT_SOURCE_DIR}/..") + +set(top_srcdir "${SOF_ROOT_SOURCE_DIRECTORY}") +set(top_bindir "${PROJECT_BINARY_DIR}") + +configure_file( + "${PROJECT_SOURCE_DIR}/sof.doxygen.in" + "${PROJECT_BINARY_DIR}/sof.doxygen" +) + +file(GLOB_RECURSE topology2_sources "${SOF_ROOT_SOURCE_DIRECTORY}/*.conf") +file(GLOB_RECURSE extra_sources "${PROJECT_SOURCE_DIR}/extra-contents/*.doxy") + +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/contents.doxy + COMMAND ${PROJECT_SOURCE_DIR}/topology2-generate-contents.sh > ${PROJECT_BINARY_DIR}/contents.doxy + DEPENDS ${PROJECT_SOURCE_DIR}/topology2-generate-contents.sh + DEPENDS ${topology2_sources} + VERBATIM +) + +add_custom_target(doc-contents + DEPENDS ${PROJECT_BINARY_DIR}/contents.doxy +) + +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/doxygen/html/index.html + COMMAND doxygen sof.doxygen + DEPENDS ${PROJECT_BINARY_DIR}/sof.doxygen + DEPENDS ${PROJECT_SOURCE_DIR}/topology2-filter.py + DEPENDS doc-contents + DEPENDS ${extra_sources} + DEPENDS ${topology2_sources} + VERBATIM + USES_TERMINAL +) + +add_custom_target(doc ALL + DEPENDS ${PROJECT_BINARY_DIR}/doxygen/html/index.html + VERBATIM +) diff --git a/tools/topology/topology2/doc/README b/tools/topology/topology2/doc/README new file mode 100644 index 000000000000..a412e1d2d707 --- /dev/null +++ b/tools/topology/topology2/doc/README @@ -0,0 +1,7 @@ +To build the topology2 source documentation do following steps: + +cd tools/topology/topology2/doc +cmake -B build/ +cmake --build build/ -v + +After the last command you should find the html documentation under: sof/tools/topology/topology2/doc/build/doxygen/html diff --git a/tools/topology/topology2/doc/extra-contents/mainpage.doxy b/tools/topology/topology2/doc/extra-contents/mainpage.doxy new file mode 100644 index 000000000000..4b2a2bfcabc8 --- /dev/null +++ b/tools/topology/topology2/doc/extra-contents/mainpage.doxy @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright(c) 2023 Intel Corporation. All rights reserved. */ +/*! \mainpage Sound Open Firmware Topology2 + * + * \section this_document This Document + * + * This document is generated from SOF topology2 sources and its + * purpose is provide examples of how SOF ALSA topologies are built + * through instantiating toppology2 classes. + * + * The documentation is provided using Doxygen package and a Doxygen + * filter that translates the topology2 classes into C-structures. The + * filter is implemented in python and can be found from + * tools/topology/topology2/doc/topology2-filter.py + * + * The topology2 language syntax is described in detail here. + * + * \subsection doc_reading Reading the document + * + * The purpose of the translated C code is not to document actual + * topology2 code, but only to provide anchors for Doxygen to form a + * network of links through which to navigate the topology sources and + * find the pieces of related Doxygen documentation. The filter also + * creates separate pages of the original code and add links next to + * the pages in the C struct definition and instance documentation. + * + * The most essential part of the documentation is the documentation of + * classes that shown as C structs in this Doxygen documentation. + * + * \copydoc doc_contents + */ diff --git a/tools/topology/topology2/doc/sof.doxygen.in b/tools/topology/topology2/doc/sof.doxygen.in new file mode 100644 index 000000000000..d964c58258da --- /dev/null +++ b/tools/topology/topology2/doc/sof.doxygen.in @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: BSD-3-Clause +PROJECT_NAME = "Sound Open Firmware Topology2" +OUTPUT_DIRECTORY = doxygen +GENERATE_LATEX = NO +GENERATE_RTF = NO +GENERATE_MAN = NO +GENERATE_XML = YES + +CASE_SENSE_NAMES = NO +INPUT = @top_srcdir@ \ + @top_srcdir@/doc/extra-contents/ \ + @top_bindir@/contents.doxy + +RECURSIVE = YES +FILE_PATTERNS = *.conf +IMAGE_PATH = +QUIET = YES +WARN_LOGFILE = doxygen_warnings.txt + +EXTRACT_ALL = YES +EXTRACT_STATIC = YES +WARN_IF_UNDOCUMENTED = NO +SHOW_INCLUDE_FILES = YES +JAVADOC_AUTOBRIEF = YES +INHERIT_DOCS = YES + +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +OPTIMIZE_OUTPUT_FOR_C = YES + +HTML_TIMESTAMP = NO + +EXTENSION_MAPPING = conf=C,doxy=C +FILTER_PATTERNS = *.conf=@top_srcdir@/doc/topology2-filter.py diff --git a/tools/topology/topology2/doc/topology2-filter.py b/tools/topology/topology2/doc/topology2-filter.py new file mode 100755 index 000000000000..7ff465c66be7 --- /dev/null +++ b/tools/topology/topology2/doc/topology2-filter.py @@ -0,0 +1,745 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2023 Intel Corporation. All rights reserved. +# +# The usage of this command is simple. It takes one argument, a path +# to a SOF topology2 source file, and it produces to stdout something +# that can be parsed by Doxygen as C source. Produced output contains +# C struct definitions and instantiations analogous to the topology2 +# classes and object instances. +# +# The purpose of the translated C code is not to document actual +# topology2 code, but only to provide anchors for Doxygen to form a +# network of links through which to navigate the topology sources and +# find the pieces of related Doxygen documentation. The filter also +# creates separate pages of the original code and adds links next to the +# pages in the C struct definition, instance documentation and their +# possible Doxygen documentation. + +import sys +import os +import re +import io +import logging + +logging.basicConfig(filename='filter_debug.txt', filemode='a', encoding='utf-8', + level=logging.DEBUG) + +def fname(): + try: + name = sys._getframe(1).f_code.co_name + except (IndexError, TypeError, AttributeError): + name = "" + return name + +def cbracket_count(line): + val = line.count("{") - line.count("}") + return val + +def sbracket_count(line): + val = line.count("[") - line.count("]") + return val + +def doxy_check_add(doxy, line): + if line.find("##") >= 0: + doxy = doxy + line[line.find("##"):].replace("##", "//!", 1) + if line.find("#") >= 0: + line = line[0:line.find("#")] + return (doxy, line) + +def print_doxy(doxy, file = sys.stdout): + if len(doxy): + print(doxy, file=file) + return "" + return doxy + +def parse_include_str(line): + if re.search(r"^\s*\<[A-Za-z0-9\/_\-\.]+\>\s*", line): + tok = line.split() + return "#include " + tok[0] + "\n" + return None + +def parse_include(line): + str = parse_include_str(line) + if str: + print(str) + return True + return False + +def parse_define_block(fline, instream): + """Parses topology2 Define { } block and outputs C-preprocessor #define macros + + Parameters: + fline (string): First input line that was read by the caller + instream (stream): Input stream of topology2 file we are decoding + + Returns: + string: The original code that was translated + """ + if re.search(r"^\s*Define\s+\{", fline): + logging.debug("fline: %s", fline) + doxy = "" + for line in instream: + if cbracket_count(line) < 0: + break + (doxy, line) = doxy_check_add(doxy, line) + doxy = print_doxy(doxy) + tok = line.split(maxsplit = 2) + if len(tok) < 2: + continue + val = trim_value(line[line.find(tok[0]) + len(tok[0]):]) + print("#define %s\t%s" % (trim_c_id(tok[0]), val)) + return True + return False + +def parse_include_by_key(fline, instream, file): # For now just skip + """Handles IncludeByKey { } blocks, currently handles only actual + includes, not nested {} blocks but everything is included into + raw_code return value. + + Parameters: + fline (string): First input line that was read by the caller + instream (stream): Input stream of topology2 file we are decoding + file (stream): Output stream for the translated output + + Returns: + string: The original code that was translated + + """ + if re.search(r"^\s*IncludeByKey\.", fline): + logging.debug("fline: %s", fline) + bsum = cbracket_count(fline) + if bsum == 1: + # Assume IncludeByKey. { + tok = fline.split() + tok = tok[0].split(".") + name = tok[1] + raw_code = "" + ifstr = "if" + for line in instream: + raw_code = raw_code + line + bsum = bsum + cbracket_count(line) + tok = line.split("\"") + if len(tok) >= 4: + print("#%s %s == \"%s\"\n#include <%s>" % + (ifstr, name, tok[1], tok[3]), file = file) + if ifstr == "if": + ifstr = "elif" + if bsum < 1: + if ifstr != "if": + print("#endif", file = file) + return raw_code + return None + +def trim_value(val): + val = val.strip(" \t\n$") + end = len(val) - 1 + if val[0:1] == "\"" and val[end:] == "\"": + return val + if val.isidentifier() or val.isnumeric(): + return val + if val[0:1] == "\'" and val[end:] == "\'": + val = val.strip("\'") + return "\"" + val + "\"" + +def trim_c_id(name): + name = name.strip(" \t\n\"\'") + name = name.replace("-", "_") + name = name.replace(" ", "_") + return name + + +def parse_attribute_constraints(instream, name): + """Parses a Constraints { } block and produces a C enum definition if possible + + Parameters: + instream (stream): Input stream of topology2 file we are decoding + name (string): Attribute name of this constraints block belongs to + + Returns: + string: C enum definition or an empty string + + """ + logging.debug("name: %s", name) + valid_values = [] + tuple_values = [] + doxy = "" # This is thrown away since there is no C anchror to connect it- + raw_code = "" + enum = "" + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if cbracket_count(line) < 0: + break + if re.search(r"^\s*\!valid_values\s+\[", line): + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if sbracket_count(line) < 0: + break + valid_values.append(trim_c_id(line)) + if re.search(r"^\s*\!tuple_values\s+\[", line): + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if sbracket_count(line) < 0: + break + tuple_values.append(trim_value(line)) + if len(valid_values) > 1 and len(valid_values) == len(tuple_values): + enum = "enum " + name + " {\n" + for i in range(len(valid_values)): + enum = enum + "\t\t" + valid_values[i] + " =\t" + tuple_values[i] + ",\n" + enum = enum + "\t}" + elif len(valid_values) > 1 and len(tuple_values) == 0: + enum = "enum " + name + " {\n" + for i in range(len(valid_values)): + enum = enum + "\t\t" + valid_values[i] + "," + if valid_values[i][0:1] == "$": # If its a variable add a reference to it + enum = enum + " //!< \\ref " + valid_values[i][1:] + enum = enum + "\n" + enum = enum + "\t}" + return (raw_code, enum) + +def parse_class_attribute(attributes, doxy, instream, fline): + """Parses a DefineAttribute { } block and collects the information into + attributes dict. Any already accumulated Doxygen documentation is + also stored there under the attribute name- + + Parameters: + attributes (dict): A dictionary where data about the attribute is stored + doxy (strung): Doxygen documentation collected just before the attribute + instream (stream): Input stream of topology2 file we are decoding + fline (string): First input line that was read by the caller + + Returns: + string: The original code that was translated + """ + logging.debug("fline: %s", fline) + tok = fline.split("\"") + if len(tok) > 1: + name = tok[1] + else: + tok = fline.split(".") + tok = tok[1].split(" ") + name = tok[0] + (doxy, fline) = doxy_check_add(doxy, fline) + bsum = cbracket_count(fline) + typestr = "" + token_ref = "" + ref_type = "" + enum = "" + raw_code = "" + if bsum < 1: + # Assume: DefineAttribute.name {} + typestr = "int" + else: + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + bsum = bsum + cbracket_count(line) + if bsum < 1: + break + if re.search(r"^\s*constraints\s+\{", line) and bsum == 2: + (code, enum) = parse_attribute_constraints(instream, name) + raw_code = raw_code + code + bsum = 1 + elif re.search(r"^\s*type\s", line): + tok = line.split() + typestr = tok[1].strip(" \t\n\"\'") + elif re.search(r"^\s*token_ref\s", line): + tok = line.split() + token_ref = tok[1].strip(" \t\n\"\'") + tok = token_ref.split(".") + ref_type = tok[1] + logging.debug("type %s token_ref %s ref_type %s enum %s", + typestr, token_ref, ref_type, enum) + if enum != "" and ref_type != "bool": + typestr = enum + if ref_type == "string": + doxy = doxy + "//! \\em string type\n" + elif typestr == "" or ref_type == "bool": + typestr = ref_type + attributes[name] = { "type": typestr, "doxy": doxy, "token_ref": token_ref } + return raw_code + +def print_attributes(attributes): + """Print out struct members and their doxygen documentation from attributes dict. + + Parameters: + attributes (dict): A dictionary where data about the attributes was stored + """ + for name in attributes: + doxy = "" + if attributes[name].get("doxy"): + doxy = attributes[name]["doxy"] + "\n" + if attributes[name].get("type"): + typestr = attributes[name]["type"] + print("%s\t%s %s;\n" % (doxy, typestr, name)) + +def set_attribute_flag(attributes, attribute, flag): + if not attributes.get(attribute): + attributes[attribute] = {} + attributes[attribute][flag] = True + +def parse_attributes_block(instream, attributes, attrib_doxy): + """Parse attributes block inside class definition and store the flags in attributes dict + + Parameters: + instream (stream): Input stream of topology2 file we are decoding + attributes (dict): Dict where data about the attributes is stored + attrib_doxy (dict): Dict to store doxygen docs associated with the attributes block + + Returns: + string: The original code that was translated + """ + logging.debug("called") + raw_code = "" + doxy = "" + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if cbracket_count(line) < 0: + break + if re.search(r"^\s*\!constructor\s+\[", line): + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if sbracket_count(line) < 0: + break + set_attribute_flag(attributes, trim_c_id(line), "constructor") + attrib_doxy["constructor"] = doxy + doxy = "" + elif re.search(r"^\s*\!mandatory\s+\[", line): + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if sbracket_count(line) < 0: + break + set_attribute_flag(attributes, trim_c_id(line), "mandatory") + attrib_doxy["mandatory"] = doxy + doxy = "" + elif re.search(r"^\s*\!immutable\s+\[", line): + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if sbracket_count(line) < 0: + break + set_attribute_flag(attributes, trim_c_id(line), "immutable") + attrib_doxy["immutable"] = doxy + doxy = "" + elif re.search(r"^\s*\!deprecated\s+\[", line): + attrib_doxy["deprecated"] = doxy + doxy = "" + for line in instream: + raw_code = raw_code + line + (doxy, line) = doxy_check_add(doxy, line) + if sbracket_count(line) < 0: + break + set_attribute_flag(attributes, trim_c_id(line), "deprecated") + elif re.search(r"^\s*unique\s+", line): + tok = line.split() + attrib_doxy["unique"] = doxy + doxy = "" + set_attribute_flag(attributes, trim_c_id(tok[1]), "unique") + return raw_code + +def attribute_block_print(class_name, attributes, attrib_doxy): + """Generates a Doxygen documentation section from attribute flags + and doxygen docs stored to attrib_doxy + + Parameters: + class_name (string): Name of the class we are processing + attributes (dict): Dict where data about the attributes was stored + attrib_doxy (dict): Dict where doxygen docs of the attributes block was stored + + """ + logging.debug("class_name \'%s\'", class_name) + for field in attrib_doxy: + # Only add attribute paragraph if there are doxygen comment for it + if attrib_doxy[field] != "": + print("//! \\par %s attributes:\n//!" % field.capitalize()) + for attr in attributes: + if attributes[attr].get(field): + print("//! \\link %s::%s \\endlink \\n" % (class_name, attr)) + print("//! \\n\n%s" % attrib_doxy[field]) + +def attribute_block_info_add(attributes, attrib_doxy): + """Add simple bullets in the attribute (= struct members) documentation if + the attribute has constructor, mandatory, immutable, deprecated, or unique + property. + + Parameters: + attributes (dict): Dict where data about the attributes was stored + attrib_doxy (dict): Dict where doxygen docs of the attributes block was stored + + """ + # TODO: Add short documentation about the attribute properties and link to it. + for field in attrib_doxy: + for attr in attributes: + if attributes[attr].get(field): + if not attributes[attr].get("doxy"): + attributes[attr]["doxy"] = "" + attributes[attr]["doxy"] = "//! - \\em " + field + " attribute.\\n\n" + attributes[attr]["doxy"] + +def parse_class_contents(class_name, attributes, attrib_doxy, objs, defaults, + instream, includef = ""): + """Translates the contents inside a class definition { } block + + Parameters: + class_name (string): Class name + attributes (dict): Dict where data about the attributes is stored + attrib_doxy (dict): Dict to store doxygen docs associated with the attributes block + objs (dict): Dict to store fist level contained objects with Docxyge docs + defaults (streaM): Stream where the instances of the objects are printed + instream (stream): Input stream of topology2 file we are decoding + includef (string): Include file from where the attributes and objects inline included from + + Returns: + string: The original code that was translated + """ + bsum = 1 + doxy_addition = "" + if includef != "": + doxy_addition = "//! - Included from <" + includef + ">\\n\n" + doxy = "" + raw_code = "" + for line in instream: + raw_code = raw_code + line + if parse_include_str(line): + # Inline files that are included from within a class definition + filename = line[line.find("<") + 1:line.find(">")] + logging.debug("try to inline include \'%s\' from \'%s\'", + filename, os.getcwd()) + # NOTE: The path is relative to when the script exists so + # two levels up and we are at topology2 root + with open("../../" + filename, "r+", encoding="ascii") as ifile: + parse_class_contents(class_name, attributes, attrib_doxy, objs, + defaults, ifile, filename) + continue + if (code := parse_include_by_key(line, instream, sys.stdout)): + raw_code = raw_code + code + continue + if re.search(r"^\s*DefineAttribute\.", line): + doxy = doxy_addition + doxy + raw_code = raw_code + parse_class_attribute(attributes, doxy, instream, line) + doxy = "" + elif re.search(r"^\s*DefineArgument\.", line): + for line in instream: # Just skip, this is only used in bytes.conf + if cbracket_count(line) < 0: + break + elif re.search(r"^\s*attributes\s+\{", line): + doxy = "" # Doxy comments before attributes block end in weird places + raw_code = raw_code + parse_attributes_block(instream, attributes, attrib_doxy) + elif re.search(r"^\s*Object\.", line): + # TODO: Pass collected doxy comments to parse_object and store in objs + doxy = "" + raw_code = raw_code + parse_object(instream, line, file = defaults, + objects = objs, tabs = "\t", ending = ",") + else: # If nothing else matched, assume a default value definition for an attribute + (doxy, line) = doxy_check_add(doxy, line) + tok = line.split() + if len(tok) == 2 and tok[0].isidentifier(): + if doxy != "": + print(doxy, file = defaults) + doxy = "" + val = trim_value(line[line.find(tok[0]) + len(tok[0]):]) + print("\t.%s =\t%s," % (trim_c_id(tok[0]), val), + file = defaults) + bsum = bsum + cbracket_count(line) + if bsum < 1: + break + return raw_code + +def parse_class(instream, fline): + """Parse class definition and print out C struct definition + + Parameters: + fline (string): First input line that was read by the caller + instream (stream): Input stream of topology2 file we are decoding + """ + logging.debug("fline: %s", fline) + tok = fline.split(maxsplit = 2) + cdef = tok[0].split(".") + # base = cdef[1] # Just in case we go back to C++ tralation + class_name = cdef[2] + class_name = trim_c_id(class_name) + attributes = {} + attrib_doxy = {} + defaults = io.StringIO() + objs = {} + raw_code = fline + parse_class_contents(class_name, attributes, attrib_doxy, + objs, defaults, instream) + attribute_block_print(class_name, attributes, attrib_doxy) + attribute_block_info_add(attributes, attrib_doxy) + print("//! \\ref %s_rawcode" % class_name) + print("struct %s {" % class_name) + print_attributes(attributes) + for obj in objs: + if objs[obj]["count"] > 1: + for i in range(objs[obj]["count"]): + print(objs[obj]["doxy"][i]) + print("\tstruct %s %s%d;" % (obj, obj, i)) + else: + print(objs[obj]["doxy"][0]) + print("\tstruct %s %s;" % (obj, obj)) + print("};\n") + print("/*! \\page %s_rawcode The %s class definition in topology2 code\n\t\\code{.unparsed}" % + (class_name, class_name)) + print(raw_code) + print("\t\\endcode\n*/") + print("//! \\var struct %s %s_defaults" % (class_name, class_name)) + print("//! \\brief %s class default values" % class_name) + print("struct %s %s_defaults = {\n" % (class_name, class_name)) + print(defaults.getvalue()) + print("};\n") + defaults.close() + +def parse_members(instream, cbsum, tabs, file): + """Parse attribute initializations from object instantiation + + Parameters: + instream (stream): Input stream of topology2 file we are decoding + cbsum (int): The amount of curly brackets "{" WE HAVE OPEN + tabs (string): Current level of indentation + + Returns: + string: The original code that was translated + """ + doxy = "" + raw_code = "" + for line in instream: + raw_code = raw_code + line + if (code := parse_include_by_key(line, instream, file)): + raw_code = raw_code + code + continue + cbsum = cbsum + cbracket_count(line) + (doxy, line) = doxy_check_add(doxy, line) + # Assume ending } to be alone on its own line + if cbsum < 1: + break + if re.search(r"^\s*Object\.", line): + logging.debug("object-line: %s", line) + obj = line.split(".") + if cbsum == 2: + # Assume Object.Base.name.1 { + doxy = print_doxy(doxy, file = file) + raw_code = raw_code + parse_object(instream, line, file = file, tabs = tabs, ending = ",") + cbsum = 1 + elif cbsum == 1: + # Assume Object.Base.name.1 {} + doxy = print_doxy(doxy, file = file) + print("%s.%s = {}," % (tabs, obj[2]), file = file) + else: + tok = line.split(maxsplit = 1) + if len(tok) >=2: + name = tok[0] + val = trim_value(tok[1]) + doxy = print_doxy(doxy, file = file) + print("%s.%s = %s," % (tabs, name, val), file = file) + return raw_code + +# +def object_instance_prefix(name, ending): + """Decide "struct name name =" or ".name =" based on instantiation ending in ',' or ';' + + Parameters: + name (string): Name of the object instance + ending (string): Either ',' or ';' indication if this is an instance or a definition + + """ + if ending == ";": + return "struct " + name + " " + return "." + +def parse_object(instream, fline, file = sys.stdout, objects = {}, tabs = "", ending = ";"): + """Translates all Object instatiation into initialized C structs + Note that dict arguments in python are passed as reference + + Parameters: + instream (stream): Input stream of topology2 file we are decoding + fline (string): First input line that was read by the caller + file (stream): Where the C struct instance or definition is printed + objects (dict): Dict to store fist level contained objects with Docxyge docs + ending (string): Either ',' or ';' indication if this is an instance or a definition + tabs (string): Current level of indentation + + Returns: + string: The original code that was translated + """ + logging.debug("fline: %s", fline) + tok = fline.split(maxsplit = 2) + obj = tok[0].split(".") + cbsum = cbracket_count(fline) + sbsum = sbracket_count(fline) + name = "" + doxy = "" + raw_code = "" + if len(obj) == 4 and sbsum == 0 and cbsum == 0: + # Assume Object.Base.name.1 { } + name = obj[2] + name = trim_c_id(name) + doxy = print_doxy(doxy, file = file) + prefix = object_instance_prefix(name, ending) + print("%s%s = {}%s" % (prefix, name, ending), file = file) + objects[name] = { "count": 1, "doxy": [doxy] } + elif len(obj) == 4 and sbsum == 0 and cbsum == 1: + # Assume Object.Base.name.1 { + name = obj[2] + name = trim_c_id(name) + doxy = print_doxy(doxy, file = file) + prefix = object_instance_prefix(name, ending) + print("%s%s%s = {" % (tabs, prefix, name), file = file) + raw_code = raw_code + parse_members(instream, cbsum, tabs + "\t", file) + print("%s}%s" % (tabs, ending), file = file) + objects[name] = { "count": 1, "doxy": [doxy] } + elif len(obj) == 3 and sbsum == 1 and cbsum == 0: + # Assume Object.Base.name [ + name = obj[2] + name = trim_c_id(name) + doxy = print_doxy(doxy, file = file) + prefix = object_instance_prefix(name, ending) + print("%s%s%s[] = {" % (tabs, prefix, name), file = file) + objects[name] = { "count": 0, "doxy": [] } + for line in instream: + raw_code = raw_code + line + if parse_include(line): + continue + if (code := parse_include_by_key(line, instream, sys.stdout)): + raw_code = raw_code + code + continue + sbsum = sbsum + sbracket_count(line) + cbsum = cbsum + cbracket_count(line) + (doxy, line) = doxy_check_add(doxy, line) + if sbsum < 1: + print("%s}%s" % (tabs, ending), file = file) + break + if cbsum == 1: # Assume starting { on its own line + objects[name]["count"] = objects[name]["count"] + 1 + objects[name]["doxy"].append(doxy) + doxy = print_doxy(doxy, file = file) + print("%s\t{" % tabs, file = file) + raw_code = raw_code + parse_members(instream, cbsum, tabs + "\t\t", file) + cbsum = 0 + print("%s\t}," % tabs, file = file) + elif len(obj) == 2 and sbsum == 0 and cbsum == 1: + # Assume Object.Base { + for line in instream: + raw_code = raw_code + line + if parse_include(line): + continue + if (code := parse_include_by_key(line, instream, file)): + raw_code = raw_code + code + continue + sbsum = sbsum + sbracket_count(line) + cbsum = cbsum + cbracket_count(line) + (doxy, line) = doxy_check_add(doxy, line) + if cbsum < 1: # Ending } found + break + if sbsum == 1 and cbsum == 1 and sbracket_count(line) > 0: + # Assume Class_name [ + tok = line.split() + name = trim_c_id(tok[0]) + objects[name] = { "count": 0, "doxy": [] } + doxy = print_doxy(doxy, file = file) + prefix = object_instance_prefix(name, ending) + print("%s%s%s[] = {" % (tabs, prefix, name), file = file) + elif sbsum == 1 and cbsum == 2: + # Assume class_name [ \n { \n + objects[name]["count"] = objects[name]["count"] + 1 + objects[name]["doxy"].append(doxy) + doxy = print_doxy(doxy, file = file) + print("%s\t{" % tabs, file = file) + raw_code = raw_code + parse_members(instream, 1, tabs + "\t\t", file) + print("%s\t}," % tabs, file = file) + cbsum = 1 + elif sbsum == 0 and cbsum == 1 and sbracket_count(line) < 0: + # Assume ending ] of class table alone one its own line + print("%s}%s" % (tabs, ending), file = file) + elif sbsum == 0 and cbsum == 1 and line.count("}") == 1: + # Assume name."1" {} + # No init values, so no instantiation code needed, but we still need + # to store the object into objexts dict. + tok = line.split() + tok = tok[0].split(".") + name = trim_c_id(tok[0]) + if not objects.get(name): + objects[name] = { "count": 0, "doxy": [] } + objects[name]["count"] = objects[name]["count"] + 1 + objects[name]["doxy"].append(doxy) + doxy = print_doxy(doxy, file = file) + elif sbsum == 0 and cbsum == 2 and cbracket_count(line) == 1: + # Assume name."1" { + tok = line.split() + tok = tok[0].split(".") + name = trim_c_id(tok[0]) + mname = name + if len(tok) > 1: + idx = tok[1].strip(" \"\'") + if idx.isidentifier() or idx.isnumeric(): + mname = mname + "_" + idx + if not objects.get(name): + objects[name] = { "count": 0, "doxy": [] } + objects[name]["count"] = objects[name]["count"] + 1 + objects[name]["doxy"].append(doxy) + doxy = print_doxy(doxy, file = file) + prefix = object_instance_prefix(name, ending) + print("%s%s%s = {" % (tabs, prefix, mname), file = file) + raw_code = raw_code + parse_members(instream, 1, tabs + "\t", file) + cbsum = 1 + print("%s}%s" % (tabs, ending), file = file) + return raw_code + +def parse_object_and_make_raw_code_page(filename, index, instream, fline): + """Parses an object instance outputs its C equivalent, and creates a raw code page of + of it and prints a reference to it. Handles an "Object.... {}" instance completely + and produces possibly multiple initialized C structs, but always just one raw code + block containing the "Object.... {}" block completely. + + Parameters: + filename (stream): Name of the file we are paring + index (int): The index of the decoded Object block in this file we are handling + instream (stream): Input stream of topology2 file we are decoding + fline (string): First input line that was read by the caller + + """ + tok = filename.split("/") + filename = fname = tok[len(tok)-1] + fname = fname.replace("-", "_") + fname = fname.replace(".", "_") + c_instances = io.StringIO() + raw_code = fline + parse_object(instream, fline, c_instances) + print("/*! \\page %s_%d_rawcode The %s instances #%d in topology2 code\n\t\\code{.unparsed}" % + (fname, index, filename, index)) + print(raw_code) + print("\t\\endcode\n*/") + print("//! \\brief \\ref %s_%d_rawcode" % (fname, index)) + print(c_instances.getvalue()) + c_instances.close() + +# Main starts here, apart from debug file opening +filename = sys.argv[1] +logging.info("file: %s", filename) +with open(filename, "r+", encoding="ascii") as instream: + block_idx = 0 + + shortfname = filename[filename.find("/topology2/"):] + print("//! \\file %s" % shortfname[11:]) + print("//! Source file can be found " + + "here." + % shortfname) + for line in instream: + if parse_include(line): + continue + if parse_define_block(line, instream): + continue + if parse_include_by_key(line, instream, sys.stdout): + continue + if re.search(r"^\s*Class\.", line): + parse_class(instream, line) + elif re.search(r"^\s*Object\.", line): + parse_object_and_make_raw_code_page(filename, block_idx, instream, line) + block_idx = block_idx + 1 + elif line.find("##") >= 0: + sys.stdout.write(line.replace("##", "//!", 1)) + else: + sys.stdout.write("\n") diff --git a/tools/topology/topology2/doc/topology2-generate-contents.sh b/tools/topology/topology2/doc/topology2-generate-contents.sh new file mode 100755 index 000000000000..ea5b4ed059cb --- /dev/null +++ b/tools/topology/topology2/doc/topology2-generate-contents.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2023 Intel Corporation. All rights reserved. +# This script generates the contents page and stores it in extra-contents/contents.doxy + +set -e +cd "$(dirname "$0")" + +generate_contents () +{ + cat < Class.Widget."gain" { # - # Pipeline ID for the gain widget object + ## Pipeline ID for the gain widget object # DefineAttribute."index" {} # - # gain object instance + ## gain object instance # DefineAttribute."instance" {} @@ -35,9 +35,9 @@ Class.Widget."gain" { # # - # Gain curve type. The values provided will be translated to integer values - # as specified in the tuple_values array. - # For example: "linear" is translated to 0, "log" to 1 etc. + ## Gain curve type. The values provided will be translated to integer values + ## as specified in the tuple_values array. + ## For example: "linear" is translated to 0, "log" to 1 etc. # DefineAttribute."curve_type" { type "string" @@ -56,7 +56,7 @@ Class.Widget."gain" { } # - # Gain curve in milliseconds + ## Gain curve in milliseconds # DefineAttribute."curve_duration" { # Token set reference name @@ -71,8 +71,8 @@ Class.Widget."gain" { # Attribute categories attributes { # - # The PGA widget name would be constructed using the index and instance attributes. - # For ex: "gain.1.1" or "gain.10.2" etc. + ## The PGA widget name would be constructed using the index and instance attributes. + ## For ex: "gain.1.1" or "gain.10.2" etc. # !constructor [ "index" @@ -114,9 +114,9 @@ Class.Widget."gain" { # gain widget mixer controls # Object.Control { - # gain mixer control + ## gain mixer control mixer."1" { - #Channel register and shift for Front Left/Right + ## Channel register and shift for Front Left/Right Object.Base.channel.1 { name "fl" shift 0 @@ -128,7 +128,7 @@ Class.Widget."gain" { Object.Base.ops.1 { name "ctl" info "volsw" - #256 binds the mixer control to volume get/put handlers + ## get = 256 binds the mixer control to volume get/put handlers get 256 put 256 } diff --git a/tools/topology/topology2/include/components/widget-common.conf b/tools/topology/topology2/include/components/widget-common.conf index af87e4e70f7d..3c398300ef89 100644 --- a/tools/topology/topology2/include/components/widget-common.conf +++ b/tools/topology/topology2/include/components/widget-common.conf @@ -2,12 +2,12 @@ # Common widget attribute definitions # -# instance of the widget object +## instance of the widget object DefineAttribute."instance" {} # -# no_pm - maps to the DAPM widget's reg field -# "false" value indicates that there is no direct DAPM for this widget +## no_pm - maps to the DAPM widget's reg field +## "false" value indicates that there is no direct DAPM for this widget # DefineAttribute."no_pm" { type "string" @@ -20,26 +20,26 @@ DefineAttribute."no_pm" { } # -# Widget Type - maps to the widget ID with values of type enum SND_SOC_TPLG_DAPM_* +## Widget Type - maps to the widget ID with values of type enum SND_SOC_TPLG_DAPM_* # DefineAttribute."type" { type "string" } # -# Stream name - maps to the DAPM widget's stream name +## Stream name - maps to the DAPM widget's stream name # DefineAttribute."stream_name" { type "string" } # -# Event type widget binds to +## Event type widget binds to # DefineAttribute.event_type {} # -# Widget event flags +## Widget event flags # DefineAttribute.event_flags {} @@ -47,7 +47,7 @@ DefineAttribute.event_flags {} # Attributes with a "token_ref" value will be added to widget's private data # -# widget format +## widget format DefineAttribute."format" { type "string" # Token set reference name and type @@ -62,49 +62,50 @@ DefineAttribute."format" { } } -# ID of the core this widget should be scheduled on +## ID of the core this widget should be scheduled on DefineAttribute."core_id" { # Token set reference name and type token_ref "comp.word" } -# number of periods to preload +## number of periods to preload DefineAttribute."preload_count" { # Token set reference name and type token_ref "comp.word" } -# Number of sink pins a widget can support +## Number of sink pins a widget can support DefineAttribute."num_input_pins" { # Token set reference name and type token_ref "comp.word" } -# Number of source pins a widget can support +## Number of source pins a widget can support DefineAttribute."num_output_pins" { # Token set reference name and type token_ref "comp.word" } -# Number of supported sink(input) audio formats +## Number of supported sink(input) audio formats DefineAttribute."num_input_audio_formats" { # Token set reference name and type token_ref "comp.word" } -# Number of supported source(output) audio formats +## Number of supported source(output) audio formats DefineAttribute."num_output_audio_formats" { # Token set reference name and type token_ref "comp.word" } -# Widget UUID +## Widget UUID DefineAttribute.uuid { type "string" # Token set reference name and type token_ref "comp.uuid" } +## Whether to add this widget's name to the beginning of all its associated mixer names DefineAttribute."no_wname_in_kcontrol_name" { type "string" # Token set reference name diff --git a/tools/topology/topology2/include/controls/common.conf b/tools/topology/topology2/include/controls/common.conf index 77593fccc1fb..0cf249914070 100644 --- a/tools/topology/topology2/include/controls/common.conf +++ b/tools/topology/topology2/include/controls/common.conf @@ -1,15 +1,19 @@ -# Common class definitions for controls - -# -# Class for channel objects. These are instantiated as: -# Object.Base.channel."fl" { -# reg 1 -# shift 0 -# } -# +## \file common.conf +## \brief Common class definitions for controls. + +## \struct channel +## \brief Class for channel objects. +## These are instantiated as: +## +## Object.Base.channel."fl" { +## reg 1 +## shift 0 +## } +## + Class.Base."channel" { DefineAttribute."instance" {} - # name of the channel + ## name of the channel DefineAttribute."name" { type "string" } @@ -33,13 +37,18 @@ Class.Base."channel" { shift 1 } -# Class definition for control ops. These are instantiated as: -# Object.Base.ops."ctl" { -# info "volsw" -# get "259" -# put "259" -# } -# +## \struct ops +## \brief Class definition for control ops. +## +## These are instantiated as: +## +## Object.Base.ops."ctl" { +## info "volsw" +## get "259" +## put "259" +## } +## + Class.Base."ops" { DefineAttribute."instance" {} # ops name @@ -68,13 +77,17 @@ Class.Base."ops" { } } -# Class definition for control extops. These are instantiated as: -# Object.Base.extops."ctl" { -# info "volsw" -# get "258" -# put "258" -# } -# +## \struct extops +## \brief Class definition for control extops. +## These are instantiated as: +## +## Object.Base.extops."ctl" { +## info "volsw" +## get "258" +## put "258" +## } +## + Class.Base."extops" { DefineAttribute."instance" {} # extops name @@ -104,12 +117,15 @@ Class.Base."extops" { } } -# -# Class definition for scale objects. These are instantiated as follows: -# Object.Base.scale."name" { -# mute 1 -# } -# +## \struct scale +## \brief Class definition for scale objects. +## These are instantiated as follows: +## +## Object.Base.scale."name" { +## mute 1 +## } +## + Class.Base."scale" { DefineAttribute."instance" {} DefineAttribute."name" { @@ -139,11 +155,17 @@ Class.Base."scale" { mute 1 } -# -# Class definition for tlv objects. These are instantiated as follows: -# Object.Base.tlv."vtlv_m64s2" { -# Object.Base.scale."0" {} -# } +## \struct tlv +## \brief Class definition for tlv objects. +## These are instantiated as follows: +## +## Object.Base.tlv."vtlv_m64s2" { +## Object.Base.scale."0" {} +## } +## +## The linked object instance is \link scale \endlink . +## + Class.Base."tlv" { DefineAttribute."instance" {} DefineAttribute."name" { diff --git a/tools/topology/topology2/include/controls/mixer.conf b/tools/topology/topology2/include/controls/mixer.conf index 66b5085cf857..191230ec5bdc 100644 --- a/tools/topology/topology2/include/controls/mixer.conf +++ b/tools/topology/topology2/include/controls/mixer.conf @@ -1,41 +1,45 @@ -# -# Mixer kcontrol class. All attributes defined herein are namespaced -# by alsatplg to "Object.Control.mixer.N.attribute_name" -# -# Usage: this component can be used by instantiating it in the parent object. i.e. -# -# Object.Control.mixer."N" { -# index 1 -# name "1 Master Playback Volume" -# mas 32 -# Object.Base.channel.1 { -# name "fl" -# shift 0 -# reg 0 -# } -# Object.Base.channel.2 { -# name "fr" -# shift 1 -# reg 1 -# } -# Object.Base.ops."ctl" { -# info "volsw" -# get "258" -# put "258" -# } -# } -# -# Where N is the unique instance number for the buffer object within the same alsaconf node. -# The mixer control object should also include the ops, channels and tlv objects in the object -# instance +## \struct mixer +## \brief Topology Mixer class +## +## All attributes defined herein are namespaced +## by alsatplg to "Object.Control.mixer.N.attribute_name" +## +## Usage: this component can be used by instantiating it in the parent object. i.e. +## +## Object.Control.mixer."N" { +## index 1 +## name "1 Master Playback Volume" +## max 32 +## Object.Base.channel.1 { +## name "fl" +## shift 0 +## reg 0 +## } +## Object.Base.channel.2 { +## name "fr" +## shift 1 +## reg 1 +## } +## Object.Base.ops."ctl" { +## info "volsw" +## get "258" +## put "258" +## } +## } +## +## The linked object instaces are \link channel \endlink and \link ops \endlink . +## +## Where N is the unique instance number for the buffer object within the same alsaconf node. +## The mixer control object should also include the ops, channels and tlv objects in the object +## instance Class.Control."mixer" { - # - # Pipeline ID for the mixer object - # - DefineAttribute."index" {} + ## + ## @ Pipeline ID for the mixer object + ## + DefineAttribute."index" {} ##< Automatically given unique index # # Instance of mixer object in the same alsaconf node diff --git a/tools/topology/topology2/include/dais/mic_extension.conf b/tools/topology/topology2/include/dais/mic_extension.conf index 6dc12a1f21b2..31628e9b2241 100644 --- a/tools/topology/topology2/include/dais/mic_extension.conf +++ b/tools/topology/topology2/include/dais/mic_extension.conf @@ -1,3 +1,6 @@ +## \brief Mic extension class +## \struct mic_extension + Class.Base."mic_extension" { DefineAttribute."id" {} diff --git a/tools/topology/topology2/include/dais/pdm_config.conf b/tools/topology/topology2/include/dais/pdm_config.conf index 2a8759a7742c..3745bea7998e 100644 --- a/tools/topology/topology2/include/dais/pdm_config.conf +++ b/tools/topology/topology2/include/dais/pdm_config.conf @@ -1,3 +1,6 @@ +## \brief Class for PDM config +## \struct pdm_config + Class.Base."pdm_config" { DefineAttribute."instance" {} # diff --git a/tools/topology/topology2/include/pipelines/volume-playback.conf b/tools/topology/topology2/include/pipelines/volume-playback.conf index d04830b2168a..fa007609ab4c 100644 --- a/tools/topology/topology2/include/pipelines/volume-playback.conf +++ b/tools/topology/topology2/include/pipelines/volume-playback.conf @@ -1,24 +1,26 @@ -# -# Volume playback pipeline -# -# A simple pipeline. All attributes defined herein are namespaced by alsatplg to -# "Object.Pipeline.volume-playback.N.attribute_name" -# -# Usage: this component can be used by declaring in the top-level topology conf file as follows: -# -# Object.Pipeline.volume-playback."N" { -# format "s16le" -# period 1000 -# time_domain "timer" -# channels 2 -# rate 48000 -# } -# -# where N is the unique pipeline_id for this pipeline object within the same alsaconf node. -# -# -# (source) host.N.playback -> buffer.N.1 -> volume.N.1 -> buffer.N.2 (sink endpoint) -# +## \struct volume_playback +## \brief Volume playback pipeline +## +## \par Instantiating volume-playback pipeline +## +## A simple pipeline. All attributes defined herein are namespaced by alsatplg +## to "Object.Pipeline.volume-playback.N.attribute_name" +## +## Usage: this component can be used by declaring in the top-level topology conf file as follows: +## +## Object.Pipeline.volume-playback."N" { +## format "s16le" +## period 1000 +## time_domain "timer" +## channels 2 +## rate 48000 +## } +## +## where N is the unique pipeline_id for this pipeline object within the same alsaconf node. +## +## +## (source) host.N.playback -> buffer.N.1 -> volume.N.1 -> buffer.N.2 (sink endpoint) +## @@ -31,7 +33,7 @@ Class.Pipeline."volume-playback" { attributes { - # pipeline name is constructed as "volume-playback.1" + ## pipeline name is constructed as "volume-playback.1" !constructor [ "index" ] @@ -49,9 +51,11 @@ Class.Pipeline."volume-playback" { } Object.Widget { + ## The pipeline object for captured playback pipeline."1" {} host."playback" { + ## "aif_in" is for playback type "aif_in" } diff --git a/tools/topology/topology2/platform/intel/ssp_aux_config.conf b/tools/topology/topology2/platform/intel/ssp_aux_config.conf index 1a814edef07e..c084faa4195c 100644 --- a/tools/topology/topology2/platform/intel/ssp_aux_config.conf +++ b/tools/topology/topology2/platform/intel/ssp_aux_config.conf @@ -1,3 +1,5 @@ +## \struct mn_config + Class.Base."mn_config" { DefineAttribute."id" {}