diff --git a/src/nouveau/headers/class_parser.py b/src/nouveau/headers/class_parser.py index 86f4db25f3a..4dd61dea926 100644 --- a/src/nouveau/headers/class_parser.py +++ b/src/nouveau/headers/class_parser.py @@ -6,6 +6,8 @@ import argparse import os.path import sys +import re +import subprocess from mako.template import Template @@ -230,6 +232,126 @@ pub const ${version[0]}: u16 = ${version[1]}; % endif """) +TEMPLATE_RS_MTHD = Template("""\ + +// parsed class ${nvcl} + +## Write out the methods in Rust +%for mthd_name, mthd in mthddict.items(): +## Identify the field type. +<% +for field_name, field_value in mthd.field_defs.items(): + if field_name == 'V' and len(field_value) > 0: + mthd.field_rs_types[field_name] = to_camel(mthd_name) + 'V' + mthd.field_is_rs_enum[field_name] = True + elif len(field_value) > 0: + assert(field_name != "") + mthd.field_rs_types[field_name] = to_camel(mthd_name) + to_camel(field_name) + mthd.field_is_rs_enum[field_name] = True + elif mthd.is_float: + mthd.field_rs_types[field_name] = "f32" + mthd.field_is_rs_enum[field_name] = False + else: + mthd.field_rs_types[field_name] = "u32" + mthd.field_is_rs_enum[field_name] = False + + # TRUE and FALSE are special cases. + if len(field_value) == 2: + for enumerant in field_value: + if enumerant.lower() == 'true' or enumerant.lower() == 'false': + mthd.field_rs_types[field_name] = "bool" + mthd.field_is_rs_enum[field_name] = False + break +%> + +## If there are a range of values for a field, we define an enum. +%for field_name in mthd.field_defs: + %if mthd.field_is_rs_enum[field_name]: +#[repr(u16)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ${mthd.field_rs_types[field_name]} { + %for field_name, field_value in mthd.field_defs[field_name].items(): + ${to_camel(rs_field_name(field_name))} = ${field_value.lower()}, + %endfor +} + %endif +%endfor + +## We also define a struct with the fields for the mthd. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct ${to_camel(mthd_name)} { + %for field_name in mthddict[mthd_name].field_name_start: + pub ${rs_field_name(field_name.lower())}: ${mthd.field_rs_types[field_name]}, + %endfor +} + +## Notice that the "to_bits" implementation is identical, so the first brace is +## not closed. +% if not mthd.is_array: +## This trait lays out how the conversion to u32 happens +impl Mthd for ${to_camel(mthd_name)} { + const ADDR: u16 = ${mthd.addr.replace('(', '').replace(')', '')}; + const CLASS: u16 = ${version[1].lower() if version is not None else nvcl.lower().replace("nv", "0x")}; + +%else: +impl ArrayMthd for ${to_camel(mthd_name)} { + const CLASS: u16 = ${version[1].lower() if version is not None else nvcl.lower().replace("nv", "0x")}; + + fn addr(i: usize) -> u16 { + <% assert not ('i' in mthd.addr and 'j' in mthd.addr) %> + (${mthd.addr.replace('j', 'i').replace('(', '').replace(')', '')}).try_into().unwrap() + } +%endif + + #[inline] + fn to_bits(self) -> u32 { + let mut val = 0; + %for field_name in mthddict[mthd_name].field_name_start: + <% + field_start = int(mthd.field_name_start[field_name]) + field_end = int(mthd.field_name_end[field_name]) + field_width = field_end - field_start + 1 + field = rs_field_name(field_name.lower()) if mthd.field_rs_types[field_name] == "u32" else f"{rs_field_name(field_name)} as u32" + %> + %if field_width == 32: + val |= self.${field}; + %else: + %if "as u32" in field: + assert!((self.${field}) < (1 << ${field_width})); + val |= (self.${field}) << ${field_start}; + %else: + assert!(self.${field} < (1 << ${field_width})); + val |= self.${field} << ${field_start}; + %endif + %endif + %endfor + + val + } +## Close the first brace. +} +%endfor +""") + +## A mere convenience to convert snake_case to CamelCase. Numbers are prefixed +## with "_". +def to_camel(snake_str): + result = ''.join(word.title() for word in snake_str.split('_')) + return result if not result[0].isdigit() else '_' + result + +def rs_field_name(name): + name = name.lower() + + # Fix up some Rust keywords + if name == 'type': + return 'type_' + elif name == 'override': + return 'override_' + elif name[0].isdigit(): + return '_' + name + else: + return name + def glob_match(glob, name): if glob.endswith('*'): return name.startswith(glob[:-1]) @@ -338,6 +460,8 @@ def parse_header(nvcl, f): x.field_name_start = {} x.field_name_end = {} x.field_defs = {} + x.field_rs_types = {} + x.field_is_rs_enum = {} mthddict[x.name] = x curmthd = x @@ -345,11 +469,72 @@ def parse_header(nvcl, f): return (version, mthddict) +def convert_to_rust_constants(filename): + with open(filename, 'r') as file: + lines = file.readlines() + + rust_items = [] + processed_constants = {} + file_prefix = "NV" + os.path.splitext(os.path.basename(filename))[0].upper() + "_" + file_prefix = file_prefix.replace('CL', '') + for line in lines: + match = re.match(r'#define\s+(\w+)\((\w+)\)\s+(.+)', line.strip()) + if match: + name, arg, expr = match.groups() + if name in processed_constants: + processed_constants[name] += 1 + name += f"_{processed_constants[name]}" + else: + processed_constants[name] = 0 + name = name.replace(file_prefix, '') + # convert to snake case + name = re.sub(r'(?<=[a-z])(?=[A-Z])', '_', name).lower() + rust_items.append(f"#[inline]\npub fn {name} ({arg}: u32) -> u32 {{ {expr.replace('(', '').replace(')', '')} }} ") + else: + match = re.match(r'#define\s+(\w+)\s+(?:MW\()?(\d+):(\d+)\)?', line.strip()) + if match: + name, high, low = match.groups() + high = int(high) + 1 # Convert to exclusive range + if name in processed_constants: + processed_constants[name] += 1 + name += f"_{processed_constants[name]}" + else: + processed_constants[name] = 0 + # name = name.replace('__', '_').replace(file_prefix, '') + name = name.replace(file_prefix, '') + rust_items.append(f"pub const {name}: Range = {low}..{high};") + else: + match = re.match(r'#define\s+(\w+)\s+\(?0x(\w+)\)?', line.strip()) + if match: + name, value = match.groups() + if name in processed_constants: + processed_constants[name] += 1 + name += f"_{processed_constants[name]}" + else: + processed_constants[name] = 0 + name = name.replace(file_prefix, '') + rust_items.append(f"pub const {name}: u32 = 0x{value};") + else: + match = re.match(r'#define\s+(\w+)\s+\(?(\d+)\)?', line.strip()) + if match: + name, value = match.groups() + if name in processed_constants: + processed_constants[name] += 1 + name += f"_{processed_constants[name]}" + else: + processed_constants[name] = 0 + name = name.replace(file_prefix, '') + rust_items.append(f"pub const {name}: u32 = {value};") + + return '\n'.join(rust_items) + def main(): parser = argparse.ArgumentParser() parser.add_argument('--out-h', required=False, help='Output C header.') parser.add_argument('--out-c', required=False, help='Output C file.') parser.add_argument('--out-rs', required=False, help='Output Rust file.') + parser.add_argument('--out-rs-mthd', required=False, + help='Output Rust file for methods.') parser.add_argument('--in-h', help='Input class header file.', required=True) @@ -370,6 +555,8 @@ def main(): 'nvcl': nvcl, 'version': version, 'mthddict': mthddict, + 'rs_field_name': rs_field_name, + 'to_camel': to_camel, 'bs': '\\' } @@ -384,6 +571,18 @@ def main(): if args.out_rs is not None: with open(args.out_rs, 'w', encoding='utf-8') as f: f.write(TEMPLATE_RS.render(**environment)) + if args.out_rs_mthd is not None: + with open(args.out_rs_mthd, 'w', encoding='utf-8') as f: + f.write("#![allow(non_camel_case_types)]\n") + f.write("#![allow(non_snake_case)]\n") + f.write("#![allow(non_upper_case_globals)]\n\n") + f.write("use std::ops::Range;\n") + f.write("use crate::Mthd;\n") + f.write("use crate::ArrayMthd;\n") + f.write("\n") + f.write(convert_to_rust_constants(args.in_h)) + f.write("\n") + f.write(TEMPLATE_RS_MTHD.render(**environment)) except Exception: # In the event there's an error, this imports some helpers from mako diff --git a/src/nouveau/headers/lib_rs_gen.py b/src/nouveau/headers/lib_rs_gen.py index b3e7de5aec5..d53f5e871fc 100644 --- a/src/nouveau/headers/lib_rs_gen.py +++ b/src/nouveau/headers/lib_rs_gen.py @@ -9,6 +9,7 @@ import argparse import os.path import re +import subprocess import sys from mako.template import Template @@ -48,6 +49,26 @@ ${decl_mod(m.children[name], path + [name])} } % endif + +/// Converts a method to its raw representation. +pub trait Mthd { + /// The hardware address of the method. + const ADDR: u16; + /// The class of the method. + const CLASS: u16; + /// Converts the method to its raw representation. + fn to_bits(self) -> u32; +} + +pub trait ArrayMthd { + /// The class of the method. + const CLASS: u16; + /// The hardware address of the method for the given index. + fn addr(i: usize) -> u16; + /// Converts the method to its raw representation. + fn to_bits(self) -> u32; +} + ${decl_mod(root, [])} """) diff --git a/src/nouveau/headers/meson.build b/src/nouveau/headers/meson.build index cf53dae3dd4..29a5b17520a 100644 --- a/src/nouveau/headers/meson.build +++ b/src/nouveau/headers/meson.build @@ -83,6 +83,14 @@ if with_nouveau_vk '--out-rs', '@OUTPUT0@'], ) + cl_rs_generated += custom_target( + 'nvh_' + cl + '_mthd.rs', + input : ['class_parser.py', 'nvidia/classes/'+cl+'.h'], + output : ['nvh_classes_'+cl+'_mthd.rs'], + command : [prog_python, '@INPUT0@', '--in-h', '@INPUT1@', + '--out-rs-mthd', '@OUTPUT0@'], + ) + fs = import('fs') if cl.endswith('c0') and fs.is_file('nvidia/classes/'+cl+'qmd.h') cl_rs_generated += custom_target(