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