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