1#!/usr/bin/python3 2# Copyright (c) 2020-2026, 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 140 def to_bytes_even(h: str) -> bytes: 141 ''' 142 Zero-fill any odd-length hex string representing an UUID since 143 bytes.fromhex() always expects even number of bytes. 144 ''' 145 h = h.zfill(len(h) + len(h) % 2) 146 return bytes.fromhex(h) 147 148 if "uuid" in sp_layout[sp]: 149 # Extract the UUID from the JSON file if the SP entry has a 'uuid' field 150 uuid_std = uuid.UUID(sp_layout[sp]['uuid']) 151 else: 152 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f: 153 uuid_lines = [l for l in pm_f if 'uuid' in l] 154 assert(len(uuid_lines) == 1) 155 # The uuid field in SP manifest is the little endian representation 156 # mapped to arguments as described in SMCCC section 5.3. 157 # Convert each unsigned integer value to a big endian representation 158 # required by fiptool. 159 uuid_parsed = re.findall("0x([0-9a-f]+)", uuid_lines[0]) 160 161 # UUID must have 4 little endian words 162 assert len(uuid_parsed) == 4, "SP manifest UUID must have exactly 4 words" 163 164 y = [to_bytes_even(uuid) for uuid in uuid_parsed] 165 z = [int.from_bytes(i, byteorder='little', signed=False) for i in y] 166 uuid_std = uuid.UUID(f'{z[0]:08x}{z[1]:08x}{z[2]:08x}{z[3]:08x}') 167 return uuid_std 168 169def get_load_address(sp_layout, sp, args :dict): 170 ''' Helper to fetch load-address from pm file listed in sp_layout.json''' 171 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f: 172 load_address_lines = [l for l in pm_f if re.search(r'load-address[^-]', l)] 173 174 if len(load_address_lines) != 1: 175 return None 176 177 load_address_parsed = re.search("(0x[0-9a-f]+)", load_address_lines[0]) 178 return load_address_parsed.group(0) 179 180@SpSetupActions.sp_action(global_action=True) 181def check_max_sps(sp_layout, _, args :dict): 182 ''' Check validate the maximum number of SPs is respected. ''' 183 if len(sp_layout.keys()) > MAX_SP: 184 raise Exception(f"Too many SPs in SP layout file. Max: {MAX_SP}") 185 return args 186 187@SpSetupActions.sp_action(global_action=True) 188def count_sps(sp_layout, _, args :dict): 189 ''' Count number of SP and put in NUM_SP ''' 190 write_to_sp_mk_gen(f"$(eval $(call add_define_val,NUM_SP,{len(sp_layout.keys())}))", args) 191 return args 192 193@SpSetupActions.sp_action 194def gen_fdt_sources(sp_layout, sp, args :dict): 195 ''' Generate FDT_SOURCES values for a given SP. ''' 196 manifest_path = get_sp_manifest_full_path(sp_layout[sp], args) 197 write_to_sp_mk_gen(f"FDT_SOURCES += {manifest_path}", args) 198 return args 199 200@SpSetupActions.sp_action(exec_order=1) 201def generate_hob_list(sp_layout, sp, args: dict): 202 ''' 203 Generates a HOB file for the partition, if it requested it in its FF-A 204 manifest. 205 ''' 206 with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as f: 207 sp_fdt = fdt.parse_dts(f.read()) 208 209 if sp_fdt.exist_property('hob_list', '/boot-info'): 210 sp_hob_name = os.path.basename(sp + ".hob.bin") 211 sp_hob_name = os.path.join(args["out_dir"], f"{sp_hob_name}") 212 213 # Add to the args so it can be consumed by the TL pkg function. 214 sp_layout[sp]["hob_path"] = sp_hob_name 215 hob_list = hob.generate_hob_from_fdt_node(sp_fdt, HOB_OFFSET_DEFAULT) 216 with open(sp_hob_name, "wb") as h: 217 for block in hob_list.get_list(): 218 h.write(block.pack()) 219 220 return args 221 222def generate_sp_pkg(sp_node, pkg, sp_img, sp_dtb): 223 ''' Generates the rule in case SP is to be generated in an SP Pkg. ''' 224 pm_offset = get_pm_offset(sp_node) 225 sptool_args = f" --pm-offset {pm_offset}" if pm_offset is not None else "" 226 image_offset = get_image_offset(sp_node) 227 sptool_args += f" --img-offset {image_offset}" if image_offset is not None else "" 228 sptool_args += f" -o {pkg}" 229 return f''' 230{pkg}: {sp_dtb} {sp_img} 231\t$(Q)echo Generating {pkg} 232\t$(Q)$(PYTHON) $(SPTOOL) -i {sp_img}:{sp_dtb} {sptool_args} 233''' 234 235def generate_tl_pkg(sp_node, pkg, sp_img, sp_dtb, hob_path = None): 236 ''' Generate make rules for a Transfer List type package. ''' 237 # TE Type for the FF-A manifest. 238 TE_FFA_MANIFEST = 0x106 239 # TE Type for the SP binary. 240 TE_SP_BINARY = 0x103 241 # TE Type for the HOB List. 242 TE_HOB_LIST = 0x3 243 tlc_add_hob = f"\t$(Q)$(TLCTOOL) add --entry {TE_HOB_LIST} {hob_path} {pkg}" if hob_path is not None else "" 244 return f''' 245{pkg}: {sp_dtb} {sp_img} 246\t$(Q)echo Generating {pkg} 247\t$(Q)$(TLCTOOL) create --size {get_size(sp_node)} --entry {TE_FFA_MANIFEST} {sp_dtb} {pkg} --align 12 248{tlc_add_hob} 249\t$(Q)$(TLCTOOL) add --entry {TE_SP_BINARY} {sp_img} {pkg} 250''' 251 252@SpSetupActions.sp_action 253def gen_partition_pkg(sp_layout, sp, args :dict): 254 ''' Generate Sp Pkgs rules. ''' 255 pkg = get_sp_pkg(sp, args) 256 257 sp_dtb_name = os.path.basename(get_file_from_layout(sp_layout[sp]["pm"]))[:-1] + "b" 258 sp_dtb = os.path.join(args["out_dir"], f"fdts/{sp_dtb_name}") 259 sp_img = get_sp_img_full_path(sp_layout[sp], args) 260 261 # Do not generate rule if already there. 262 if is_line_in_sp_gen(f'{pkg}:', args): 263 return args 264 265 # This should include all packages of all kinds. 266 write_to_sp_mk_gen(f"SP_PKGS += {pkg}\n", args) 267 package_type = sp_layout[sp]["package"] if "package" in sp_layout[sp] else "sp_pkg" 268 269 if package_type == "sp_pkg": 270 partition_pkg_rule = generate_sp_pkg(sp_layout[sp], pkg, sp_img, sp_dtb) 271 elif package_type == "tl_pkg": 272 # Conditionally provide the Hob. 273 hob_path = sp_layout[sp]["hob_path"] if "hob_path" in sp_layout[sp] else None 274 partition_pkg_rule = generate_tl_pkg( 275 sp_layout[sp], pkg, sp_img, sp_dtb, hob_path) 276 else: 277 raise ValueError(f"Specified invalid pkg type {package_type}") 278 279 write_to_sp_mk_gen(partition_pkg_rule, args) 280 return args 281 282@SpSetupActions.sp_action(global_action=True, exec_order=1) 283def check_dualroot(sp_layout, _, args :dict): 284 ''' Validate the amount of SPs from SiP and Platform owners. ''' 285 if not args.get("dualroot"): 286 return args 287 args["split"] = int(MAX_SP / 2) 288 owners = [sp_layout[sp].get("owner") for sp in sp_layout] 289 args["plat_max_count"] = owners.count("Plat") 290 291 # If it is owned by the platform owner, it is assigned to the SiP. 292 args["sip_max_count"] = len(sp_layout.keys()) - args["plat_max_count"] 293 if args["sip_max_count"] > args["split"] or args["sip_max_count"] > args["split"]: 294 print(f"WARN: SiP Secure Partitions should not be more than {args['split']}") 295 # Counters for gen_crt_args. 296 args["sip_count"] = 1 297 args["plat_count"] = 1 298 return args 299 300@SpSetupActions.sp_action 301def gen_crt_args(sp_layout, sp, args :dict): 302 ''' Append CRT_ARGS. ''' 303 # If "dualroot" is configured, 'sp_pkg_idx' depends on whether the SP is owned 304 # by the "SiP" or the "Plat". 305 if args.get("dualroot"): 306 # If the owner is not specified as "Plat", default to "SiP". 307 if sp_layout[sp].get("owner") == "Plat": 308 if args["plat_count"] > args["plat_max_count"]: 309 raise ValueError("plat_count can't surpass plat_max_count in args.") 310 sp_pkg_idx = args["plat_count"] + args["split"] 311 args["plat_count"] += 1 312 else: 313 if args["sip_count"] > args["sip_max_count"]: 314 raise ValueError("sip_count can't surpass sip_max_count in args.") 315 sp_pkg_idx = args["sip_count"] 316 args["sip_count"] += 1 317 else: 318 sp_pkg_idx = [k for k in sp_layout.keys()].index(sp) + 1 319 write_to_sp_mk_gen(f"CRT_ARGS += --sp-pkg{sp_pkg_idx} {get_sp_pkg(sp, args)}\n", args) 320 return args 321 322@SpSetupActions.sp_action 323def gen_fiptool_args(sp_layout, sp, args :dict): 324 ''' Generate arguments for the FIP Tool. ''' 325 uuid_std = get_uuid(sp_layout, sp, args) 326 write_to_sp_mk_gen(f"FIP_ARGS += --blob uuid={str(uuid_std)},file={get_sp_pkg(sp, args)}\n", args) 327 return args 328 329@SpSetupActions.sp_action 330def gen_fconf_fragment(sp_layout, sp, args: dict): 331 ''' Generate the fconf fragment file''' 332 with open(args["fconf_fragment"], "a") as f: 333 uuid = get_uuid(sp_layout, sp, args) 334 owner = "Plat" if sp_layout[sp].get("owner") == "Plat" else "SiP" 335 336 if "physical-load-address" in sp_layout[sp].keys(): 337 load_address = sp_layout[sp]["physical-load-address"] 338 else: 339 load_address = get_load_address(sp_layout, sp, args) 340 341 if load_address is not None: 342 f.write( 343f'''\ 344{sp} {{ 345 uuid = "{uuid}"; 346 load-address = <{load_address}>; 347 owner = "{owner}"; 348}}; 349 350''') 351 else: 352 print("Warning: No load-address was found in the SP manifest.") 353 354 return args 355 356def init_sp_actions(sys): 357 # Initialize arguments for the SP actions framework 358 args = {} 359 args["sp_gen_mk"] = os.path.abspath(sys.argv[1]) 360 sp_layout_file = os.path.abspath(sys.argv[2]) 361 args["sp_layout_dir"] = os.path.dirname(sp_layout_file) 362 args["out_dir"] = os.path.abspath(sys.argv[3]) 363 args["dualroot"] = sys.argv[4] == "dualroot" 364 args["fconf_fragment"] = os.path.abspath(sys.argv[5]) 365 366 367 with open(sp_layout_file) as json_file: 368 sp_layout = json.load(json_file) 369 #Clear content of file "sp_gen.mk". 370 with open(args["sp_gen_mk"], "w"): 371 None 372 #Clear content of file "fconf_fragment". 373 with open(args["fconf_fragment"], "w"): 374 None 375 376 return args, sp_layout 377 378if __name__ == "__main__": 379 args, sp_layout = init_sp_actions(sys) 380 SpSetupActions.run_actions(sp_layout, args) 381