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