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 33It populates the number of SP in the defined macro 'NUM_SP' 34 $(eval $(call add_define_val,NUM_SP,{len(sp_layout.keys())})) 35 36A typical SP_LAYOUT_FILE file will look like 37{ 38 "SP1" : { 39 "image": "sp1.bin", 40 "pm": "test/sp1.dts" 41 }, 42 43 "SP2" : { 44 "image": "sp2.bin", 45 "pm": "test/sp2.dts", 46 "uuid": "1b1820fe-48f7-4175-8999-d51da00b7c9f" 47 } 48 49 ... 50} 51 52""" 53import json 54import os 55import re 56import sys 57import uuid 58import fdt 59from spactions import SpSetupActions 60import hob 61import struct 62from hob import HobList 63 64MAX_SP = 8 65UUID_LEN = 4 66HOB_OFFSET_DEFAULT=0x2000 67 68# Some helper functions to access args propagated to the action functions in 69# SpSetupActions framework. 70def check_sp_mk_gen(args :dict): 71 if "sp_gen_mk" not in args.keys(): 72 raise Exception(f"Path to file sp_gen.mk needs to be in 'args'.") 73 74def check_out_dir(args :dict): 75 if "out_dir" not in args.keys() or not os.path.isdir(args["out_dir"]): 76 raise Exception("Define output folder with \'out_dir\' key.") 77 78def check_sp_layout_dir(args :dict): 79 if "sp_layout_dir" not in args.keys() or not os.path.isdir(args["sp_layout_dir"]): 80 raise Exception("Define output folder with \'sp_layout_dir\' key.") 81 82def write_to_sp_mk_gen(content, args :dict): 83 check_sp_mk_gen(args) 84 with open(args["sp_gen_mk"], "a") as f: 85 f.write(f"{content}\n") 86 87def get_sp_manifest_full_path(sp_node, args :dict): 88 check_sp_layout_dir(args) 89 return os.path.join(args["sp_layout_dir"], get_file_from_layout(sp_node["pm"])) 90 91def get_sp_img_full_path(sp_node, args :dict): 92 check_sp_layout_dir(args) 93 return os.path.join(args["sp_layout_dir"], get_file_from_layout(sp_node["image"])) 94 95def get_size(sp_node): 96 if not "size" in sp_node: 97 print("WARNING: default image size 0x100000") 98 return 0x100000 99 100 # Try if it was a decimal value. 101 try: 102 return int(sp_node["size"]) 103 except ValueError: 104 print("WARNING: trying to parse base 16 size") 105 # Try if it is of base 16 106 return int(sp_node["size"], 16) 107 108def get_sp_pkg(sp, args :dict): 109 check_out_dir(args) 110 return os.path.join(args["out_dir"], f"{sp}.pkg") 111 112def is_line_in_sp_gen(line, args :dict): 113 with open(args["sp_gen_mk"], "r") as f: 114 sppkg_rule = [l for l in f if line in l] 115 return len(sppkg_rule) != 0 116 117def get_file_from_layout(node): 118 ''' Helper to fetch a file path from sp_layout.json. ''' 119 if type(node) is dict and "file" in node.keys(): 120 return node["file"] 121 return node 122 123def get_offset_from_layout(node): 124 ''' Helper to fetch an offset from sp_layout.json. ''' 125 if type(node) is dict and "offset" in node.keys(): 126 return int(node["offset"], 0) 127 return None 128 129def get_image_offset(node): 130 ''' Helper to fetch image offset from sp_layout.json ''' 131 return get_offset_from_layout(node["image"]) 132 133def get_pm_offset(node): 134 ''' Helper to fetch pm offset from sp_layout.json ''' 135 return get_offset_from_layout(node["pm"]) 136 137def get_uuid(sp_layout, sp, args :dict): 138 ''' Helper to fetch uuid from pm file listed in sp_layout.json''' 139 if "uuid" in sp_layout[sp]: 140 # Extract the UUID from the JSON file if the SP entry has a 'uuid' field 141 uuid_std = uuid.UUID(sp_layout[sp]['uuid']) 142 else: 143 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f: 144 uuid_lines = [l for l in pm_f if 'uuid' in l] 145 assert(len(uuid_lines) == 1) 146 # The uuid field in SP manifest is the little endian representation 147 # mapped to arguments as described in SMCCC section 5.3. 148 # Convert each unsigned integer value to a big endian representation 149 # required by fiptool. 150 uuid_parsed = re.findall("0x([0-9a-f]+)", uuid_lines[0]) 151 y = list(map(bytearray.fromhex, uuid_parsed)) 152 z = [int.from_bytes(i, byteorder='little', signed=False) for i in y] 153 uuid_std = uuid.UUID(f'{z[0]:08x}{z[1]:08x}{z[2]:08x}{z[3]:08x}') 154 return uuid_std 155 156def get_load_address(sp_layout, sp, args :dict): 157 ''' Helper to fetch load-address from pm file listed in sp_layout.json''' 158 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f: 159 load_address_lines = [l for l in pm_f if 'load-address' in l] 160 161 if len(load_address_lines) != 1: 162 return None 163 164 load_address_parsed = re.search("(0x[0-9a-f]+)", load_address_lines[0]) 165 return load_address_parsed.group(0) 166 167@SpSetupActions.sp_action(global_action=True) 168def check_max_sps(sp_layout, _, args :dict): 169 ''' Check validate the maximum number of SPs is respected. ''' 170 if len(sp_layout.keys()) > MAX_SP: 171 raise Exception(f"Too many SPs in SP layout file. Max: {MAX_SP}") 172 return args 173 174@SpSetupActions.sp_action(global_action=True) 175def count_sps(sp_layout, _, args :dict): 176 ''' Count number of SP and put in NUM_SP ''' 177 write_to_sp_mk_gen(f"$(eval $(call add_define_val,NUM_SP,{len(sp_layout.keys())}))", args) 178 return args 179 180@SpSetupActions.sp_action 181def gen_fdt_sources(sp_layout, sp, args :dict): 182 ''' Generate FDT_SOURCES values for a given SP. ''' 183 manifest_path = get_sp_manifest_full_path(sp_layout[sp], args) 184 write_to_sp_mk_gen(f"FDT_SOURCES += {manifest_path}", args) 185 return args 186 187@SpSetupActions.sp_action(exec_order=1) 188def generate_hob_list(sp_layout, sp, args: dict): 189 ''' 190 Generates a HOB file for the partition, if it requested it in its FF-A 191 manifest. 192 ''' 193 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as f: 194 sp_fdt = fdt.parse_dts(f.read()) 195 196 if sp_fdt.exist_property('hob_list', '/boot-info'): 197 sp_hob_name = os.path.basename(sp + ".hob.bin") 198 sp_hob_name = os.path.join(args["out_dir"], f"{sp_hob_name}") 199 200 # Add to the args so it can be consumed by the TL pkg function. 201 sp_layout[sp]["hob_path"] = sp_hob_name 202 hob_list = hob.generate_hob_from_fdt_node(sp_fdt, HOB_OFFSET_DEFAULT) 203 with open(sp_hob_name, "wb") as h: 204 for block in hob_list.get_list(): 205 h.write(block.pack()) 206 207 return args 208 209def generate_sp_pkg(sp_node, pkg, sp_img, sp_dtb): 210 ''' Generates the rule in case SP is to be generated in an SP Pkg. ''' 211 pm_offset = get_pm_offset(sp_node) 212 sptool_args = f" --pm-offset {pm_offset}" if pm_offset is not None else "" 213 image_offset = get_image_offset(sp_node) 214 sptool_args += f" --img-offset {image_offset}" if image_offset is not None else "" 215 sptool_args += f" -o {pkg}" 216 return f''' 217{pkg}: {sp_dtb} {sp_img} 218\t$(Q)echo Generating {pkg} 219\t$(Q)$(PYTHON) $(SPTOOL) -i {sp_img}:{sp_dtb} {sptool_args} 220''' 221 222def generate_tl_pkg(sp_node, pkg, sp_img, sp_dtb, hob_path = None): 223 ''' Generate make rules for a Transfer List type package. ''' 224 # TE Type for the FF-A manifest. 225 TE_FFA_MANIFEST = 0x106 226 # TE Type for the SP binary. 227 TE_SP_BINARY = 0x103 228 # TE Type for the HOB List. 229 TE_HOB_LIST = 0x3 230 tlc_add_hob = f"\t$(Q)poetry run tlc add --entry {TE_HOB_LIST} {hob_path} {pkg}" if hob_path is not None else "" 231 return f''' 232{pkg}: {sp_dtb} {sp_img} 233\t$(Q)echo Generating {pkg} 234\t$(Q)$(TLCTOOL) create --size {get_size(sp_node)} --entry {TE_FFA_MANIFEST} {sp_dtb} {pkg} --align 12 235\t$(Q)$(TLCTOOL) add --entry {TE_SP_BINARY} {sp_img} {pkg} 236''' 237 238@SpSetupActions.sp_action 239def gen_partition_pkg(sp_layout, sp, args :dict): 240 ''' Generate Sp Pkgs rules. ''' 241 pkg = get_sp_pkg(sp, args) 242 243 sp_dtb_name = os.path.basename(get_file_from_layout(sp_layout[sp]["pm"]))[:-1] + "b" 244 sp_dtb = os.path.join(args["out_dir"], f"fdts/{sp_dtb_name}") 245 sp_img = get_sp_img_full_path(sp_layout[sp], args) 246 247 # Do not generate rule if already there. 248 if is_line_in_sp_gen(f'{pkg}:', args): 249 return args 250 251 # This should include all packages of all kinds. 252 write_to_sp_mk_gen(f"SP_PKGS += {pkg}\n", args) 253 package_type = sp_layout[sp]["package"] if "package" in sp_layout[sp] else "sp_pkg" 254 255 if package_type == "sp_pkg": 256 partition_pkg_rule = generate_sp_pkg(sp_layout[sp], pkg, sp_img, sp_dtb) 257 elif package_type == "tl_pkg": 258 partition_pkg_rule = generate_tl_pkg(sp_layout[sp], pkg, sp_img, sp_dtb) 259 else: 260 raise ValueError(f"Specified invalid pkg type {package_type}") 261 262 write_to_sp_mk_gen(partition_pkg_rule, args) 263 return args 264 265@SpSetupActions.sp_action(global_action=True, exec_order=1) 266def check_dualroot(sp_layout, _, args :dict): 267 ''' Validate the amount of SPs from SiP and Platform owners. ''' 268 if not args.get("dualroot"): 269 return args 270 args["split"] = int(MAX_SP / 2) 271 owners = [sp_layout[sp].get("owner") for sp in sp_layout] 272 args["plat_max_count"] = owners.count("Plat") 273 274 # If it is owned by the platform owner, it is assigned to the SiP. 275 args["sip_max_count"] = len(sp_layout.keys()) - args["plat_max_count"] 276 if args["sip_max_count"] > args["split"] or args["sip_max_count"] > args["split"]: 277 print(f"WARN: SiP Secure Partitions should not be more than {args['split']}") 278 # Counters for gen_crt_args. 279 args["sip_count"] = 1 280 args["plat_count"] = 1 281 return args 282 283@SpSetupActions.sp_action 284def gen_crt_args(sp_layout, sp, args :dict): 285 ''' Append CRT_ARGS. ''' 286 # If "dualroot" is configured, 'sp_pkg_idx' depends on whether the SP is owned 287 # by the "SiP" or the "Plat". 288 if args.get("dualroot"): 289 # If the owner is not specified as "Plat", default to "SiP". 290 if sp_layout[sp].get("owner") == "Plat": 291 if args["plat_count"] > args["plat_max_count"]: 292 raise ValueError("plat_count can't surpass plat_max_count in args.") 293 sp_pkg_idx = args["plat_count"] + args["split"] 294 args["plat_count"] += 1 295 else: 296 if args["sip_count"] > args["sip_max_count"]: 297 raise ValueError("sip_count can't surpass sip_max_count in args.") 298 sp_pkg_idx = args["sip_count"] 299 args["sip_count"] += 1 300 else: 301 sp_pkg_idx = [k for k in sp_layout.keys()].index(sp) + 1 302 write_to_sp_mk_gen(f"CRT_ARGS += --sp-pkg{sp_pkg_idx} {get_sp_pkg(sp, args)}\n", args) 303 return args 304 305@SpSetupActions.sp_action 306def gen_fiptool_args(sp_layout, sp, args :dict): 307 ''' Generate arguments for the FIP Tool. ''' 308 uuid_std = get_uuid(sp_layout, sp, args) 309 write_to_sp_mk_gen(f"FIP_ARGS += --blob uuid={str(uuid_std)},file={get_sp_pkg(sp, args)}\n", args) 310 return args 311 312@SpSetupActions.sp_action 313def gen_fconf_fragment(sp_layout, sp, args: dict): 314 ''' Generate the fconf fragment file''' 315 with open(args["fconf_fragment"], "a") as f: 316 uuid = get_uuid(sp_layout, sp, args) 317 owner = "Plat" if sp_layout[sp].get("owner") == "Plat" else "SiP" 318 319 if "physical-load-address" in sp_layout[sp].keys(): 320 load_address = sp_layout[sp]["physical-load-address"] 321 else: 322 load_address = get_load_address(sp_layout, sp, args) 323 324 if load_address is not None: 325 f.write( 326f'''\ 327{sp} {{ 328 uuid = "{uuid}"; 329 load-address = <{load_address}>; 330 owner = "{owner}"; 331}}; 332 333''') 334 else: 335 print("Warning: No load-address was found in the SP manifest.") 336 337 return args 338 339def init_sp_actions(sys): 340 # Initialize arguments for the SP actions framework 341 args = {} 342 args["sp_gen_mk"] = os.path.abspath(sys.argv[1]) 343 sp_layout_file = os.path.abspath(sys.argv[2]) 344 args["sp_layout_dir"] = os.path.dirname(sp_layout_file) 345 args["out_dir"] = os.path.abspath(sys.argv[3]) 346 args["dualroot"] = sys.argv[4] == "dualroot" 347 args["fconf_fragment"] = os.path.abspath(sys.argv[5]) 348 349 350 with open(sp_layout_file) as json_file: 351 sp_layout = json.load(json_file) 352 #Clear content of file "sp_gen.mk". 353 with open(args["sp_gen_mk"], "w"): 354 None 355 #Clear content of file "fconf_fragment". 356 with open(args["fconf_fragment"], "w"): 357 None 358 359 return args, sp_layout 360 361if __name__ == "__main__": 362 args, sp_layout = init_sp_actions(sys) 363 SpSetupActions.run_actions(sp_layout, args) 364