vc4: Introduce XML-based packet header generation like Intel's.
[mesa.git] / src / broadcom / cle / gen_pack_header.py
diff --git a/src/broadcom/cle/gen_pack_header.py b/src/broadcom/cle/gen_pack_header.py
new file mode 100644 (file)
index 0000000..d9ead68
--- /dev/null
@@ -0,0 +1,546 @@
+#encoding=utf-8
+
+# Copyright (C) 2016 Intel Corporation
+# Copyright (C) 2016 Broadcom
+#
+# 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.
+
+from __future__ import (
+    absolute_import, division, print_function, unicode_literals
+)
+import xml.parsers.expat
+import re
+import sys
+import copy
+
+license =  """/* Generated code, see packets.xml and gen_packet_header.py */
+"""
+
+pack_header = """%(license)s
+
+/* Packets, enums and structures for %(platform)s.
+ *
+ * This file has been generated, do not hand edit.
+ */
+
+#ifndef %(guard)s
+#define %(guard)s
+
+#include "v3d_packet_helpers.h"
+
+"""
+
+def to_alphanum(name):
+    substitutions = {
+        ' ': '_',
+        '/': '_',
+        '[': '',
+        ']': '',
+        '(': '',
+        ')': '',
+        '-': '_',
+        ':': '',
+        '.': '',
+        ',': '',
+        '=': '',
+        '>': '',
+        '#': '',
+        'α': 'alpha',
+        '&': '',
+        '*': '',
+        '"': '',
+        '+': '',
+        '\'': '',
+    }
+
+    for i, j in substitutions.items():
+        name = name.replace(i, j)
+
+    return name
+
+def safe_name(name):
+    name = to_alphanum(name)
+    if not name[0].isalpha():
+        name = '_' + name
+
+    return name
+
+def num_from_str(num_str):
+    if num_str.lower().startswith('0x'):
+        return int(num_str, base=16)
+    else:
+        assert(not num_str.startswith('0') and 'octals numbers not allowed')
+        return int(num_str)
+
+class Field(object):
+    ufixed_pattern = re.compile(r"u(\d+)\.(\d+)")
+    sfixed_pattern = re.compile(r"s(\d+)\.(\d+)")
+
+    def __init__(self, parser, attrs):
+        self.parser = parser
+        if "name" in attrs:
+            self.name = safe_name(attrs["name"]).lower()
+
+        if str(attrs["start"]).endswith("b"):
+            self.start = int(attrs["start"][:-1]) * 8
+        else:
+            self.start = int(attrs["start"])
+        # packet <field> entries in XML start from the bit after the
+        # opcode, so shift everything up by 8 since we'll also have a
+        # Field for the opcode.
+        if not parser.struct:
+            self.start += 8
+
+        self.end = self.start + int(attrs["size"]) - 1
+        self.type = attrs["type"]
+
+        if "prefix" in attrs:
+            self.prefix = safe_name(attrs["prefix"]).upper()
+        else:
+            self.prefix = None
+
+        if "default" in attrs:
+            self.default = int(attrs["default"])
+        else:
+            self.default = None
+
+        ufixed_match = Field.ufixed_pattern.match(self.type)
+        if ufixed_match:
+            self.type = 'ufixed'
+            self.fractional_size = int(ufixed_match.group(2))
+
+        sfixed_match = Field.sfixed_pattern.match(self.type)
+        if sfixed_match:
+            self.type = 'sfixed'
+            self.fractional_size = int(sfixed_match.group(2))
+
+    def emit_template_struct(self, dim):
+        if self.type == 'address':
+            type = '__gen_address_type'
+        elif self.type == 'bool':
+            type = 'bool'
+        elif self.type == 'float':
+            type = 'float'
+        elif self.type == 'ufixed':
+            type = 'float'
+        elif self.type == 'sfixed':
+            type = 'float'
+        elif self.type == 'uint' and self.end - self.start > 32:
+            type = 'uint64_t'
+        elif self.type == 'offset':
+            type = 'uint64_t'
+        elif self.type == 'int':
+            type = 'int32_t'
+        elif self.type == 'uint':
+            type = 'uint32_t'
+        elif self.type in self.parser.structs:
+            type = 'struct ' + self.parser.gen_prefix(safe_name(self.type))
+        elif self.type == 'mbo':
+            return
+        else:
+            print("#error unhandled type: %s" % self.type)
+
+        print("   %-36s %s%s;" % (type, self.name, dim))
+
+        if len(self.values) > 0 and self.default == None:
+            if self.prefix:
+                prefix = self.prefix + "_"
+            else:
+                prefix = ""
+
+        for value in self.values:
+            print("#define %-40s %d" % ((prefix + value.name).replace("__", "_"),
+                                        value.value))
+
+    def overlaps(self, field):
+        return self != field and max(self.start, field.start) <= min(self.end, field.end)
+
+
+class Group(object):
+    def __init__(self, parser, parent, start, count):
+        self.parser = parser
+        self.parent = parent
+        self.start = start
+        self.count = count
+        self.size = 0
+        self.fields = []
+
+    def emit_template_struct(self, dim):
+        if self.count == 0:
+            print("   /* variable length fields follow */")
+        else:
+            if self.count > 1:
+                dim = "%s[%d]" % (dim, self.count)
+
+            for field in self.fields:
+                field.emit_template_struct(dim)
+
+    class Byte:
+        def __init__(self):
+            self.size = 8
+            self.fields = []
+            self.address = None
+
+    def collect_bytes(self, bytes):
+        for field in self.fields:
+            first_byte = field.start // 8
+            last_byte = field.end // 8
+
+            for b in xrange(first_byte, last_byte + 1):
+                if not b in bytes:
+                    bytes[b] = self.Byte()
+
+                bytes[b].fields.append(field)
+
+                if field.type == "address":
+                    # assert bytes[index].address == None
+                    bytes[b].address = field
+
+    def emit_pack_function(self, start):
+        # Determine number of bytes in this group.
+        self.length = max(field.end // 8 for field in self.fields) + 1
+
+        bytes = {}
+        self.collect_bytes(bytes)
+
+        relocs_emitted = set()
+        memcpy_fields = set()
+
+        for index in range(self.length):
+            # Handle MBZ bytes
+            if not index in bytes:
+                print("   cl[%2d] = 0;" % index)
+                continue
+            byte = bytes[index]
+
+            # Call out to the driver to note our relocations.  Inside of the
+            # packet we only store offsets within the BOs, and we store the
+            # handle to the packet outside.  Unlike Intel genxml, we don't
+            # need to have the other bits that will be stored together with
+            # the address during the reloc process, so there's no need for the
+            # complicated combine_address() function.
+            if byte.address and byte.address not in relocs_emitted:
+                print("   __gen_emit_reloc(data, &values->%s);" % byte.address.name)
+                relocs_emitted.add(byte.address)
+
+            # Special case: floats can't have any other fields packed into
+            # them (since they'd change the meaning of the float), and the
+            # per-byte bitshifting math below bloats the pack code for floats,
+            # so just copy them directly here.  Also handle 16/32-bit
+            # uints/ints with no merged fields.
+            if len(byte.fields) == 1:
+                field = byte.fields[0]
+                if field.type in ["float", "uint", "int"] and field.start % 8 == 0 and field.end - field.start == 31:
+                    if field in memcpy_fields:
+                        continue
+
+                    if not any(field.overlaps(scan_field) for scan_field in self.fields):
+                        assert(field.start == index * 8)
+                        print("")
+                        print("   memcpy(&cl[%d], &values->%s, sizeof(values->%s));" %
+                                (index, field.name, field.name))
+                        memcpy_fields.add(field)
+                        continue
+
+            byte_start = index * 8
+
+            v = None
+            prefix = "   cl[%2d] =" % index
+
+            field_index = 0
+            for field in byte.fields:
+                if field.type != "mbo":
+                    name = field.name
+
+                start = field.start
+                end = field.end
+                field_byte_start = (field.start // 8) * 8
+                start -= field_byte_start
+                end -= field_byte_start
+
+                if field.type == "mbo":
+                    s = "__gen_mbo(%d, %d)" % \
+                        (start, end)
+                elif field.type == "address":
+                    s = "__gen_address_offset(&values->%s)" % byte.address.name
+                elif field.type == "uint":
+                    s = "__gen_uint(values->%s, %d, %d)" % \
+                        (name, start, end)
+                elif field.type == "int":
+                    s = "__gen_sint(values->%s, %d, %d)" % \
+                        (name, start, end)
+                elif field.type == "bool":
+                    s = "__gen_uint(values->%s, %d, %d)" % \
+                        (name, start, end)
+                elif field.type == "float":
+                    s = "#error %s float value mixed in with other fields" % name
+                elif field.type == "offset":
+                    s = "__gen_offset(values->%s, %d, %d)" % \
+                        (name, start, end)
+                elif field.type == 'ufixed':
+                    s = "__gen_ufixed(values->%s, %d, %d, %d)" % \
+                        (name, start, end, field.fractional_size)
+                elif field.type == 'sfixed':
+                    s = "__gen_sfixed(values->%s, %d, %d, %d)" % \
+                        (name, start, end, field.fractional_size)
+                elif field.type in self.parser.structs:
+                    s = "__gen_uint(v%d_%d, %d, %d)" % \
+                        (index, field_index, start, end)
+                    field_index = field_index + 1
+                else:
+                    print("/* unhandled field %s, type %s */\n" % (name, field.type))
+                    s = None
+
+                if not s == None:
+                    if byte_start - field_byte_start != 0:
+                        s = "%s >> %d" % (s, byte_start - field_byte_start)
+
+                    if field == byte.fields[-1]:
+                        print("%s %s;" % (prefix, s))
+                    else:
+                        print("%s %s |" % (prefix, s))
+                    prefix = "           "
+
+            print("")
+            continue
+
+    def emit_unpack_function(self, start):
+        for field in self.fields:
+            if field.type != "mbo":
+                convert = None
+
+                if field.type == "address":
+                    convert = "__gen_unpack_address"
+                elif field.type == "uint":
+                    convert = "__gen_unpack_uint"
+                elif field.type == "int":
+                    convert = "__gen_unpack_sint"
+                elif field.type == "bool":
+                    convert = "__gen_unpack_uint"
+                elif field.type == "float":
+                    convert = "__gen_unpack_float"
+                elif field.type == "offset":
+                    convert = "__gen_unpack_offset"
+                elif field.type == 'ufixed':
+                    convert = "__gen_unpack_ufixed"
+                elif field.type == 'sfixed':
+                    convert = "__gen_unpack_sfixed"
+                else:
+                    print("/* unhandled field %s, type %s */\n" % (name, field.type))
+                    s = None
+
+                print("   values->%s = %s(cl, %s, %s);" % \
+                      (field.name, convert, \
+                       start + field.start, start + field.end))
+
+
+class Value(object):
+    def __init__(self, attrs):
+        self.name = safe_name(attrs["name"]).upper()
+        self.value = int(attrs["value"])
+
+class Parser(object):
+    def __init__(self):
+        self.parser = xml.parsers.expat.ParserCreate()
+        self.parser.StartElementHandler = self.start_element
+        self.parser.EndElementHandler = self.end_element
+
+        self.packet = None
+        self.struct = None
+        self.structs = {}
+        self.registers = {}
+
+    def gen_prefix(self, name):
+        if name[0] == "_":
+            return 'V3D%s%s' % (self.ver, name)
+        else:
+            return 'V3D%s_%s' % (self.ver, name)
+
+    def gen_guard(self):
+        return self.gen_prefix("PACK_H")
+
+    def start_element(self, name, attrs):
+        if name == "vcxml":
+            self.platform = "V3D {}".format(attrs["ver"])
+            self.ver = attrs["ver"].replace('.', '')
+            print(pack_header % {'license': license, 'platform': self.platform, 'guard': self.gen_guard()})
+        elif name in ("packet", "struct", "register"):
+            default_field = None
+
+            object_name = self.gen_prefix(safe_name(attrs["name"].upper()))
+            if name == "packet":
+                self.packet = object_name
+
+                # Add a fixed Field for the opcode.  We only make <field>s in
+                # the XML for the fields listed in the spec, and all of those
+                # start from bit 0 after of the opcode.
+                default_field = {
+                    "name" : "opcode",
+                    "default" : attrs["code"],
+                    "type" : "uint",
+                    "start" : -8,
+                    "size" : 8,
+                }
+            elif name == "struct":
+                self.struct = object_name
+                self.structs[attrs["name"]] = 1
+            elif name == "register":
+                self.register = object_name
+                self.reg_num = num_from_str(attrs["num"])
+                self.registers[attrs["name"]] = 1
+
+            self.group = Group(self, None, 0, 1)
+            if default_field:
+                field = Field(self, default_field)
+                field.values = []
+                self.group.fields.append(field)
+
+        elif name == "field":
+            self.group.fields.append(Field(self, attrs))
+            self.values = []
+        elif name == "enum":
+            self.values = []
+            self.enum = safe_name(attrs["name"])
+            if "prefix" in attrs:
+                self.prefix = safe_name(attrs["prefix"])
+            else:
+                self.prefix= None
+        elif name == "value":
+            self.values.append(Value(attrs))
+
+    def end_element(self, name):
+        if name  == "packet":
+            self.emit_packet()
+            self.packet = None
+            self.group = None
+        elif name == "struct":
+            self.emit_struct()
+            self.struct = None
+            self.group = None
+        elif name == "register":
+            self.emit_register()
+            self.register = None
+            self.reg_num = None
+            self.group = None
+        elif name  == "field":
+            self.group.fields[-1].values = self.values
+        elif name  == "enum":
+            self.emit_enum()
+            self.enum = None
+        elif name == "vcxml":
+            print('#endif /* %s */' % self.gen_guard())
+
+    def emit_template_struct(self, name, group):
+        print("struct %s {" % name)
+        group.emit_template_struct("")
+        print("};\n")
+
+    def emit_pack_function(self, name, group):
+        print("static inline void\n%s_pack(__gen_user_data *data, uint8_t * restrict cl,\n%sconst struct %s * restrict values)\n{" %
+              (name, ' ' * (len(name) + 6), name))
+
+        group.emit_pack_function(0)
+
+        print("}\n")
+
+        print('#define %-33s %6d' %
+              (name + "_length", self.group.length))
+
+    def emit_unpack_function(self, name, group):
+        print("#ifdef __gen_unpack_address")
+        print("static inline void")
+        print("%s_unpack(const uint8_t * restrict cl,\n%sstruct %s * restrict values)\n{" %
+              (name, ' ' * (len(name) + 8), name))
+
+        group.emit_unpack_function(0)
+
+        print("}\n#endif\n")
+
+    def emit_packet(self):
+        name = self.packet
+
+        assert(self.group.fields[0].name == "opcode")
+        print('#define %-33s %6d' %
+              (name + "_opcode", self.group.fields[0].default))
+
+        default_fields = []
+        for field in self.group.fields:
+            if not type(field) is Field:
+                continue
+            if field.default == None:
+                continue
+            default_fields.append("   .%-35s = %6d" % (field.name, field.default))
+
+        if default_fields:
+            print('#define %-40s\\' % (name + '_header'))
+            print(",  \\\n".join(default_fields))
+            print('')
+
+        self.emit_template_struct(self.packet, self.group)
+        self.emit_pack_function(self.packet, self.group)
+        self.emit_unpack_function(self.packet, self.group)
+
+        print('')
+
+    def emit_register(self):
+        name = self.register
+        if not self.reg_num == None:
+            print('#define %-33s 0x%04x' %
+                  (self.gen_prefix(name + "_num"), self.reg_num))
+
+        self.emit_template_struct(self.register, self.group)
+        self.emit_pack_function(self.register, self.group)
+        self.emit_unpack_function(self.register, self.group)
+
+    def emit_struct(self):
+        name = self.struct
+        # Emit an empty header define so that we can use the CL pack functions
+        # with structs.
+        print('#define ' + name + '_header')
+
+        self.emit_template_struct(self.struct, self.group)
+        self.emit_pack_function(self.struct, self.group)
+        self.emit_unpack_function(self.struct, self.group)
+
+        print('')
+
+    def emit_enum(self):
+        print('/* enum %s */' % self.gen_prefix(self.enum))
+        for value in self.values:
+            if self.prefix:
+                name = self.prefix + "_" + value.name
+            else:
+                name = value.name
+                print('#define %-36s %6d' % (name.upper(), value.value))
+        print('')
+
+    def parse(self, filename):
+        file = open(filename, "rb")
+        self.parser.ParseFile(file)
+        file.close()
+
+if len(sys.argv) < 2:
+    print("No input xml file specified")
+    sys.exit(1)
+
+input_file = sys.argv[1]
+
+p = Parser()
+p.parse(input_file)