xref: /rk3399_ARM-atf/tools/sptool/sp_mk_generator.py (revision 0fe374ef046f70b6e990024922034d14d418b109)
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