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_sp_pkg(sp, args :dict):
88    check_out_dir(args)
89    return os.path.join(args["out_dir"], f"{sp}.pkg")
90
91def is_line_in_sp_gen(line, args :dict):
92    with open(args["sp_gen_mk"], "r") as f:
93        sppkg_rule = [l for l in f if line in l]
94    return len(sppkg_rule) != 0
95
96def get_file_from_layout(node):
97    ''' Helper to fetch a file path from sp_layout.json. '''
98    if type(node) is dict and "file" in node.keys():
99        return node["file"]
100    return node
101
102def get_offset_from_layout(node):
103    ''' Helper to fetch an offset from sp_layout.json. '''
104    if type(node) is dict and "offset" in node.keys():
105        return int(node["offset"], 0)
106    return None
107
108def get_image_offset(node):
109    ''' Helper to fetch image offset from sp_layout.json '''
110    return get_offset_from_layout(node["image"])
111
112def get_pm_offset(node):
113    ''' Helper to fetch pm offset from sp_layout.json '''
114    return get_offset_from_layout(node["pm"])
115
116def get_uuid(sp_layout, sp, args :dict):
117    ''' Helper to fetch uuid from pm file listed in sp_layout.json'''
118    if "uuid" in sp_layout[sp]:
119        # Extract the UUID from the JSON file if the SP entry has a 'uuid' field
120        uuid_std = uuid.UUID(sp_layout[sp]['uuid'])
121    else:
122        with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f:
123            uuid_lines = [l for l in pm_f if 'uuid' in l]
124        assert(len(uuid_lines) == 1)
125        # The uuid field in SP manifest is the little endian representation
126        # mapped to arguments as described in SMCCC section 5.3.
127        # Convert each unsigned integer value to a big endian representation
128        # required by fiptool.
129        uuid_parsed = re.findall("0x([0-9a-f]+)", uuid_lines[0])
130        y = list(map(bytearray.fromhex, uuid_parsed))
131        z = [int.from_bytes(i, byteorder='little', signed=False) for i in y]
132        uuid_std = uuid.UUID(f'{z[0]:08x}{z[1]:08x}{z[2]:08x}{z[3]:08x}')
133    return uuid_std
134
135def get_load_address(sp_layout, sp, args :dict):
136    ''' Helper to fetch load-address from pm file listed in sp_layout.json'''
137    with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f:
138        load_address_lines = [l for l in pm_f if 'load-address' in l]
139
140    if len(load_address_lines) != 1:
141        return None
142
143    load_address_parsed = re.search("(0x[0-9a-f]+)", load_address_lines[0])
144    return load_address_parsed.group(0)
145
146
147@SpSetupActions.sp_action(global_action=True)
148def check_max_sps(sp_layout, _, args :dict):
149    ''' Check validate the maximum number of SPs is respected. '''
150    if len(sp_layout.keys()) > MAX_SP:
151        raise Exception(f"Too many SPs in SP layout file. Max: {MAX_SP}")
152    return args
153
154@SpSetupActions.sp_action
155def gen_fdt_sources(sp_layout, sp, args :dict):
156    ''' Generate FDT_SOURCES values for a given SP. '''
157    manifest_path = get_sp_manifest_full_path(sp_layout[sp], args)
158    write_to_sp_mk_gen(f"FDT_SOURCES += {manifest_path}", args)
159    return args
160
161@SpSetupActions.sp_action
162def gen_sptool_args(sp_layout, sp, args :dict):
163    ''' Generate Sp Pkgs rules. '''
164    sp_pkg = get_sp_pkg(sp, args)
165    sp_dtb_name = os.path.basename(get_file_from_layout(sp_layout[sp]["pm"]))[:-1] + "b"
166    sp_dtb = os.path.join(args["out_dir"], f"fdts/{sp_dtb_name}")
167    sp_img = get_sp_img_full_path(sp_layout[sp], args)
168
169    # Do not generate rule if already there.
170    if is_line_in_sp_gen(f'{sp_pkg}:', args):
171        return args
172    write_to_sp_mk_gen(f"SP_PKGS += {sp_pkg}\n", args)
173
174    sptool_args = f" -i {sp_img}:{sp_dtb}"
175    pm_offset = get_pm_offset(sp_layout[sp])
176    sptool_args += f" --pm-offset {pm_offset}" if pm_offset is not None else ""
177    image_offset = get_image_offset(sp_layout[sp])
178    sptool_args += f" --img-offset {image_offset}" if image_offset is not None else ""
179    sptool_args += f" -o {sp_pkg}"
180    sppkg_rule = f'''
181{sp_pkg}: {sp_dtb} {sp_img}
182\t$(Q)echo Generating {sp_pkg}
183\t$(Q)$(PYTHON) $(SPTOOL) {sptool_args}
184'''
185    write_to_sp_mk_gen(sppkg_rule, args)
186    return args
187
188@SpSetupActions.sp_action(global_action=True, exec_order=1)
189def check_dualroot(sp_layout, _, args :dict):
190    ''' Validate the amount of SPs from SiP and Platform owners. '''
191    if not args.get("dualroot"):
192        return args
193    args["split"] =  int(MAX_SP / 2)
194    owners = [sp_layout[sp].get("owner") for sp in sp_layout]
195    args["plat_max_count"] = owners.count("Plat")
196    # If it is owned by the platform owner, it is assigned to the SiP.
197    args["sip_max_count"] = len(sp_layout.keys()) - args["plat_max_count"]
198    if  args["sip_max_count"] > args["split"] or args["sip_max_count"] > args["split"]:
199        print(f"WARN: SiP Secure Partitions should not be more than {args['split']}")
200    # Counters for gen_crt_args.
201    args["sip_count"] = 1
202    args["plat_count"] = 1
203    return args
204
205@SpSetupActions.sp_action
206def gen_crt_args(sp_layout, sp, args :dict):
207    ''' Append CRT_ARGS. '''
208    # If "dualroot" is configured, 'sp_pkg_idx' depends on whether the SP is owned
209    # by the "SiP" or the "Plat".
210    if args.get("dualroot"):
211        # If the owner is not specified as "Plat", default to "SiP".
212        if sp_layout[sp].get("owner") == "Plat":
213            if args["plat_count"] > args["plat_max_count"]:
214                raise ValueError("plat_count can't surpass plat_max_count in args.")
215            sp_pkg_idx = args["plat_count"] + args["split"]
216            args["plat_count"] += 1
217        else:
218            if args["sip_count"] > args["sip_max_count"]:
219                raise ValueError("sip_count can't surpass sip_max_count in args.")
220            sp_pkg_idx = args["sip_count"]
221            args["sip_count"] += 1
222    else:
223        sp_pkg_idx = [k for k in sp_layout.keys()].index(sp) + 1
224    write_to_sp_mk_gen(f"CRT_ARGS += --sp-pkg{sp_pkg_idx} {get_sp_pkg(sp, args)}\n", args)
225    return args
226
227@SpSetupActions.sp_action
228def gen_fiptool_args(sp_layout, sp, args :dict):
229    ''' Generate arguments for the FIP Tool. '''
230    uuid_std = get_uuid(sp_layout, sp, args)
231    write_to_sp_mk_gen(f"FIP_ARGS += --blob uuid={str(uuid_std)},file={get_sp_pkg(sp, args)}\n", args)
232    return args
233
234@SpSetupActions.sp_action
235def gen_fconf_fragment(sp_layout, sp, args: dict):
236    ''' Generate the fconf fragment file'''
237    with open(args["fconf_fragment"], "a") as f:
238        uuid = get_uuid(sp_layout, sp, args)
239        owner = "Plat" if sp_layout[sp].get("owner") == "Plat" else "SiP"
240
241        if "physical-load-address" in sp_layout[sp].keys():
242            load_address = sp_layout[sp]["physical-load-address"]
243        else:
244            load_address = get_load_address(sp_layout, sp, args)
245
246        if load_address is not None:
247            f.write(
248f'''\
249{sp} {{
250    uuid = "{uuid}";
251    load-address = <{load_address}>;
252    owner = "{owner}";
253}};
254
255''')
256        else:
257            print("Warning: No load-address was found in the SP manifest.")
258
259    return args
260
261def init_sp_actions(sys):
262    # Initialize arguments for the SP actions framework
263    args = {}
264    args["sp_gen_mk"] = os.path.abspath(sys.argv[1])
265    sp_layout_file = os.path.abspath(sys.argv[2])
266    args["sp_layout_dir"] = os.path.dirname(sp_layout_file)
267    args["out_dir"] = os.path.abspath(sys.argv[3])
268    args["dualroot"] = sys.argv[4] == "dualroot"
269    args["fconf_fragment"] = os.path.abspath(sys.argv[5])
270
271
272    with open(sp_layout_file) as json_file:
273        sp_layout = json.load(json_file)
274    #Clear content of file "sp_gen.mk".
275    with open(args["sp_gen_mk"], "w"):
276        None
277    #Clear content of file "fconf_fragment".
278    with open(args["fconf_fragment"], "w"):
279        None
280
281    return args, sp_layout
282
283if __name__ == "__main__":
284    args, sp_layout = init_sp_actions(sys)
285    SpSetupActions.run_actions(sp_layout, args)
286