diff --git a/src/etnaviv/hwdb/hwdb.h.py b/src/etnaviv/hwdb/hwdb.h.py new file mode 100755 index 00000000000..2116b5d066f --- /dev/null +++ b/src/etnaviv/hwdb/hwdb.h.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +# +# Copyright © 2024 Igalia S.L. +# SPDX-License-Identifier: MIT + +import argparse +import ctypes +import logging +import re +from datetime import datetime +from mako.template import Template +from pycparser import parse_file, c_ast + +logger = logging.getLogger('hwdb') + +template = """/* + * Copyright © 2024 Igalia S.L. + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +typedef struct +{ +% for type, name in struct: + ${type} ${name}; +% endfor +} gcsFEATURE_DATABASE; + +static gcsFEATURE_DATABASE gChipInfo[] = { +% for entry in entries: + { +% for name, value in entry: + ${value}, /* ${name} */ +% endfor + }, +% endfor +}; + +static inline gcsFEATURE_DATABASE * +gcQueryFeatureDB( + uint32_t ChipID, + uint32_t ChipVersion, + uint32_t ProductID, + uint32_t EcoID, + uint32_t CustomerID + ) +{ + int entryNum = sizeof(gChipInfo) / sizeof(gChipInfo[0]); + + /* check formal release entries first */ + for (int i = 0; i < entryNum; ++i) + { + if ((gChipInfo[i].chipID == ChipID) + && (gChipInfo[i].chipVersion == ChipVersion) + && (gChipInfo[i].productID == ProductID) + && (gChipInfo[i].ecoID == EcoID) + && (gChipInfo[i].customerID == CustomerID) + && (gChipInfo[i].formalRelease) + ) + { + return &gChipInfo[i]; + } + } + + /* check informal release entries if we dont find in formal entries */ + for (int i = 0; i < entryNum; ++i) + { + if ((gChipInfo[i].chipID == ChipID) + && ((gChipInfo[i].chipVersion & 0xFFF0) == (ChipVersion & 0xFFF0)) + && (gChipInfo[i].productID == ProductID) + && (gChipInfo[i].ecoID == EcoID) + && (gChipInfo[i].customerID == CustomerID) + && (!gChipInfo[i].formalRelease) + ) + { + return &gChipInfo[i]; + } + } + + return 0; +} +""" + + +class HeaderFile(c_ast.NodeVisitor): + """Class representing a complete header file""" + + # Regular expression to match the date and time in the comment + _DATE_RE = re.compile(r'/\*Auto created on (\d{4}-\d{2}-\d{2} \d{2}:\d{2})\*/') + + def __init__(self, filename): + self.filename = filename + self.structs = {} + self.data = [] + self.date_time = None + self.database_struct = None + + self._read_date() + self._parse() + + logger.debug('Parsed %s (autogenerated at %s, %u struct members, %u entries)', self.filename, self.date_time, len(self.database_struct._fields_), len(self.data)) + + def _read_date(self): + """Function parsing the creation date with re.""" + # Read the content of the file and search for pattern + with open(self.filename, 'r', encoding="utf-8") as file: + file_content = file.read() + + match = self._DATE_RE.search(file_content) + + if match: + self.date_time = datetime.strptime(match.group(1), '%Y-%m-%d %H:%M') + + def _parse(self): + ast = parse_file(self.filename, use_cpp=True, cpp_args=['-E', r'-I./utils/fake_libc_include', '-include', 'stdint.h', '-DgctUINT32=uint32_t', '-DgctINT=int']) + self.visit(ast) + + self.database_struct = self.structs['gcsFEATURE_DATABASE'] + + def visit_Typedef(self, node): + if isinstance(node.type, c_ast.TypeDecl) and isinstance(node.type.type, c_ast.Struct): + struct_node = node.type.type + struct_name = node.name # Typedef name as the struct name + fields = self._extract_fields_from_struct(struct_node) + if fields is not None: + # Create the ctypes.Structure subclass and add it to the structures dictionary + self.structs[struct_name] = type(struct_name, (ctypes.Structure,), {'_fields_': fields}) + + def _extract_fields_from_struct(self, struct_node): + """Function returning all fields of struct.""" + fields = [] + for decl in (struct_node.decls or []): + if isinstance(decl.type, c_ast.TypeDecl) or isinstance(decl.type, c_ast.PtrDecl): + field_name = decl.name + field_type = self._map_type_to_ctypes(decl.type.type, decl.bitsize) + if field_type: + fields.append((field_name, field_type)) + elif isinstance(decl.type, c_ast.ArrayDecl): + # Handle array type + field_name = decl.type.type.declname + element_type = self._map_type_to_ctypes(decl.type.type.type) + array_size = int(decl.type.dim.value) # Assuming dim is a Constant node with the size as its value + if element_type: + fields.append((field_name, element_type * array_size)) + + return fields if fields else None + + def _map_type_to_ctypes(self, type_node, bitsize=None): + """Function returning a ctype type based node type.""" + type_mappings = { + 'bool': ctypes.c_bool, + 'uint32_t': ctypes.c_uint, + 'int': ctypes.c_int, + } + + if isinstance(type_node, c_ast.IdentifierType): + c_type = ' '.join(type_node.names) + + if bitsize and bitsize.value == '1': + c_type = 'bool' + + return type_mappings.get(c_type) + elif isinstance(type_node, c_ast.TypeDecl): + return ctypes.c_char_p + + return None + + def visit_Decl(self, node): + # Check if the node is a declaration of an array of structs + if isinstance(node.type, c_ast.ArrayDecl) and isinstance(node.type.type, c_ast.TypeDecl): + struct_name = node.type.type.type.names[0] + if struct_name in self.structs: + elements = self._parse_array_initializer(node.init, self.structs[struct_name]) + self.data.extend(elements) + + def _parse_initializer(self, initializer, struct): + """Function returning one parsed struct initializer.""" + return [ + (param if not isinstance(param, str) else param.encode('utf-8')) + for index, expr in enumerate(initializer.exprs) + for param in [self._parse_expr(expr, getattr(struct._fields_[index][1], '_length_', None))] + ] + + def _parse_array_initializer(self, init, struct_class): + """Function returning a fully processed struct initializer list.""" + assert (isinstance(init, c_ast.InitList)) + return [struct_class(*self._parse_initializer(initializer, struct_class)) for initializer in init.exprs] + + def _parse_expr(self, expr, expected_size=None): + """Function returning parsed expression.""" + # Direct handling of constant types + if isinstance(expr, c_ast.Constant): + if expr.type == "int": + # Base 0 automatically handles decimal, hex, and octal + return int(expr.value, 0) + elif expr.type == "string": + return expr.value.strip('"') + + # Handling arrays with potential conversion to ctypes arrays + elif isinstance(expr, c_ast.InitList) and expected_size is not None: + element_list = [self._parse_expr(e) for e in expr.exprs] + + # Ensure the list matches expected size, filling with zeros if necessary + if len(element_list) < expected_size: + element_list.extend([0] * (expected_size - len(element_list))) + + # Convert to ctypes array, dynamically adjusting type based on context if needed + return (ctypes.c_uint * expected_size)(*element_list) + + # Fallback or default return for unhandled cases + return None + + +def merge_structures(structures): + """Function creating a new type by merging provided types.""" + combined_fields = [] + for struct in structures: + for field_name, field_type in struct._fields_: + # Check if the field name already exists + if field_name not in [field[0] for field in combined_fields]: + combined_fields.append((field_name, field_type)) + + # Create a new structure dynamically + return type("MergedStructure", (ctypes.Structure,), {'_fields_': combined_fields}) + + +def get_field_type(c, field_name): + """Function returning the type of a field type based on its name.""" + for field in c._fields_: + if field[0] == field_name: + return field[1] + + return None + + +def create_merged_struct(c, entry): + """Function returning a fully populate instance of MergedStructure.""" + fields = [] + for field_name, field_type in c._fields_: + # We might need to 'upgrade' to an array - check field type too. + # e.g. VIP_SRAM_SIZE_ARRAY -> VIP_SRAM_SIZE_ARRAY[9] + + if hasattr(entry, field_name) and get_field_type(entry, field_name) is field_type: + fields.append(getattr(entry, field_name)) + else: + # Add a suitable default value + if field_type == ctypes.c_uint or field_type == ctypes.c_bool: + fields.append(0) + else: + # It must be an array + expected_size = getattr(field_type, '_length_') + fields.append((ctypes.c_uint * expected_size)(*[0] * expected_size)) + + return c(*fields) + + +def enumerate_struct(obj): + for field_name, field_type in obj._fields_: + type = 'uint32_t' + + if field_type == ctypes.c_char_p: + type = 'const char *' + + if field_type == ctypes.c_bool: + field_name = field_name + ':1' + + if hasattr(field_type, '_length_'): + field_name = f'{field_name}[{field_type._length_}]' + + yield type, field_name + + +def enumerate_values(obj): + for field_name, field_type in obj._fields_: + value = getattr(obj, field_name) + + if field_type in {ctypes.c_uint, ctypes.c_bool}: + value = hex(value) + elif field_type == ctypes.c_char_p: + value = '"{}"'.format(value.decode('utf-8')) + elif isinstance(value, ctypes.Array): + value = '{{{}}}'.format(', '.join(str(element) for element in value)) + + yield field_name, value + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--output', required=True, type=str, action="store", + help='output C header file') + parser.add_argument('headers', metavar='header', type=str, nargs='+', + help='gc database header to process') + parser.add_argument('--verbose', "-v", action="store_true", + help='be verbose') + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + + files = [HeaderFile(header) for header in args.headers] + structs = [file.database_struct for file in files] + merged = merge_structures(structs) + logger.debug('merged struct: %u members', len(merged._fields_)) + + entries = [] + for file in files: + logger.debug('Processing %s', file.filename) + for entry in file.data: + merged_entry = create_merged_struct(merged, entry) + entry_data = list(enumerate_values(merged_entry)) + entries.append(entry_data) + + logger.debug('Total entries: %u', len(entries)) + + with open(args.output, "w", encoding="UTF-8") as fh: + print(Template(template).render(struct=enumerate_struct(merged), entries=entries), file=fh) + + +if __name__ == '__main__': + main()