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