sp_mk_generator.py 10 KB

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