1#! /usr/bin/env python3 2# 3# Copyright © 2024 Collabora Ltd. and Red Hat Inc. 4# SPDX-License-Identifier: MIT 5 6import argparse 7import os.path 8import re 9import sys 10 11from collections import namedtuple 12from mako.template import Template 13 14TEMPLATE_RS = Template("""\ 15// Copyright © 2024 Collabora Ltd. and Red Hat Inc. 16// SPDX-License-Identifier: MIT 17 18// This file is generated by struct_parser.py. DO NOT EDIT! 19 20#![allow(non_snake_case)] 21 22use std::ops::Range; 23 24% for s in structs: 25 % for f in s.fields: 26 % if f.stride: 27#[inline] 28pub fn ${s.name}_${f.name}(i: usize) -> Range<usize> { 29 (i * ${f.stride} + ${f.lo})..(i * ${f.stride} + ${f.hi + 1}) 30} 31 % else: 32pub const ${s.name}_${f.name}: Range<usize> = ${f.lo}..${f.hi + 1}; 33 % endif: 34 % for e in f.enums: 35pub const ${s.name}_${f.name}_${e.name}: u32 = ${e.value}; 36 % endfor 37 % endfor 38% endfor 39""") 40 41STRUCTS = [ 42 'SPHV3', 43 'SPHV4', 44 'TEXHEADV2', 45 'TEXHEADV3', 46 'TEXHEAD_BL', 47 'TEXHEAD_1D', 48 'TEXHEAD_PITCH', 49 # This one goes last because it's a substring of the others 50 'TEXHEAD', 51 'TEXSAMP', 52 'QMDV00_06', 53 'QMDV01_06', 54 'QMDV01_07', 55 'QMDV02_01', 56 'QMDV02_02', 57 'QMDV02_03', 58 'QMDV02_04', 59 'QMDV03_00', 60] 61 62Enum = namedtuple('Enum', ['name', 'value']) 63 64class Field(object): 65 def __init__(self, name, lo, hi, stride=0): 66 self.name = name 67 self.lo = lo 68 self.hi = hi 69 self.stride = stride 70 self.enums = [] 71 72 def add_enum(self, name, value): 73 self.enums.append(Enum(name, value)) 74 75class Struct(object): 76 def __init__(self, name): 77 self.name = name 78 self.fields = [] 79 80 def add_field(self, name, lo, hi, stride=0): 81 self.fields.append(Field(name, lo, hi, stride)) 82 83DRF_RE = re.compile(r'(?P<hi>[0-9]+):(?P<lo>[0-9]+)') 84FIELD_NAME_RE = re.compile(r'_?(?P<dw>[0-9]+)?_?(?P<name>.*)') 85MW_RE = re.compile(r'MW\((?P<hi>[0-9]+):(?P<lo>[0-9]+)\)') 86MW_ARR_RE = re.compile(r'MW\(\((?P<hi>\d+)\+\(i\)\*(?P<stride>\d+)\):\((?P<lo>[0-9]+)\+\(i\)\*(?P=stride)\)\)') 87 88def parse_header(nvcl, file): 89 structs = {} 90 for line in file: 91 line = line.strip().split() 92 if not line: 93 continue 94 95 if line[0] != '#define': 96 continue 97 98 if not line[1].startswith(nvcl): 99 continue 100 101 name = line[1][(len(nvcl)+1):] 102 103 struct = None 104 for s in STRUCTS: 105 if name.startswith(s): 106 if s not in structs: 107 structs[s] = Struct(s) 108 struct = structs[s] 109 name = name[len(s):] 110 break 111 112 if struct is None: 113 continue 114 115 name_m = FIELD_NAME_RE.match(name) 116 name = name_m.group('name') 117 118 drf = DRF_RE.match(line[2]) 119 mw = MW_RE.match(line[2]) 120 mw_arr = MW_ARR_RE.match(line[2]) 121 if drf: 122 dw = int(name_m.group('dw')) 123 lo = int(drf.group('lo')) + dw * 32 124 hi = int(drf.group('hi')) + dw * 32 125 struct.add_field(name, lo, hi) 126 elif mw: 127 lo = int(mw.group('lo')) 128 hi = int(mw.group('hi')) 129 struct.add_field(name, lo, hi) 130 elif mw_arr: 131 lo = int(mw_arr.group('lo')) 132 hi = int(mw_arr.group('hi')) 133 stride = int(mw_arr.group('stride')) 134 assert name.endswith('(i)') 135 struct.add_field(name.removesuffix('(i)'), lo, hi, stride) 136 else: 137 for f in struct.fields: 138 if name.startswith(f.name + '_'): 139 name = name[(len(f.name)+1):] 140 f.add_enum(name, line[2]) 141 142 return list(structs.values()) 143 144NVCL_RE = re.compile(r'cl(?P<clsver>[0-9a-f]{4}).*') 145 146def main(): 147 parser = argparse.ArgumentParser() 148 parser.add_argument('--out-rs', required=True, help='Output Rust file.') 149 parser.add_argument('--in-h', 150 help='Input class header file.', 151 required=True) 152 args = parser.parse_args() 153 154 clheader = os.path.basename(args.in_h) 155 nvcl = NVCL_RE.match(clheader).group('clsver') 156 nvcl = nvcl.upper() 157 nvcl = "NV" + nvcl 158 159 with open(args.in_h, 'r', encoding='utf-8') as f: 160 structs = parse_header(nvcl, f) 161 162 try: 163 with open(args.out_rs, 'w', encoding='utf-8') as f: 164 f.write(TEMPLATE_RS.render(structs=structs)) 165 166 except Exception: 167 # In the event there's an error, this imports some helpers from mako 168 # to print a useful stack trace and prints it, then exits with 169 # status 1, if python is run with debug; otherwise it just raises 170 # the exception 171 import sys 172 from mako import exceptions 173 print(exceptions.text_error_template().render(), file=sys.stderr) 174 sys.exit(1) 175 176if __name__ == '__main__': 177 main() 178