1#!/usr/bin/env python3 2# 3# Copyright © 2024 Igalia S.L. 4# SPDX-License-Identifier: MIT 5 6import argparse 7import ctypes 8import logging 9import re 10from datetime import datetime 11from mako.template import Template 12from pycparser import parse_file, c_ast 13 14logger = logging.getLogger('hwdb') 15 16template = """/* 17 * Copyright © 2024 Igalia S.L. 18 * SPDX-License-Identifier: MIT 19 */ 20 21#pragma once 22 23#include <stdint.h> 24 25typedef struct 26{ 27% for type, name in struct: 28 ${type} ${name}; 29% endfor 30} gcsFEATURE_DATABASE; 31 32static gcsFEATURE_DATABASE gChipInfo[] = { 33% for entry in entries: 34 { 35% for name, value in entry: 36 ${value}, /* ${name} */ 37% endfor 38 }, 39% endfor 40}; 41 42static inline gcsFEATURE_DATABASE * 43gcQueryFeatureDB( 44 uint32_t ChipID, 45 uint32_t ChipVersion, 46 uint32_t ProductID, 47 uint32_t EcoID, 48 uint32_t CustomerID 49 ) 50{ 51 int entryNum = sizeof(gChipInfo) / sizeof(gChipInfo[0]); 52 53 /* check formal release entries first */ 54 for (int i = 0; i < entryNum; ++i) 55 { 56 if ((gChipInfo[i].chipID == ChipID) 57 && (gChipInfo[i].chipVersion == ChipVersion) 58 && (gChipInfo[i].productID == ProductID) 59 && (gChipInfo[i].ecoID == EcoID) 60 && (gChipInfo[i].customerID == CustomerID) 61 && (gChipInfo[i].formalRelease) 62 ) 63 { 64 return &gChipInfo[i]; 65 } 66 } 67 68 /* check informal release entries if we dont find in formal entries */ 69 for (int i = 0; i < entryNum; ++i) 70 { 71 if ((gChipInfo[i].chipID == ChipID) 72 && ((gChipInfo[i].chipVersion & 0xFFF0) == (ChipVersion & 0xFFF0)) 73 && (gChipInfo[i].productID == ProductID) 74 && (gChipInfo[i].ecoID == EcoID) 75 && (gChipInfo[i].customerID == CustomerID) 76 && (!gChipInfo[i].formalRelease) 77 ) 78 { 79 return &gChipInfo[i]; 80 } 81 } 82 83 return 0; 84} 85""" 86 87 88class HeaderFile(c_ast.NodeVisitor): 89 """Class representing a complete header file""" 90 91 # Regular expression to match the date and time in the comment 92 _DATE_RE = re.compile(r'/\*Auto created on (\d{4}-\d{2}-\d{2} \d{2}:\d{2})\*/') 93 94 def __init__(self, filename): 95 self.filename = filename 96 self.structs = {} 97 self.data = [] 98 self.date_time = None 99 self.database_struct = None 100 101 self._read_date() 102 self._parse() 103 104 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)) 105 106 def _read_date(self): 107 """Function parsing the creation date with re.""" 108 # Read the content of the file and search for pattern 109 with open(self.filename, 'r', encoding="utf-8") as file: 110 file_content = file.read() 111 112 match = self._DATE_RE.search(file_content) 113 114 if match: 115 self.date_time = datetime.strptime(match.group(1), '%Y-%m-%d %H:%M') 116 117 def _parse(self): 118 ast = parse_file(self.filename, use_cpp=True, cpp_args=['-E', r'-I./utils/fake_libc_include', '-DgctUINT32=unsigned int', '-DgctINT=int']) 119 self.visit(ast) 120 121 self.database_struct = self.structs['gcsFEATURE_DATABASE'] 122 123 def visit_Typedef(self, node): 124 if isinstance(node.type, c_ast.TypeDecl) and isinstance(node.type.type, c_ast.Struct): 125 struct_node = node.type.type 126 struct_name = node.name # Typedef name as the struct name 127 fields = self._extract_fields_from_struct(struct_node) 128 if fields is not None: 129 # Create the ctypes.Structure subclass and add it to the structures dictionary 130 self.structs[struct_name] = type(struct_name, (ctypes.Structure,), {'_fields_': fields}) 131 132 def _extract_fields_from_struct(self, struct_node): 133 """Function returning all fields of struct.""" 134 fields = [] 135 for decl in (struct_node.decls or []): 136 if isinstance(decl.type, c_ast.TypeDecl) or isinstance(decl.type, c_ast.PtrDecl): 137 field_name = decl.name 138 field_type = self._map_type_to_ctypes(decl.type.type, decl.bitsize) 139 if field_type: 140 fields.append((field_name, field_type)) 141 elif isinstance(decl.type, c_ast.ArrayDecl): 142 # Handle array type 143 field_name = decl.type.type.declname 144 element_type = self._map_type_to_ctypes(decl.type.type.type) 145 array_size = int(decl.type.dim.value) # Assuming dim is a Constant node with the size as its value 146 if element_type: 147 fields.append((field_name, element_type * array_size)) 148 149 return fields if fields else None 150 151 def _map_type_to_ctypes(self, type_node, bitsize=None): 152 """Function returning a ctype type based node type.""" 153 type_mappings = { 154 'bool': ctypes.c_bool, 155 'unsigned int': ctypes.c_uint, 156 'int': ctypes.c_int, 157 } 158 159 if isinstance(type_node, c_ast.IdentifierType): 160 c_type = ' '.join(type_node.names) 161 162 if bitsize and bitsize.value == '1': 163 c_type = 'bool' 164 165 return type_mappings.get(c_type) 166 elif isinstance(type_node, c_ast.TypeDecl): 167 return ctypes.c_char_p 168 169 return None 170 171 def visit_Decl(self, node): 172 # Check if the node is a declaration of an array of structs 173 if isinstance(node.type, c_ast.ArrayDecl) and isinstance(node.type.type, c_ast.TypeDecl): 174 struct_name = node.type.type.type.names[0] 175 if struct_name in self.structs: 176 elements = self._parse_array_initializer(node.init, self.structs[struct_name]) 177 self.data.extend(elements) 178 179 def _parse_initializer(self, initializer, struct): 180 """Function returning one parsed struct initializer.""" 181 return [ 182 (param if not isinstance(param, str) else param.encode('utf-8')) 183 for index, expr in enumerate(initializer.exprs) 184 for param in [self._parse_expr(expr, getattr(struct._fields_[index][1], '_length_', None))] 185 ] 186 187 def _parse_array_initializer(self, init, struct_class): 188 """Function returning a fully processed struct initializer list.""" 189 assert (isinstance(init, c_ast.InitList)) 190 return [struct_class(*self._parse_initializer(initializer, struct_class)) for initializer in init.exprs] 191 192 def _parse_expr(self, expr, expected_size=None): 193 """Function returning parsed expression.""" 194 # Direct handling of constant types 195 if isinstance(expr, c_ast.Constant): 196 if expr.type == "int": 197 # Base 0 automatically handles decimal, hex, and octal 198 return int(expr.value, 0) 199 elif expr.type == "string": 200 return expr.value.strip('"') 201 202 # Handling arrays with potential conversion to ctypes arrays 203 elif isinstance(expr, c_ast.InitList) and expected_size is not None: 204 element_list = [self._parse_expr(e) for e in expr.exprs] 205 206 # Ensure the list matches expected size, filling with zeros if necessary 207 if len(element_list) < expected_size: 208 element_list.extend([0] * (expected_size - len(element_list))) 209 210 # Convert to ctypes array, dynamically adjusting type based on context if needed 211 return (ctypes.c_uint * expected_size)(*element_list) 212 213 # Fallback or default return for unhandled cases 214 return None 215 216 217def merge_structures(structures): 218 """Function creating a new type by merging provided types.""" 219 combined_fields = [] 220 for struct in structures: 221 for field_name, field_type in struct._fields_: 222 # Check if the field name already exists 223 if field_name not in [field[0] for field in combined_fields]: 224 combined_fields.append((field_name, field_type)) 225 226 # Create a new structure dynamically 227 return type("MergedStructure", (ctypes.Structure,), {'_fields_': combined_fields}) 228 229 230def get_field_type(c, field_name): 231 """Function returning the type of a field type based on its name.""" 232 for field in c._fields_: 233 if field[0] == field_name: 234 return field[1] 235 236 return None 237 238 239def create_merged_struct(c, entry): 240 """Function returning a fully populate instance of MergedStructure.""" 241 fields = [] 242 for field_name, field_type in c._fields_: 243 # We might need to 'upgrade' to an array - check field type too. 244 # e.g. VIP_SRAM_SIZE_ARRAY -> VIP_SRAM_SIZE_ARRAY[9] 245 246 if hasattr(entry, field_name) and get_field_type(entry, field_name) is field_type: 247 fields.append(getattr(entry, field_name)) 248 else: 249 # Add a suitable default value 250 if field_type == ctypes.c_uint or field_type == ctypes.c_bool: 251 fields.append(0) 252 else: 253 # It must be an array 254 expected_size = getattr(field_type, '_length_') 255 fields.append((ctypes.c_uint * expected_size)(*[0] * expected_size)) 256 257 return c(*fields) 258 259 260def enumerate_struct(obj): 261 for field_name, field_type in obj._fields_: 262 type = 'uint32_t' 263 264 if field_type == ctypes.c_char_p: 265 type = 'const char *' 266 267 if field_type == ctypes.c_bool: 268 field_name = field_name + ':1' 269 270 if hasattr(field_type, '_length_'): 271 field_name = f'{field_name}[{field_type._length_}]' 272 273 yield type, field_name 274 275 276def enumerate_values(obj): 277 for field_name, field_type in obj._fields_: 278 value = getattr(obj, field_name) 279 280 if field_type in {ctypes.c_uint, ctypes.c_bool}: 281 value = hex(value) 282 elif field_type == ctypes.c_char_p: 283 value = '"{}"'.format(value.decode('utf-8')) 284 elif isinstance(value, ctypes.Array): 285 value = '{{{}}}'.format(', '.join(str(element) for element in value)) 286 287 yield field_name, value 288 289 290def main(): 291 parser = argparse.ArgumentParser() 292 parser.add_argument('--output', required=True, type=str, action="store", 293 help='output C header file') 294 parser.add_argument('headers', metavar='header', type=str, nargs='+', 295 help='gc database header to process') 296 parser.add_argument('--verbose', "-v", action="store_true", 297 help='be verbose') 298 args = parser.parse_args() 299 300 if args.verbose: 301 logging.basicConfig(level=logging.DEBUG) 302 303 files = [HeaderFile(header) for header in args.headers] 304 structs = [file.database_struct for file in files] 305 merged = merge_structures(structs) 306 logger.debug('merged struct: %u members', len(merged._fields_)) 307 308 entries = [] 309 for file in files: 310 logger.debug('Processing %s', file.filename) 311 for entry in file.data: 312 merged_entry = create_merged_struct(merged, entry) 313 entry_data = list(enumerate_values(merged_entry)) 314 entries.append(entry_data) 315 316 logger.debug('Total entries: %u', len(entries)) 317 318 with open(args.output, "w", encoding="UTF-8") as fh: 319 print(Template(template).render(struct=enumerate_struct(merged), entries=entries), file=fh) 320 321 322if __name__ == '__main__': 323 main() 324