xref: /rk3399_ARM-atf/lib/romlib/romlib_generator.py (revision d8210dc67af5b8c5f17a799bc2af5186dd30ba21)
1*d8210dc6SImre Kis#!/usr/bin/env python3
2*d8210dc6SImre Kis# Copyright (c) 2019, Arm Limited. All rights reserved.
3*d8210dc6SImre Kis#
4*d8210dc6SImre Kis# SPDX-License-Identifier: BSD-3-Clause
5*d8210dc6SImre Kis
6*d8210dc6SImre Kis"""
7*d8210dc6SImre KisThis module contains a set of classes and a runner that can generate code for the romlib module
8*d8210dc6SImre Kisbased on the templates in the 'templates' directory.
9*d8210dc6SImre Kis"""
10*d8210dc6SImre Kis
11*d8210dc6SImre Kisimport argparse
12*d8210dc6SImre Kisimport os
13*d8210dc6SImre Kisimport re
14*d8210dc6SImre Kisimport subprocess
15*d8210dc6SImre Kisimport string
16*d8210dc6SImre Kisimport sys
17*d8210dc6SImre Kis
18*d8210dc6SImre Kisclass IndexFileParser:
19*d8210dc6SImre Kis    """
20*d8210dc6SImre Kis    Parses the contents of the index file into the items and dependencies variables. It
21*d8210dc6SImre Kis    also resolves included files in the index files recursively with circular inclusion detection.
22*d8210dc6SImre Kis    """
23*d8210dc6SImre Kis
24*d8210dc6SImre Kis    def __init__(self):
25*d8210dc6SImre Kis        self.items = []
26*d8210dc6SImre Kis        self.dependencies = {}
27*d8210dc6SImre Kis        self.include_chain = []
28*d8210dc6SImre Kis
29*d8210dc6SImre Kis    def add_dependency(self, parent, dependency):
30*d8210dc6SImre Kis        """ Adds a dependency into the dependencies variable. """
31*d8210dc6SImre Kis        if parent in self.dependencies:
32*d8210dc6SImre Kis            self.dependencies[parent].append(dependency)
33*d8210dc6SImre Kis        else:
34*d8210dc6SImre Kis            self.dependencies[parent] = [dependency]
35*d8210dc6SImre Kis
36*d8210dc6SImre Kis    def get_dependencies(self, parent):
37*d8210dc6SImre Kis        """ Gets all the recursive dependencies of a parent file. """
38*d8210dc6SImre Kis        parent = os.path.normpath(parent)
39*d8210dc6SImre Kis        if parent in self.dependencies:
40*d8210dc6SImre Kis            direct_deps = self.dependencies[parent]
41*d8210dc6SImre Kis            deps = direct_deps
42*d8210dc6SImre Kis            for direct_dep in direct_deps:
43*d8210dc6SImre Kis                deps += self.get_dependencies(direct_dep)
44*d8210dc6SImre Kis            return deps
45*d8210dc6SImre Kis
46*d8210dc6SImre Kis        return []
47*d8210dc6SImre Kis
48*d8210dc6SImre Kis    def parse(self, file_name):
49*d8210dc6SImre Kis        """ Opens and parses index file. """
50*d8210dc6SImre Kis        file_name = os.path.normpath(file_name)
51*d8210dc6SImre Kis
52*d8210dc6SImre Kis        if file_name not in self.include_chain:
53*d8210dc6SImre Kis            self.include_chain.append(file_name)
54*d8210dc6SImre Kis            self.dependencies[file_name] = []
55*d8210dc6SImre Kis        else:
56*d8210dc6SImre Kis            raise Exception("Circular dependency detected: " + file_name)
57*d8210dc6SImre Kis
58*d8210dc6SImre Kis        with open(file_name, "r") as index_file:
59*d8210dc6SImre Kis            for line in index_file.readlines():
60*d8210dc6SImre Kis                line_elements = line.split()
61*d8210dc6SImre Kis
62*d8210dc6SImre Kis                if line.startswith("#") or not line_elements:
63*d8210dc6SImre Kis                    # Comment or empty line
64*d8210dc6SImre Kis                    continue
65*d8210dc6SImre Kis
66*d8210dc6SImre Kis                if line_elements[0] == "reserved":
67*d8210dc6SImre Kis                    # Reserved slot in the jump table
68*d8210dc6SImre Kis                    self.items.append({"type": "reserved"})
69*d8210dc6SImre Kis                elif line_elements[0] == "include" and len(line_elements) > 1:
70*d8210dc6SImre Kis                    # Include other index file
71*d8210dc6SImre Kis                    included_file = os.path.normpath(line_elements[1])
72*d8210dc6SImre Kis                    self.add_dependency(file_name, included_file)
73*d8210dc6SImre Kis                    self.parse(included_file)
74*d8210dc6SImre Kis                elif len(line_elements) > 1:
75*d8210dc6SImre Kis                    # Library function
76*d8210dc6SImre Kis                    library_name = line_elements[0]
77*d8210dc6SImre Kis                    function_name = line_elements[1]
78*d8210dc6SImre Kis                    patch = bool(len(line_elements) > 2 and line_elements[2] == "patch")
79*d8210dc6SImre Kis
80*d8210dc6SImre Kis                    self.items.append({"type": "function", "library_name": library_name,
81*d8210dc6SImre Kis                                       "function_name": function_name, "patch": patch})
82*d8210dc6SImre Kis                else:
83*d8210dc6SImre Kis                    raise Exception("Invalid line: '" + line + "'")
84*d8210dc6SImre Kis
85*d8210dc6SImre Kis        self.include_chain.pop()
86*d8210dc6SImre Kis
87*d8210dc6SImre Kisclass RomlibApplication:
88*d8210dc6SImre Kis    """ Base class of romlib applications. """
89*d8210dc6SImre Kis    TEMPLATE_DIR = os.path.dirname(os.path.realpath(__file__)) + "/templates/"
90*d8210dc6SImre Kis
91*d8210dc6SImre Kis    def __init__(self, prog):
92*d8210dc6SImre Kis        self.args = argparse.ArgumentParser(prog=prog, description=self.__doc__)
93*d8210dc6SImre Kis        self.config = None
94*d8210dc6SImre Kis
95*d8210dc6SImre Kis    def parse_arguments(self, argv):
96*d8210dc6SImre Kis        """ Parses the arguments that should come from the command line arguments. """
97*d8210dc6SImre Kis        self.config = self.args.parse_args(argv)
98*d8210dc6SImre Kis
99*d8210dc6SImre Kis    def build_template(self, name, mapping=None, remove_comment=False):
100*d8210dc6SImre Kis        """
101*d8210dc6SImre Kis        Loads a template and builds it with the defined mapping. Template paths are always relative
102*d8210dc6SImre Kis        to this script.
103*d8210dc6SImre Kis        """
104*d8210dc6SImre Kis
105*d8210dc6SImre Kis        with open(self.TEMPLATE_DIR + name, "r") as template_file:
106*d8210dc6SImre Kis            if remove_comment:
107*d8210dc6SImre Kis                # Removing copyright comment to make the generated code more readable when the
108*d8210dc6SImre Kis                # template is inserted multiple times into the output.
109*d8210dc6SImre Kis                template_lines = template_file.readlines()
110*d8210dc6SImre Kis                end_of_comment_line = 0
111*d8210dc6SImre Kis                for index, line in enumerate(template_lines):
112*d8210dc6SImre Kis                    if line.find("*/") != -1:
113*d8210dc6SImre Kis                        end_of_comment_line = index
114*d8210dc6SImre Kis                        break
115*d8210dc6SImre Kis                template_data = "".join(template_lines[end_of_comment_line + 1:])
116*d8210dc6SImre Kis            else:
117*d8210dc6SImre Kis                template_data = template_file.read()
118*d8210dc6SImre Kis
119*d8210dc6SImre Kis            template = string.Template(template_data)
120*d8210dc6SImre Kis            return template.substitute(mapping)
121*d8210dc6SImre Kis
122*d8210dc6SImre Kisclass IndexPreprocessor(RomlibApplication):
123*d8210dc6SImre Kis    """ Removes empty and comment lines from the index file and resolves includes. """
124*d8210dc6SImre Kis
125*d8210dc6SImre Kis    def __init__(self, prog):
126*d8210dc6SImre Kis        RomlibApplication.__init__(self, prog)
127*d8210dc6SImre Kis
128*d8210dc6SImre Kis        self.args.add_argument("-o", "--output", help="Output file", metavar="output",
129*d8210dc6SImre Kis                               default="jmpvar.s")
130*d8210dc6SImre Kis        self.args.add_argument("--deps", help="Dependency file")
131*d8210dc6SImre Kis        self.args.add_argument("file", help="Input file")
132*d8210dc6SImre Kis
133*d8210dc6SImre Kis    def main(self):
134*d8210dc6SImre Kis        """
135*d8210dc6SImre Kis        After parsing the input index file it generates a clean output with all includes resolved.
136*d8210dc6SImre Kis        Using --deps option it also outputs the dependencies in makefile format like gcc's with -M.
137*d8210dc6SImre Kis        """
138*d8210dc6SImre Kis
139*d8210dc6SImre Kis        index_file_parser = IndexFileParser()
140*d8210dc6SImre Kis        index_file_parser.parse(self.config.file)
141*d8210dc6SImre Kis
142*d8210dc6SImre Kis        with open(self.config.output, "w") as output_file:
143*d8210dc6SImre Kis            for item in index_file_parser.items:
144*d8210dc6SImre Kis                if item["type"] == "function":
145*d8210dc6SImre Kis                    patch = "\tpatch" if item["patch"] else ""
146*d8210dc6SImre Kis                    output_file.write(
147*d8210dc6SImre Kis                        item["library_name"] + "\t" + item["function_name"] + patch + "\n")
148*d8210dc6SImre Kis                else:
149*d8210dc6SImre Kis                    output_file.write("reserved\n")
150*d8210dc6SImre Kis
151*d8210dc6SImre Kis        if self.config.deps:
152*d8210dc6SImre Kis            with open(self.config.deps, "w") as deps_file:
153*d8210dc6SImre Kis                deps = [self.config.file] + index_file_parser.get_dependencies(self.config.file)
154*d8210dc6SImre Kis                deps_file.write(self.config.output + ": " + " \\\n".join(deps) + "\n")
155*d8210dc6SImre Kis
156*d8210dc6SImre Kisclass TableGenerator(RomlibApplication):
157*d8210dc6SImre Kis    """ Generates the jump table by parsing the index file. """
158*d8210dc6SImre Kis
159*d8210dc6SImre Kis    def __init__(self, prog):
160*d8210dc6SImre Kis        RomlibApplication.__init__(self, prog)
161*d8210dc6SImre Kis
162*d8210dc6SImre Kis        self.args.add_argument("-o", "--output", help="Output file", metavar="output",
163*d8210dc6SImre Kis                               default="jmpvar.s")
164*d8210dc6SImre Kis        self.args.add_argument("--bti", help="Branch Target Identification", type=int)
165*d8210dc6SImre Kis        self.args.add_argument("file", help="Input file")
166*d8210dc6SImre Kis
167*d8210dc6SImre Kis    def main(self):
168*d8210dc6SImre Kis        """
169*d8210dc6SImre Kis        Inserts the jmptbl definition and the jump entries into the output file. Also can insert
170*d8210dc6SImre Kis        BTI related code before entries if --bti option set. It can output a dependency file of the
171*d8210dc6SImre Kis        included index files. This can be directly included in makefiles.
172*d8210dc6SImre Kis        """
173*d8210dc6SImre Kis
174*d8210dc6SImre Kis        index_file_parser = IndexFileParser()
175*d8210dc6SImre Kis        index_file_parser.parse(self.config.file)
176*d8210dc6SImre Kis
177*d8210dc6SImre Kis        with open(self.config.output, "w") as output_file:
178*d8210dc6SImre Kis            output_file.write(self.build_template("jmptbl_header.S"))
179*d8210dc6SImre Kis            bti = "_bti" if self.config.bti == 1 else ""
180*d8210dc6SImre Kis
181*d8210dc6SImre Kis            for item in index_file_parser.items:
182*d8210dc6SImre Kis                template_name = "jmptbl_entry_" + item["type"] + bti + ".S"
183*d8210dc6SImre Kis                output_file.write(self.build_template(template_name, item, True))
184*d8210dc6SImre Kis
185*d8210dc6SImre Kisclass WrapperGenerator(RomlibApplication):
186*d8210dc6SImre Kis    """
187*d8210dc6SImre Kis    Generates a wrapper function for each entry in the index file except for the ones that contain
188*d8210dc6SImre Kis    the keyword patch. The generated wrapper file is called <lib>_<fn_name>.s.
189*d8210dc6SImre Kis    """
190*d8210dc6SImre Kis
191*d8210dc6SImre Kis    def __init__(self, prog):
192*d8210dc6SImre Kis        RomlibApplication.__init__(self, prog)
193*d8210dc6SImre Kis
194*d8210dc6SImre Kis        self.args.add_argument("-b", help="Build directory", default=".", metavar="build")
195*d8210dc6SImre Kis        self.args.add_argument("--bti", help="Branch Target Identification", type=int)
196*d8210dc6SImre Kis        self.args.add_argument("--list", help="Only list assembly files", action="store_true")
197*d8210dc6SImre Kis        self.args.add_argument("file", help="Input file")
198*d8210dc6SImre Kis
199*d8210dc6SImre Kis    def main(self):
200*d8210dc6SImre Kis        """
201*d8210dc6SImre Kis        Iterates through the items in the parsed index file and builds the template for each entry.
202*d8210dc6SImre Kis        """
203*d8210dc6SImre Kis
204*d8210dc6SImre Kis        index_file_parser = IndexFileParser()
205*d8210dc6SImre Kis        index_file_parser.parse(self.config.file)
206*d8210dc6SImre Kis
207*d8210dc6SImre Kis        bti = "_bti" if self.config.bti == 1 else ""
208*d8210dc6SImre Kis        function_offset = 0
209*d8210dc6SImre Kis        files = []
210*d8210dc6SImre Kis
211*d8210dc6SImre Kis        for item_index in range(0, len(index_file_parser.items)):
212*d8210dc6SImre Kis            item = index_file_parser.items[item_index]
213*d8210dc6SImre Kis
214*d8210dc6SImre Kis            if item["type"] == "reserved" or item["patch"]:
215*d8210dc6SImre Kis                continue
216*d8210dc6SImre Kis
217*d8210dc6SImre Kis            asm = self.config.b + "/" + item["function_name"] + ".s"
218*d8210dc6SImre Kis            if self.config.list:
219*d8210dc6SImre Kis                # Only listing files
220*d8210dc6SImre Kis                files.append(asm)
221*d8210dc6SImre Kis            else:
222*d8210dc6SImre Kis                with open(asm, "w") as asm_file:
223*d8210dc6SImre Kis                    # The jump instruction is 4 bytes but BTI requires and extra instruction so
224*d8210dc6SImre Kis                    # this makes it 8 bytes per entry.
225*d8210dc6SImre Kis                    function_offset = item_index * (8 if self.config.bti else 4)
226*d8210dc6SImre Kis
227*d8210dc6SImre Kis                    item["function_offset"] = function_offset
228*d8210dc6SImre Kis                    asm_file.write(self.build_template("wrapper" + bti + ".S", item))
229*d8210dc6SImre Kis
230*d8210dc6SImre Kis        if self.config.list:
231*d8210dc6SImre Kis            print(" ".join(files))
232*d8210dc6SImre Kis
233*d8210dc6SImre Kisclass VariableGenerator(RomlibApplication):
234*d8210dc6SImre Kis    """ Generates the jump table global variable with the absolute address in ROM. """
235*d8210dc6SImre Kis
236*d8210dc6SImre Kis    def __init__(self, prog):
237*d8210dc6SImre Kis        RomlibApplication.__init__(self, prog)
238*d8210dc6SImre Kis
239*d8210dc6SImre Kis        self.args.add_argument("-o", "--output", help="Output file", metavar="output",
240*d8210dc6SImre Kis                               default="jmpvar.s")
241*d8210dc6SImre Kis        self.args.add_argument("file", help="Input file")
242*d8210dc6SImre Kis
243*d8210dc6SImre Kis    def main(self):
244*d8210dc6SImre Kis        """
245*d8210dc6SImre Kis        Runs nm -a command on the input file and inserts the address of the .text section into the
246*d8210dc6SImre Kis        template as the ROM address of the jmp_table.
247*d8210dc6SImre Kis        """
248*d8210dc6SImre Kis        symbols = subprocess.check_output(["nm", "-a", self.config.file])
249*d8210dc6SImre Kis
250*d8210dc6SImre Kis        matching_symbol = re.search("([0-9A-Fa-f]+) . \\.text", str(symbols))
251*d8210dc6SImre Kis        if not matching_symbol:
252*d8210dc6SImre Kis            raise Exception("No '.text' section was found in %s" % self.config.file)
253*d8210dc6SImre Kis
254*d8210dc6SImre Kis        mapping = {"jmptbl_address": matching_symbol.group(1)}
255*d8210dc6SImre Kis
256*d8210dc6SImre Kis        with open(self.config.output, "w") as output_file:
257*d8210dc6SImre Kis            output_file.write(self.build_template("jmptbl_glob_var.S", mapping))
258*d8210dc6SImre Kis
259*d8210dc6SImre Kisif __name__ == "__main__":
260*d8210dc6SImre Kis    APPS = {"genvar": VariableGenerator, "pre": IndexPreprocessor,
261*d8210dc6SImre Kis            "gentbl": TableGenerator, "genwrappers": WrapperGenerator}
262*d8210dc6SImre Kis
263*d8210dc6SImre Kis    if len(sys.argv) < 2 or sys.argv[1] not in APPS:
264*d8210dc6SImre Kis        print("usage: romlib_generator.py [%s] [args]" % "|".join(APPS.keys()), file=sys.stderr)
265*d8210dc6SImre Kis        sys.exit(1)
266*d8210dc6SImre Kis
267*d8210dc6SImre Kis    APP = APPS[sys.argv[1]]("romlib_generator.py " + sys.argv[1])
268*d8210dc6SImre Kis    APP.parse_arguments(sys.argv[2:])
269*d8210dc6SImre Kis    try:
270*d8210dc6SImre Kis        APP.main()
271*d8210dc6SImre Kis        sys.exit(0)
272*d8210dc6SImre Kis    except FileNotFoundError as file_not_found_error:
273*d8210dc6SImre Kis        print(file_not_found_error, file=sys.stderr)
274*d8210dc6SImre Kis    except subprocess.CalledProcessError as called_process_error:
275*d8210dc6SImre Kis        print(called_process_error.output, file=sys.stderr)
276*d8210dc6SImre Kis
277*d8210dc6SImre Kis    sys.exit(1)
278