Files
mesa/src/nouveau/headers/class_parser.py
T
Daniel Almeida 03d79bc09d nouveau/headers: Import the video class headers from NVIDIA
From open-gpu-doc commit c8607fe576b5 ("Add hopper and blackwell
dma-copy class header files").

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
Signed-off-by: Faith Ekstrand <faith.ekstrand@collabora.com>
Reviewed-by: Mel Henning <mhenning@darkrefraction.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36641>
2025-08-07 20:09:10 +00:00

600 lines
18 KiB
Python

#! /usr/bin/env python3
# script to parse nvidia CL headers and generate inlines to be used in pushbuffer encoding.
# probably needs python3.9
import argparse
import os.path
import sys
import re
import subprocess
from mako.template import Template
import util
METHOD_ARRAY_SIZES = {
'BIND_GROUP_CONSTANT_BUFFER' : 16,
'CALL_MME_DATA' : 256,
'CALL_MME_MACRO' : 256,
'LOAD_CONSTANT_BUFFER' : 16,
'LOAD_INLINE_QMD_DATA' : 64,
'SET_ANTI_ALIAS_SAMPLE_POSITIONS' : 4,
'SET_BLEND' : 8,
'SET_BLEND_PER_TARGET_*' : 8,
'SET_COLOR_TARGET_*' : 8,
'SET_COLOR_COMPRESSION' : 8,
'SET_COLOR_CLEAR_VALUE' : 4,
'SET_CT_WRITE' : 8,
'SET_MME_SHADOW_SCRATCH' : 256,
'SET_MULTI_VIEW_RENDER_TARGET_ARRAY_INDEX_OFFSET' : 4,
'SET_PIPELINE_*' : 6,
'SET_ROOT_TABLE_VISIBILITY' : 8,
'SET_SCG_COMPUTE_SCHEDULING_PARAMETERS' : 16,
'SET_SCG_GRAPHICS_SCHEDULING_PARAMETERS' : 16,
'SET_SCISSOR_*' : 16,
'SET_SHADER_PERFORMANCE_SNAPSHOT_COUNTER_VALUE*' : 8,
'SET_SHADER_PERFORMANCE_COUNTER_VALUE*' : 8,
'SET_SHADER_PERFORMANCE_COUNTER_EVENT' : 8,
'SET_SHADER_PERFORMANCE_COUNTER_CONTROL_A' : 8,
'SET_SHADER_PERFORMANCE_COUNTER_CONTROL_B' : 8,
'SET_SHADING_RATE_INDEX_SURFACE_*' : 1,
'SET_SPARE_MULTI_VIEW_RENDER_TARGET_ARRAY_INDEX_OFFSET' : 4,
'SET_STREAM_OUT_BUFFER_*' : 4,
'SET_STREAM_OUT_CONTROL_*' : 4,
'SET_VARIABLE_PIXEL_RATE_SAMPLE_ORDER' : 13,
'SET_VARIABLE_PIXEL_RATE_SHADING_CONTROL*' : 16,
'SET_VARIABLE_PIXEL_RATE_SHADING_INDEX_TO_RATE*' : 16,
'SET_VIEWPORT_*' : 16,
'SET_VERTEX_ATTRIBUTE_*' : 16,
'SET_VERTEX_STREAM_*' : 16,
'SET_WINDOW_CLIP_*' : 8,
'SET_CLIP_ID_EXTENT_*' : 4,
}
METHOD_IS_FLOAT = [
'SET_BLEND_CONST_*',
'SET_DEPTH_BIAS',
'SET_SLOPE_SCALE_DEPTH_BIAS',
'SET_DEPTH_BIAS_CLAMP',
'SET_DEPTH_BOUNDS_M*',
'SET_LINE_WIDTH_FLOAT',
'SET_ALIASED_LINE_WIDTH_FLOAT',
'SET_VIEWPORT_SCALE_*',
'SET_VIEWPORT_OFFSET_*',
'SET_VIEWPORT_CLIP_MIN_Z',
'SET_VIEWPORT_CLIP_MAX_Z',
'SET_Z_CLEAR_VALUE',
]
# generator chokes on these in later files
SKIP_FIELD = [
'CURRENT_SCG_TYPE'
]
TEMPLATE_H = Template("""\
/* parsed class ${nvcl} */
#include "nvtypes.h"
#include "${clheader}"
#include <assert.h>
#include <stdio.h>
#include "util/u_math.h"
%for mthd in methods:
struct nv_${nvcl.lower()}_${mthd.name} {
%for field in mthd.fields:
uint32_t ${field.name.lower()};
%endfor
};
static inline void
__${nvcl}_${mthd.name}(uint32_t *val_out, struct nv_${nvcl.lower()}_${mthd.name} st)
{
uint32_t val = 0;
%for field in mthd.fields:
<% field_width = field.end - field.start + 1 %>
%if field_width == 32:
val |= st.${field.name.lower()};
%else:
assert(st.${field.name.lower()} < (1ULL << ${field_width}));
val |= st.${field.name.lower()} << ${field.start};
%endif
%endfor
*val_out = val;
}
#define V_${nvcl}_${mthd.name}(val, args...) { ${bs}
%for field in mthd.fields:
%for d in field.defs:
UNUSED uint32_t ${field.name}_${d} = ${nvcl}_${mthd.name}_${field.name}_${d}; ${bs}
%endfor
%endfor
%if len(mthd.fields) > 1:
struct nv_${nvcl.lower()}_${mthd.name} __data = args; ${bs}
%else:
<% field_name = mthd.fields[0].name.lower() %>\
struct nv_${nvcl.lower()}_${mthd.name} __data = { .${field_name} = (args) }; ${bs}
%endif
__${nvcl}_${mthd.name}(&val, __data); ${bs}
}
%if mthd.is_array:
#define VA_${nvcl}_${mthd.name}(i) V_${nvcl}_${mthd.name}
%else:
#define VA_${nvcl}_${mthd.name} V_${nvcl}_${mthd.name}
%endif
%if mthd.is_array:
#define P_${nvcl}_${mthd.name}(push, idx, args...) do { ${bs}
%else:
#define P_${nvcl}_${mthd.name}(push, args...) do { ${bs}
%endif
%for field in mthd.fields:
%for d in field.defs:
UNUSED uint32_t ${field.name}_${d} = ${nvcl}_${mthd.name}_${field.name}_${d}; ${bs}
%endfor
%endfor
uint32_t nvk_p_ret; ${bs}
V_${nvcl}_${mthd.name}(nvk_p_ret, args); ${bs}
%if mthd.is_array:
nv_push_val(push, ${nvcl}_${mthd.name}(idx), nvk_p_ret); ${bs}
%else:
nv_push_val(push, ${nvcl}_${mthd.name}, nvk_p_ret); ${bs}
%endif
} while(0)
%endfor
const char *P_PARSE_${nvcl}_MTHD(uint16_t idx);
void P_DUMP_${nvcl}_MTHD_DATA(FILE *fp, uint16_t idx, uint32_t data,
const char *prefix);
""")
TEMPLATE_C = Template("""\
#include "${header}"
#include <stdio.h>
const char*
P_PARSE_${nvcl}_MTHD(uint16_t idx)
{
switch (idx) {
%for mthd in methods:
%if mthd.is_array and mthd.array_size == 0:
<% continue %>
%endif
%if mthd.is_array:
%for i in range(mthd.array_size):
case ${nvcl}_${mthd.name}(${i}):
return "${nvcl}_${mthd.name}(${i})";
%endfor
% else:
case ${nvcl}_${mthd.name}:
return "${nvcl}_${mthd.name}";
%endif
%endfor
default:
return "unknown method";
}
}
void
P_DUMP_${nvcl}_MTHD_DATA(FILE *fp, uint16_t idx, uint32_t data,
const char *prefix)
{
UNUSED uint32_t parsed;
switch (idx) {
%for mthd in methods:
%if mthd.is_array and mthd.array_size == 0:
<% continue %>
%endif
%if mthd.is_array:
%for i in range(mthd.array_size):
case ${nvcl}_${mthd.name}(${i}):
%endfor
% else:
case ${nvcl}_${mthd.name}:
%endif
%for field in mthd.fields:
<% field_width = field.end - field.start + 1 %>
%if field_width == 32:
parsed = data;
%else:
parsed = (data >> ${field.start}) & ((1u << ${field_width}) - 1);
%endif
fprintf(fp, "%s.${field.name} = ", prefix);
%if len(field.defs):
switch (parsed) {
%for d in field.unique_defs:
case ${nvcl}_${mthd.name}_${field.name}_${d}:
fprintf(fp, "${d}${bs}n");
break;
%endfor
default:
fprintf(fp, "0x%x${bs}n", parsed);
break;
}
%else:
%if mthd.is_float:
fprintf(fp, "%ff (0x%x)${bs}n", uif(parsed), parsed);
%else:
fprintf(fp, "(0x%x)${bs}n", parsed);
%endif
%endif
%endfor
break;
%endfor
default:
fprintf(fp, "%s.VALUE = 0x%x${bs}n", prefix, data);
break;
}
}
""")
TEMPLATE_RS = Template("""\
// parsed class ${nvcl}
% if version is not None:
pub const ${version[0]}: u16 = ${version[1]};
% endif
""")
TEMPLATE_RS_MTHD = Template("""\
use crate::Mthd;
use crate::ArrayMthd;
%if prev_mod is not None:
use crate::classes::${prev_mod}::mthd as ${prev_mod};
%endif
// parsed class ${nvcl}
## Write out the methods in Rust
%for mthd in methods:
## If this method is the same as the one in the previous class header, just
## pub use everything instead of re-generating it. This significantly
## reduces the number of unique Rust types and trait implementations.
%if prev_methods.get(mthd.name, None) == mthd:
%for field in mthd.fields:
%if field.is_rs_enum:
pub use ${prev_mod}::${field.rs_type(mthd)};
%endif
%endfor
pub use ${prev_mod}::${to_camel(mthd.name)};
<% continue %>
%endif
## If there are a range of values for a field, we define an enum.
%for field in mthd.fields:
%if field.is_rs_enum:
#[repr(u16)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ${field.rs_type(mthd)} {
%for def_name, def_value in field.unique_defs.items():
${to_camel(def_name)} = ${def_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 in mthd.fields:
pub ${field.rs_name}: ${field.rs_type(mthd)},
%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 in mthd.fields:
<% field_width = field.end - field.start + 1 %>
%if field_width == 32:
%if field.rs_type(mthd) == "u32":
val |= self.${field.rs_name};
%else:
val |= self.${field.rs_name} as u32;
%endif
%else:
%if field.rs_type(mthd) == "u32":
assert!(self.${field.rs_name} < (1 << ${field_width}));
val |= self.${field.rs_name} << ${field.start};
%else:
assert!((self.${field.rs_name} as u32) < (1 << ${field_width}));
val |= (self.${field.rs_name} as u32) << ${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 strip_parens(s):
s = s.strip()
while s.startswith('(') and s.endswith(')'):
s = s[1:-1].strip()
return s
def glob_match(glob, name):
if glob.endswith('*'):
return name.startswith(glob[:-1])
else:
assert '*' not in glob
return name == glob
class Field(object):
def __init__(self, name, start, end):
self.name = name
self.start = int(start)
self.end = int(end)
self.defs = {}
self._values = set()
self.unique_defs = {}
def __eq__(self, other):
if not isinstance(other, Field):
return False
return self.name == other.name and \
self.start == other.start and \
self.end == other.end and \
self.defs == other.defs
def add_def(self, name, value):
assert name not in self.defs
self.defs[name] = value
if value not in self._values:
self._values.add(value)
self.unique_defs[name] = value
@property
def is_bool(self):
if len(self.defs) != 2:
return False
for d in self.defs:
if not d.lower() in ['true', 'false']:
return False
return True
@property
def is_rs_enum(self):
return not self.is_bool and len(self.defs) > 0
def rs_type(self, mthd):
if self.is_bool:
return "bool"
elif self.name == 'V' and len(self.defs) > 0:
return to_camel(mthd.name) + 'V'
elif len(self.defs) > 0:
assert(self.name != "")
return to_camel(mthd.name) + to_camel(self.name)
elif mthd.is_float:
return "f32"
else:
return "u32"
@property
def rs_name(self):
name = self.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 re.sub(r'_+', '_', name)
class Method(object):
def __init__(self, name, addr, is_array=False):
self.name = name
self.addr = addr
self.is_array = is_array
self.fields = []
def __eq__(self, other):
if not isinstance(other, Method):
return False
return self.name == other.name and \
self.addr == other.addr and \
self.is_array == other.is_array and \
self.fields == other.fields
@property
def array_size(self):
for (glob, value) in METHOD_ARRAY_SIZES.items():
if glob_match(glob, self.name):
return value
return 0
@property
def is_float(self):
for glob in METHOD_IS_FLOAT:
if glob_match(glob, self.name):
assert len(self.fields) == 1
return True
return False
def parse_header(nvcl, f):
# Simple state machine
# state 0 looking for a new method define
# state 1 looking for new fields in a method
# state 2 looking for enums for a fields in a method
# blank lines reset the state machine to 0
version = None
state = 0
methods = {}
curmthd = None
for line in f:
if line.strip() == "":
state = 0
if curmthd is not None and len(curmthd.fields) == 0:
del methods[curmthd.name]
curmthd = None
continue
if line.startswith("#define"):
list = [strip_parens(e) for e in line.split()]
if "_cl_" in list[1]:
continue
if (not list[1].startswith(nvcl)) or list[1].endswith('VIDEO_DECODER'):
if len(list) > 2 and list[2].startswith("0x"):
assert version is None
version = (list[1], list[2])
continue
if list[1].endswith("TYPEDEF"):
continue
if state == 2:
teststr = nvcl + "_" + curmthd.name + "_" + curfield.name + "_"
if ":" in list[2]:
state = 1
elif teststr in list[1]:
if not SKIP_FIELD[0] in list[1]:
curfield.add_def(list[1].removeprefix(teststr), list[2])
else:
state = 1
if state == 1:
teststr = nvcl + "_" + curmthd.name + "_"
if teststr in list[1]:
if ("0x" in list[2]):
state = 1
else:
field = list[1].removeprefix(teststr)
bitfield = list[2].split(":")
f = Field(field, bitfield[1], bitfield[0])
curmthd.fields.append(f)
curfield = f
state = 2
else:
if len(curmthd.fields) == 0:
del methods[curmthd.name]
curmthd = None
state = 0
if state == 0:
if curmthd is not None and len(curmthd.fields) == 0:
del methods[curmthd.name]
teststr = nvcl + "_"
is_array = 0
if (':' in list[2]):
continue
name = list[1].removeprefix(teststr)
if name.endswith("(i)"):
is_array = 1
name = name.removesuffix("(i)")
if name.endswith("(j)"):
is_array = 1
name = name.removesuffix("(j)")
curmthd = Method(name, list[2], is_array)
methods[name] = curmthd
state = 1
return (version, methods)
def nvcl_for_filename(name):
name = name.removeprefix("cl")
name = name.removesuffix(".h")
return "NV" + name.upper()
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)
parser.add_argument('--prev-in-h',
help='Previous input class header file.',
required=False)
args = parser.parse_args()
clheader = os.path.basename(args.in_h)
nvcl = nvcl_for_filename(clheader)
with open(args.in_h, 'r', encoding='utf-8') as f:
(version, methods) = parse_header(nvcl, f)
prev_mod = None
prev_methods = {}
if args.prev_in_h is not None:
prev_clheader = os.path.basename(args.prev_in_h)
prev_nvcl = nvcl_for_filename(prev_clheader)
prev_mod = prev_clheader.removesuffix(".h")
with open(args.prev_in_h, 'r', encoding='utf-8') as f:
(prev_version, prev_methods) = parse_header(prev_nvcl, f)
environment = {
'clheader': clheader,
'nvcl': nvcl,
'version': version,
'methods': list(methods.values()),
'prev_mod': prev_mod,
'prev_methods': prev_methods,
'to_camel': to_camel,
'bs': '\\'
}
if args.out_h is not None:
environment['header'] = os.path.basename(args.out_h)
util.write_template(args.out_h, TEMPLATE_H, environment)
if args.out_c is not None:
util.write_template(args.out_c, TEMPLATE_C, environment)
if args.out_rs is not None:
util.write_template_rs(args.out_rs, TEMPLATE_RS, environment)
if args.out_rs_mthd is not None:
util.write_template_rs(args.out_rs_mthd, TEMPLATE_RS_MTHD, environment)
if __name__ == '__main__':
main()