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