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