1#!/usr/bin/python3
2# Copyright (c) 2020-2022, 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
24
25Generated "sp_gen.mk" file contains triplet of following information for each
26Secure Partition entry
27    FDT_SOURCES +=  sp1.dts
28    SPTOOL_ARGS += -i sp1.bin:sp1.dtb -o sp1.pkg
29    FIP_ARGS += --blob uuid=XXXXX-XXX...,file=sp1.pkg
30    CRT_ARGS += --sp-pkg1 sp1.pkg
31
32A typical SP_LAYOUT_FILE file will look like
33{
34        "SP1" : {
35                "image": "sp1.bin",
36                "pm": "test/sp1.dts"
37        },
38
39        "SP2" : {
40                "image": "sp2.bin",
41                "pm": "test/sp2.dts",
42                "uuid": "1b1820fe-48f7-4175-8999-d51da00b7c9f"
43        }
44
45        ...
46}
47
48"""
49import json
50import os
51import re
52import sys
53import uuid
54from spactions import SpSetupActions
55
56MAX_SP = 8
57UUID_LEN = 4
58
59# Some helper functions to access args propagated to the action functions in
60# SpSetupActions framework.
61def check_sp_mk_gen(args :dict):
62    if "sp_gen_mk" not in args.keys():
63        raise Exception(f"Path to file sp_gen.mk needs to be in 'args'.")
64
65def check_out_dir(args :dict):
66    if "out_dir" not in args.keys() or not os.path.isdir(args["out_dir"]):
67        raise Exception("Define output folder with \'out_dir\' key.")
68
69def check_sp_layout_dir(args :dict):
70    if "sp_layout_dir" not in args.keys() or not os.path.isdir(args["sp_layout_dir"]):
71        raise Exception("Define output folder with \'sp_layout_dir\' key.")
72
73def write_to_sp_mk_gen(content, args :dict):
74    check_sp_mk_gen(args)
75    with open(args["sp_gen_mk"], "a") as f:
76        f.write(f"{content}\n")
77
78def get_sp_manifest_full_path(sp_node, args :dict):
79    check_sp_layout_dir(args)
80    return os.path.join(args["sp_layout_dir"], get_file_from_layout(sp_node["pm"]))
81
82def get_sp_img_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["image"]))
85
86def get_sp_pkg(sp, args :dict):
87    check_out_dir(args)
88    return os.path.join(args["out_dir"], f"{sp}.pkg")
89
90def is_line_in_sp_gen(line, args :dict):
91    with open(args["sp_gen_mk"], "r") as f:
92        sppkg_rule = [l for l in f if line in l]
93    return len(sppkg_rule) != 0
94
95def get_file_from_layout(node):
96    ''' Helper to fetch a file path from sp_layout.json. '''
97    if type(node) is dict and "file" in node.keys():
98        return node["file"]
99    return node
100
101def get_offset_from_layout(node):
102    ''' Helper to fetch an offset from sp_layout.json. '''
103    if type(node) is dict and "offset" in node.keys():
104        return int(node["offset"], 0)
105    return None
106
107def get_image_offset(node):
108    ''' Helper to fetch image offset from sp_layout.json '''
109    return get_offset_from_layout(node["image"])
110
111def get_pm_offset(node):
112    ''' Helper to fetch pm offset from sp_layout.json '''
113    return get_offset_from_layout(node["pm"])
114
115@SpSetupActions.sp_action(global_action=True)
116def check_max_sps(sp_layout, _, args :dict):
117    ''' Check validate the maximum number of SPs is respected. '''
118    if len(sp_layout.keys()) > MAX_SP:
119        raise Exception(f"Too many SPs in SP layout file. Max: {MAX_SP}")
120    return args
121
122@SpSetupActions.sp_action
123def gen_fdt_sources(sp_layout, sp, args :dict):
124    ''' Generate FDT_SOURCES values for a given SP. '''
125    manifest_path = get_sp_manifest_full_path(sp_layout[sp], args)
126    write_to_sp_mk_gen(f"FDT_SOURCES += {manifest_path}", args)
127    return args
128
129@SpSetupActions.sp_action
130def gen_sptool_args(sp_layout, sp, args :dict):
131    ''' Generate Sp Pkgs rules. '''
132    sp_pkg = get_sp_pkg(sp, args)
133    sp_dtb_name = os.path.basename(get_file_from_layout(sp_layout[sp]["pm"]))[:-1] + "b"
134    sp_dtb = os.path.join(args["out_dir"], f"fdts/{sp_dtb_name}")
135    sp_img = get_sp_img_full_path(sp_layout[sp], args)
136
137    # Do not generate rule if already there.
138    if is_line_in_sp_gen(f'{sp_pkg}:', args):
139        return args
140    write_to_sp_mk_gen(f"SP_PKGS += {sp_pkg}\n", args)
141
142    sptool_args = f" -i {sp_img}:{sp_dtb}"
143    pm_offset = get_pm_offset(sp_layout[sp])
144    sptool_args += f" --pm-offset {pm_offset}" if pm_offset is not None else ""
145    image_offset = get_image_offset(sp_layout[sp])
146    sptool_args += f" --img-offset {image_offset}" if image_offset is not None else ""
147    sptool_args += f" -o {sp_pkg}"
148    sppkg_rule = f'''
149{sp_pkg}: {sp_dtb} {sp_img}
150\t$(Q)echo Generating {sp_pkg}
151\t$(Q)$(PYTHON) $(SPTOOL) {sptool_args}
152'''
153    write_to_sp_mk_gen(sppkg_rule, args)
154    return args
155
156@SpSetupActions.sp_action(global_action=True, exec_order=1)
157def check_dualroot(sp_layout, _, args :dict):
158    ''' Validate the amount of SPs from SiP and Platform owners. '''
159    if not args.get("dualroot"):
160        return args
161    args["split"] =  int(MAX_SP / 2)
162    owners = [sp_layout[sp].get("owner") for sp in sp_layout]
163    args["plat_max_count"] = owners.count("Plat")
164    # If it is owned by the platform owner, it is assigned to the SiP.
165    args["sip_max_count"] = len(sp_layout.keys()) - args["plat_max_count"]
166    if  args["sip_max_count"] > args["split"] or args["sip_max_count"] > args["split"]:
167        print(f"WARN: SiP Secure Partitions should not be more than {args['split']}")
168    # Counters for gen_crt_args.
169    args["sip_count"] = 1
170    args["plat_count"] = 1
171    return args
172
173@SpSetupActions.sp_action
174def gen_crt_args(sp_layout, sp, args :dict):
175    ''' Append CRT_ARGS. '''
176    # If "dualroot" is configured, 'sp_pkg_idx' depends on whether the SP is owned
177    # by the "SiP" or the "Plat".
178    if args.get("dualroot"):
179        # If the owner is not specified as "Plat", default to "SiP".
180        if sp_layout[sp].get("owner") == "Plat":
181            if args["plat_count"] > args["plat_max_count"]:
182                raise ValueError("plat_count can't surpass plat_max_count in args.")
183            sp_pkg_idx = args["plat_count"] + args["split"]
184            args["plat_count"] += 1
185        else:
186            if args["sip_count"] > args["sip_max_count"]:
187                raise ValueError("sip_count can't surpass sip_max_count in args.")
188            sp_pkg_idx = args["sip_count"]
189            args["sip_count"] += 1
190    else:
191        sp_pkg_idx = [k for k in sp_layout.keys()].index(sp) + 1
192    write_to_sp_mk_gen(f"CRT_ARGS += --sp-pkg{sp_pkg_idx} {get_sp_pkg(sp, args)}\n", args)
193    return args
194
195@SpSetupActions.sp_action
196def gen_fiptool_args(sp_layout, sp, args :dict):
197    ''' Generate arguments for the FIP Tool. '''
198    if "uuid" in sp_layout[sp]:
199        # Extract the UUID from the JSON file if the SP entry has a 'uuid' field
200        uuid_std = uuid.UUID(sp_layout[sp]['uuid'])
201    else:
202        with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as pm_f:
203            uuid_lines = [l for l in pm_f if 'uuid' in l]
204        assert(len(uuid_lines) == 1)
205        # The uuid field in SP manifest is the little endian representation
206        # mapped to arguments as described in SMCCC section 5.3.
207        # Convert each unsigned integer value to a big endian representation
208        # required by fiptool.
209        uuid_parsed = re.findall("0x([0-9a-f]+)", uuid_lines[0])
210        y = list(map(bytearray.fromhex, uuid_parsed))
211        z = [int.from_bytes(i, byteorder='little', signed=False) for i in y]
212        uuid_std = uuid.UUID(f'{z[0]:08x}{z[1]:08x}{z[2]:08x}{z[3]:08x}')
213    write_to_sp_mk_gen(f"FIP_ARGS += --blob uuid={str(uuid_std)},file={get_sp_pkg(sp, args)}\n", args)
214    return args
215
216def init_sp_actions(sys):
217    sp_layout_file = os.path.abspath(sys.argv[2])
218    with open(sp_layout_file) as json_file:
219        sp_layout = json.load(json_file)
220    # Initialize arguments for the SP actions framework
221    args = {}
222    args["sp_gen_mk"] = os.path.abspath(sys.argv[1])
223    args["sp_layout_dir"] = os.path.dirname(sp_layout_file)
224    args["out_dir"] = os.path.abspath(sys.argv[3])
225    args["dualroot"] = sys.argv[4] == "dualroot"
226    #Clear content of file "sp_gen.mk".
227    with open(args["sp_gen_mk"], "w"):
228        None
229    return args, sp_layout
230
231if __name__ == "__main__":
232    args, sp_layout = init_sp_actions(sys)
233    SpSetupActions.run_actions(sp_layout, args)
234