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