1#!/usr/bin/python3 2# Copyright (c) 2020-2024, Arm Limited. All rights reserved. 3# 4# SPDX-License-Identifier: BSD-3-Clause 5 6""" 7This script is invoked by Make system and generates secure partition makefile. 8It expects platform provided secure partition layout file which contains list 9of Secure Partition Images and Partition manifests(PM). 10Layout file can exist outside of TF-A tree and the paths of Image and PM files 11must be relative to it. 12 13This script parses the layout file and generates a make file which updates 14FDT_SOURCES, FIP_ARGS, CRT_ARGS and SPTOOL_ARGS which are used in later build 15steps. 16If the SP entry in the layout file has a "uuid" field the scripts gets the UUID 17from there, otherwise it parses the associated partition manifest and extracts 18the UUID from there. 19 20param1: Generated mk file "sp_gen.mk" 21param2: "SP_LAYOUT_FILE", json file containing platform provided information 22param3: plat out directory 23param4: CoT parameter 24param5: Generated dts file "sp_list_fragment.dts" 25 26Generated "sp_gen.mk" file contains triplet of following information for each 27Secure Partition entry 28 FDT_SOURCES += sp1.dts 29 SPTOOL_ARGS += -i sp1.bin:sp1.dtb -o sp1.pkg 30 FIP_ARGS += --blob uuid=XXXXX-XXX...,file=sp1.pkg 31 CRT_ARGS += --sp-pkg1 sp1.pkg 32 33A typical SP_LAYOUT_FILE file will look like 34{ 35 "SP1" : { 36 "image": "sp1.bin", 37 "pm": "test/sp1.dts" 38 }, 39 40 "SP2" : { 41 "image": "sp2.bin", 42 "pm": "test/sp2.dts", 43 "uuid": "1b1820fe-48f7-4175-8999-d51da00b7c9f" 44 } 45 46 ... 47} 48 49""" 50import json 51import os 52import re 53import sys 54import uuid 55from spactions import SpSetupActions 56 57MAX_SP = 8 58UUID_LEN = 4 59 60# Some helper functions to access args propagated to the action functions in 61# SpSetupActions framework. 62def check_sp_mk_gen(args :dict): 63 if "sp_gen_mk" not in args.keys(): 64 raise Exception(f"Path to file sp_gen.mk needs to be in 'args'.") 65 66def check_out_dir(args :dict): 67 if "out_dir" not in args.keys() or not os.path.isdir(args["out_dir"]): 68 raise Exception("Define output folder with \'out_dir\' key.") 69 70def check_sp_layout_dir(args :dict): 71 if "sp_layout_dir" not in args.keys() or not os.path.isdir(args["sp_layout_dir"]): 72 raise Exception("Define output folder with \'sp_layout_dir\' key.") 73 74def write_to_sp_mk_gen(content, args :dict): 75 check_sp_mk_gen(args) 76 with open(args["sp_gen_mk"], "a") as f: 77 f.write(f"{content}\n") 78 79def get_sp_manifest_full_path(sp_node, args :dict): 80 check_sp_layout_dir(args) 81 return os.path.join(args["sp_layout_dir"], get_file_from_layout(sp_node["pm"])) 82 83def get_sp_img_full_path(sp_node, args :dict): 84 check_sp_layout_dir(args) 85 return os.path.join(args["sp_layout_dir"], get_file_from_layout(sp_node["image"])) 86 87def get_size(sp_node): 88 if not "size" in sp_node: 89 print("WARNING: default image size 0x100000") 90 return 0x100000 91 92 # Try if it was a decimal value. 93 try: 94 return int(sp_node["size"]) 95 except ValueError: 96 print("WARNING: trying to parse base 16 size") 97 # Try if it is of base 16 98 return int(sp_node["size"], 16) 99 100def get_sp_pkg(sp, args :dict): 101 check_out_dir(args) 102 return os.path.join(args["out_dir"], f"{sp}.pkg") 103 104def is_line_in_sp_gen(line, args :dict): 105 with open(args["sp_gen_mk"], "r") as f: 106 sppkg_rule = [l for l in f if line in l] 107 return len(sppkg_rule) != 0 108 109def get_file_from_layout(node): 110 ''' Helper to fetch a file path from sp_layout.json. ''' 111 if type(node) is dict and "file" in node.keys(): 112 return node["file"] 113 return node 114 115def get_offset_from_layout(node): 116 ''' Helper to fetch an offset from sp_layout.json. ''' 117 if type(node) is dict and "offset" in node.keys(): 118 return int(node["offset"], 0) 119 return None 120 121def get_image_offset(node): 122 ''' Helper to fetch image offset from sp_layout.json ''' 123 return get_offset_from_layout(node["image"]) 124 125def get_pm_offset(node): 126 ''' Helper to fetch pm offset from sp_layout.json ''' 127 return get_offset_from_layout(node["pm"]) 128 129def get_uuid(sp_layout, sp, args :dict): 130 ''' Helper to fetch uuid from pm file listed in sp_layout.json''' 131 if "uuid" in sp_layout[sp]: 132 # Extract the UUID from the JSON file if the SP entry has a 'uuid' field 133 uuid_std = uuid.UUID(sp_layout[sp]['uuid']) 134 else: 135 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f: 136 uuid_lines = [l for l in pm_f if 'uuid' in l] 137 assert(len(uuid_lines) == 1) 138 # The uuid field in SP manifest is the little endian representation 139 # mapped to arguments as described in SMCCC section 5.3. 140 # Convert each unsigned integer value to a big endian representation 141 # required by fiptool. 142 uuid_parsed = re.findall("0x([0-9a-f]+)", uuid_lines[0]) 143 y = list(map(bytearray.fromhex, uuid_parsed)) 144 z = [int.from_bytes(i, byteorder='little', signed=False) for i in y] 145 uuid_std = uuid.UUID(f'{z[0]:08x}{z[1]:08x}{z[2]:08x}{z[3]:08x}') 146 return uuid_std 147 148def get_load_address(sp_layout, sp, args :dict): 149 ''' Helper to fetch load-address from pm file listed in sp_layout.json''' 150 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f: 151 load_address_lines = [l for l in pm_f if 'load-address' in l] 152 153 if len(load_address_lines) != 1: 154 return None 155 156 load_address_parsed = re.search("(0x[0-9a-f]+)", load_address_lines[0]) 157 return load_address_parsed.group(0) 158 159@SpSetupActions.sp_action(global_action=True) 160def check_max_sps(sp_layout, _, args :dict): 161 ''' Check validate the maximum number of SPs is respected. ''' 162 if len(sp_layout.keys()) > MAX_SP: 163 raise Exception(f"Too many SPs in SP layout file. Max: {MAX_SP}") 164 return args 165 166@SpSetupActions.sp_action 167def gen_fdt_sources(sp_layout, sp, args :dict): 168 ''' Generate FDT_SOURCES values for a given SP. ''' 169 manifest_path = get_sp_manifest_full_path(sp_layout[sp], args) 170 write_to_sp_mk_gen(f"FDT_SOURCES += {manifest_path}", args) 171 return args 172 173def generate_sp_pkg(sp_node, pkg, sp_img, sp_dtb): 174 ''' Generates the rule in case SP is to be generated in an SP Pkg. ''' 175 pm_offset = get_pm_offset(sp_node) 176 sptool_args = f" --pm-offset {pm_offset}" if pm_offset is not None else "" 177 image_offset = get_image_offset(sp_node) 178 sptool_args += f" --img-offset {image_offset}" if image_offset is not None else "" 179 sptool_args += f" -o {pkg}" 180 return f''' 181{pkg}: {sp_dtb} {sp_img} 182\t$(Q)echo Generating {pkg} 183\t$(Q)$(PYTHON) $(SPTOOL) -i {sp_img}:{sp_dtb} {sptool_args} 184''' 185 186def generate_tl_pkg(sp_node, pkg, sp_img, sp_dtb, hob_path = None): 187 ''' Generate make rules for a Transfer List type package. ''' 188 # TE Type for the FF-A manifest. 189 TE_FFA_MANIFEST = 0x106 190 # TE Type for the SP binary. 191 TE_SP_BINARY = 0x103 192 # TE Type for the HOB List. 193 TE_HOB_LIST = 0x3 194 tlc_add_hob = f"\t$(Q)poetry run tlc add --entry {TE_HOB_LIST} {hob_path} {pkg}" if hob_path is not None else "" 195 return f''' 196{pkg}: {sp_dtb} {sp_img} 197\t$(Q)echo Generating {pkg} 198\t$(Q)$(TLCTOOL) create --size {get_size(sp_node)} --entry {TE_FFA_MANIFEST} {sp_dtb} {pkg} --align 12 199\t$(Q)$(TLCTOOL) add --entry {TE_SP_BINARY} {sp_img} {pkg} 200''' 201 202@SpSetupActions.sp_action 203def gen_partition_pkg(sp_layout, sp, args :dict): 204 ''' Generate Sp Pkgs rules. ''' 205 pkg = get_sp_pkg(sp, args) 206 207 sp_dtb_name = os.path.basename(get_file_from_layout(sp_layout[sp]["pm"]))[:-1] + "b" 208 sp_dtb = os.path.join(args["out_dir"], f"fdts/{sp_dtb_name}") 209 sp_img = get_sp_img_full_path(sp_layout[sp], args) 210 211 # Do not generate rule if already there. 212 if is_line_in_sp_gen(f'{pkg}:', args): 213 return args 214 215 # This should include all packages of all kinds. 216 write_to_sp_mk_gen(f"SP_PKGS += {pkg}\n", args) 217 package_type = sp_layout[sp]["package"] if "package" in sp_layout[sp] else "sp_pkg" 218 219 if package_type == "sp_pkg": 220 partition_pkg_rule = generate_sp_pkg(sp_layout[sp], pkg, sp_img, sp_dtb) 221 elif package_type == "tl_pkg": 222 partition_pkg_rule = generate_tl_pkg(sp_layout[sp], pkg, sp_img, sp_dtb) 223 else: 224 raise ValueError(f"Specified invalid pkg type {package_type}") 225 226 write_to_sp_mk_gen(partition_pkg_rule, args) 227 return args 228 229@SpSetupActions.sp_action(global_action=True, exec_order=1) 230def check_dualroot(sp_layout, _, args :dict): 231 ''' Validate the amount of SPs from SiP and Platform owners. ''' 232 if not args.get("dualroot"): 233 return args 234 args["split"] = int(MAX_SP / 2) 235 owners = [sp_layout[sp].get("owner") for sp in sp_layout] 236 args["plat_max_count"] = owners.count("Plat") 237 238 # If it is owned by the platform owner, it is assigned to the SiP. 239 args["sip_max_count"] = len(sp_layout.keys()) - args["plat_max_count"] 240 if args["sip_max_count"] > args["split"] or args["sip_max_count"] > args["split"]: 241 print(f"WARN: SiP Secure Partitions should not be more than {args['split']}") 242 # Counters for gen_crt_args. 243 args["sip_count"] = 1 244 args["plat_count"] = 1 245 return args 246 247@SpSetupActions.sp_action 248def gen_crt_args(sp_layout, sp, args :dict): 249 ''' Append CRT_ARGS. ''' 250 # If "dualroot" is configured, 'sp_pkg_idx' depends on whether the SP is owned 251 # by the "SiP" or the "Plat". 252 if args.get("dualroot"): 253 # If the owner is not specified as "Plat", default to "SiP". 254 if sp_layout[sp].get("owner") == "Plat": 255 if args["plat_count"] > args["plat_max_count"]: 256 raise ValueError("plat_count can't surpass plat_max_count in args.") 257 sp_pkg_idx = args["plat_count"] + args["split"] 258 args["plat_count"] += 1 259 else: 260 if args["sip_count"] > args["sip_max_count"]: 261 raise ValueError("sip_count can't surpass sip_max_count in args.") 262 sp_pkg_idx = args["sip_count"] 263 args["sip_count"] += 1 264 else: 265 sp_pkg_idx = [k for k in sp_layout.keys()].index(sp) + 1 266 write_to_sp_mk_gen(f"CRT_ARGS += --sp-pkg{sp_pkg_idx} {get_sp_pkg(sp, args)}\n", args) 267 return args 268 269@SpSetupActions.sp_action 270def gen_fiptool_args(sp_layout, sp, args :dict): 271 ''' Generate arguments for the FIP Tool. ''' 272 uuid_std = get_uuid(sp_layout, sp, args) 273 write_to_sp_mk_gen(f"FIP_ARGS += --blob uuid={str(uuid_std)},file={get_sp_pkg(sp, args)}\n", args) 274 return args 275 276@SpSetupActions.sp_action 277def gen_fconf_fragment(sp_layout, sp, args: dict): 278 ''' Generate the fconf fragment file''' 279 with open(args["fconf_fragment"], "a") as f: 280 uuid = get_uuid(sp_layout, sp, args) 281 owner = "Plat" if sp_layout[sp].get("owner") == "Plat" else "SiP" 282 283 if "physical-load-address" in sp_layout[sp].keys(): 284 load_address = sp_layout[sp]["physical-load-address"] 285 else: 286 load_address = get_load_address(sp_layout, sp, args) 287 288 if load_address is not None: 289 f.write( 290f'''\ 291{sp} {{ 292 uuid = "{uuid}"; 293 load-address = <{load_address}>; 294 owner = "{owner}"; 295}}; 296 297''') 298 else: 299 print("Warning: No load-address was found in the SP manifest.") 300 301 return args 302 303def init_sp_actions(sys): 304 # Initialize arguments for the SP actions framework 305 args = {} 306 args["sp_gen_mk"] = os.path.abspath(sys.argv[1]) 307 sp_layout_file = os.path.abspath(sys.argv[2]) 308 args["sp_layout_dir"] = os.path.dirname(sp_layout_file) 309 args["out_dir"] = os.path.abspath(sys.argv[3]) 310 args["dualroot"] = sys.argv[4] == "dualroot" 311 args["fconf_fragment"] = os.path.abspath(sys.argv[5]) 312 313 314 with open(sp_layout_file) as json_file: 315 sp_layout = json.load(json_file) 316 #Clear content of file "sp_gen.mk". 317 with open(args["sp_gen_mk"], "w"): 318 None 319 #Clear content of file "fconf_fragment". 320 with open(args["fconf_fragment"], "w"): 321 None 322 323 return args, sp_layout 324 325if __name__ == "__main__": 326 args, sp_layout = init_sp_actions(sys) 327 SpSetupActions.run_actions(sp_layout, args) 328