From 1f725a222ad9b4b249f480811ed2fc1e9eabc9f0 Mon Sep 17 00:00:00 2001 From: Mark Janes Date: Fri, 12 Jan 2024 17:31:16 -0800 Subject: [PATCH] intel/dev: implement json serialization for intel_device_info Generate intel_device_serialize.c from a mako template, providing functions to dump and parse intel_device_info. intel_device_info.py declares python objects representing all type declarations associated with intel_device_info. It is used as a data source for intel_device_serialize_c.py intel_device_serialize_c.py emits a c++ file with routines to dump or load all struct members to/from json. The json format is a direct translation of the c structure, with 2 exceptions: - When parsing json, the no_hw member is always set to true to indicate that the driver's intel_device_info does not correspond to the current platform. - When dumping to json, devinfo_type_sha1 is calculated to be a checksum which changes whenever intel_device_info is updated. This checksum is encoded in json. When parsing json, the driver verifies that the checksum matches before loading data into intel_device_info. This verifies compatibility. Reviewed-by: Dylan Baker Reviewed-by: Lionel Landwerlin Part-of: --- src/intel/dev/intel_device_info_serialize.h | 43 ++++ .../dev/intel_device_info_serialize_gen_c.py | 219 ++++++++++++++++++ src/intel/dev/meson.build | 6 + 3 files changed, 268 insertions(+) create mode 100644 src/intel/dev/intel_device_info_serialize.h create mode 100755 src/intel/dev/intel_device_info_serialize_gen_c.py diff --git a/src/intel/dev/intel_device_info_serialize.h b/src/intel/dev/intel_device_info_serialize.h new file mode 100644 index 00000000000..b49850a6028 --- /dev/null +++ b/src/intel/dev/intel_device_info_serialize.h @@ -0,0 +1,43 @@ + /* + * Copyright © 2024 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef INTEL_DEVICE_INFO_SERIALIZE_H +#define INTEL_DEVICE_INFO_SERIALIZE_H + +#include "dev/intel_device_info.h" +#include "util/parson.h" + +/* Initialize a device info struct with the json-formatted data at the path. + * Returns false if json format is incompatible (from an older version of + * mesa) + */ +bool intel_device_info_from_json(const char *path, + struct intel_device_info *devinfo); + +/* Dump all device info data into a parson tree, for serializaiton. Caller + * must free the returned reference with json_value_free() + */ +JSON_Value *intel_device_info_dump_json(const struct intel_device_info *devinfo); + +#endif /* INTEL_DEVICE_INFO_SERIALIZE_H */ diff --git a/src/intel/dev/intel_device_info_serialize_gen_c.py b/src/intel/dev/intel_device_info_serialize_gen_c.py new file mode 100755 index 00000000000..adf8ce1eb60 --- /dev/null +++ b/src/intel/dev/intel_device_info_serialize_gen_c.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +COPYRIGHT = """\ +/* + * Copyright 2024 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +""" + +import hashlib +import os +import sys + +from mako.template import Template +from mako import exceptions + +from intel_device_info import TYPES, TYPES_BY_NAME, FUNDAMENTAL_TYPES, Enum + +template = COPYRIGHT + """ + +/* DO NOT EDIT - This file generated automatically by intel_device_serialize_c.py script */ + +#include "dev/intel_device_info_serialize.h" +#include "util/parson.h" +<%! import intel_device_info %> +% for decl in TYPES: +% if isinstance(decl, intel_device_info.Enum): +static JSON_Value * +dump_${decl.name}(enum ${decl.name} arg) { + switch (arg) { +% for value in decl.values: + case ${value}: + return json_value_init_string("${value}"); +% endfor + default: + unreachable("invalid ${decl.name} value"); + } +} + +static enum ${decl.name} +load_${decl.name}(const char * val) { +% for value in decl.values: + if (strcmp("${value}", val) == 0) + return ${value}; +% endfor + assert(false); + return (enum ${decl.name}) -1; +} + +% endif +% if isinstance(decl, intel_device_info.Struct): +static JSON_Value * +dump_${decl.name}(const struct ${decl.name} *arg) { + JSON_Value *val = json_value_init_object(); + JSON_Object *json = json_object(val); +% for member in decl.members: +% if member.member_type in intel_device_info.INT_TYPES: +% if member.array: + { + JSON_Value *jtmp = json_value_init_array(); + JSON_Array *jarray = json_array(jtmp); + for (unsigned i = 0; i < sizeof(arg->${member.name}) / sizeof(*arg->${member.name}); ++i) + json_array_append_number(jarray, arg->${member.name}[i]); + json_object_set_value(json, "${member.name}", jtmp); + } +% else: + json_object_set_number(json, "${member.name}", arg->${member.name}); +% endif +% elif member.member_type == "char": + json_object_set_string(json, "${member.name}", arg->${member.name}); +% elif member.member_type == "bool": + json_object_set_boolean(json, "${member.name}", arg->${member.name}); +% elif isinstance(intel_device_info.TYPES_BY_NAME[member.member_type], intel_device_info.Enum): + json_object_set_value(json, "${member.name}", dump_${member.member_type}(arg->${member.name})); +% elif isinstance(intel_device_info.TYPES_BY_NAME[member.member_type], intel_device_info.Struct): +% if member.array: + { + JSON_Value *jtmp = json_value_init_array(); + JSON_Array *jarray = json_array(jtmp); + for (unsigned i = 0; i < sizeof(arg->${member.name}) / sizeof(*arg->${member.name}); ++i) + json_array_append_value(jarray, dump_${member.member_type}(&arg->${member.name}[i])); + json_object_set_value(json, "${member.name}", jtmp); + } +% else: + json_object_set_value(json, "${member.name}", dump_${member.member_type}(&arg->${member.name})); +% endif +% endif +% endfor + return val; +} + +static void +load_${decl.name}(JSON_Object *json, struct ${decl.name} *target) { +% for member in decl.members: +% if member.member_type in intel_device_info.INT_TYPES: +% if member.array: + { + JSON_Array *array = json_object_get_array(json, "${member.name}"); + for (unsigned i = 0; i < json_array_get_count(array); ++i) + target->${member.name}[i] = json_array_get_number(array, i); + } +% else: + target->${member.name} = json_object_get_number(json, "${member.name}"); +% endif +% elif member.member_type == "char": + strncpy(target->${member.name}, + json_object_get_string(json, "${member.name}"), + sizeof(target->${member.name}) - 1); +% elif member.member_type == "bool": + target->${member.name} = json_object_get_boolean(json, "${member.name}"); +% elif isinstance(intel_device_info.TYPES_BY_NAME[member.member_type], intel_device_info.Enum): + target->${member.name} = load_${member.member_type}(json_object_get_string(json, "${member.name}")); +% elif isinstance(intel_device_info.TYPES_BY_NAME[member.member_type], intel_device_info.Struct): +% if member.array: + { + JSON_Array *array = json_object_get_array(json, "${member.name}"); + for (unsigned i = 0; i < json_array_get_count(array); ++i) + load_${member.member_type}(json_array_get_object(array, i), target->${member.name} + i); + } +% else: + load_${member.member_type}(json_object_get_object(json, "${member.name}"), &target->${member.name}); +% endif +% endif +% endfor +} + +% endif +% endfor +bool +intel_device_info_from_json(const char *path, + struct intel_device_info *devinfo) { + JSON_Value *root = json_parse_file(path); + JSON_Object *obj = json_object(root); + const char *devinfo_type_sha1 = "${checksum}"; + + /* verify that json was generated with a compatible driver */ + if (strncmp(devinfo_type_sha1, + json_object_get_string(obj, "devinfo_type_sha1"), + strlen(devinfo_type_sha1))) + return false; + load_intel_device_info(obj, devinfo); + + /* override no_hw to indicate that the GPU has been stubbed */ + devinfo->no_hw = true; + + json_value_free(root); + intel_device_info_init_was(devinfo); + return true; +} + +JSON_Value * +intel_device_info_dump_json(const struct intel_device_info *devinfo) { + JSON_Value * root = dump_intel_device_info(devinfo); + + /* record a hash of the structure type declaration, to prevent loading with + * an incompatible driver + */ + JSON_Object *obj = json_object(root); + json_object_set_string(obj, "devinfo_type_sha1", "${checksum}"); + + return root; +} + +""" + +def declaration_checksum(typename): + """Calculate a stable checksum for the struct that will change whenever any + of the members change. We will refer to this value as the 'type declaration hash'""" + h = hashlib.new('sha1') + struct_description = TYPES_BY_NAME[typename] + for member in struct_description.members: + h.update(member.name.encode(encoding="utf-8")) + h.update(f"{member.array}".encode(encoding="utf-8")) + member_type = member.member_type + if member_type in FUNDAMENTAL_TYPES: + h.update(member_type.encode(encoding="utf-8")) + elif isinstance(TYPES_BY_NAME[member_type], Enum): + h.update(member_type.encode(encoding="utf-8")) + else: + # encode the type declaration hash for the nested struct + h.update(declaration_checksum(member_type).encode(encoding="utf-8")) + return h.hexdigest() + +def main(): + """print intel_device_serialize.c at the specified path""" + if len(sys.argv) > 1: + outf = open(sys.argv[1], 'w', encoding='utf-8') + else: + outf = sys.stdout + try: + outf.write(Template(template).render(TYPES=TYPES, + checksum=declaration_checksum("intel_device_info"))) + except: + print(exceptions.text_error_template().render( + TYPES=TYPES, + checksum=declaration_checksum("intel_device_info"))) + sys.exit(1) + outf.close() + +if __name__ == "__main__": + main() diff --git a/src/intel/dev/meson.build b/src/intel/dev/meson.build index fb7f2ed6638..ba39f382725 100644 --- a/src/intel/dev/meson.build +++ b/src/intel/dev/meson.build @@ -45,6 +45,12 @@ intel_dev_info_gen_src = custom_target('intel_device_info_gen.h', output : ['intel_device_info_gen.h'], command : [prog_python, '@INPUT0@', '--outdir', meson.current_build_dir()]) +intel_dev_serialize_src = custom_target('intel_device_info_serialize_gen.c', + input : 'intel_device_info_serialize_gen_c.py', + depend_files: 'intel_device_info.py', + output : ['intel_device_info_serialize_gen.c'], + command : [prog_python, '@INPUT@', '@OUTPUT@']) + # ensures intel_wa.h exists before implementation files are compiled idep_intel_dev_wa = declare_dependency(sources : [intel_dev_wa_src[0]])