1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2023 Weidmueller GmbH 3# Written by Lukas Funke <lukas.funke@weidmueller.com> 4# 5# Entry-type module for Zynq(MP) boot images (boot.bin) 6# 7 8import tempfile 9 10from collections import OrderedDict 11 12from binman import elf 13from binman.etype.section import Entry_section 14 15from dtoc import fdt_util 16 17from u_boot_pylib import tools 18from u_boot_pylib import command 19 20# pylint: disable=C0103 21class Entry_xilinx_bootgen(Entry_section): 22 """Signed SPL boot image for Xilinx ZynqMP devices 23 24 Properties / Entry arguments: 25 - auth-params: (Optional) Authentication parameters passed to bootgen 26 - fsbl-config: (Optional) FSBL parameters passed to bootgen 27 - keysrc-enc: (Optional) Key source when using decryption engine 28 - pmufw-filename: Filename of PMU firmware. Default: pmu-firmware.elf 29 - psk-key-name-hint: Name of primary secret key to use for signing the 30 secondardy public key. Format: .pem file 31 - ssk-key-name-hint: Name of secondardy secret key to use for signing 32 the boot image. Format: .pem file 33 34 The etype is used to create a boot image for Xilinx ZynqMP 35 devices. 36 37 Information for signed images: 38 39 In AMD/Xilinx SoCs, two pairs of public and secret keys are used 40 - primary and secondary. The function of the primary public/secret key pair 41 is to authenticate the secondary public/secret key pair. 42 The function of the secondary key is to sign/verify the boot image. [1] 43 44 AMD/Xilinx uses the following terms for private/public keys [1]: 45 46 PSK = Primary Secret Key (Used to sign Secondary Public Key) 47 PPK = Primary Public Key (Used to verify Secondary Public Key) 48 SSK = Secondary Secret Key (Used to sign the boot image/partitions) 49 SPK = Used to verify the actual boot image 50 51 The following example builds a signed boot image. The fuses of 52 the primary public key (ppk) should be fused together with the RSA_EN flag. 53 54 Example node:: 55 56 spl { 57 filename = "boot.signed.bin"; 58 59 xilinx-bootgen { 60 psk-key-name-hint = "psk0"; 61 ssk-key-name-hint = "ssk0"; 62 auth-params = "ppk_select=0", "spk_id=0x00000000"; 63 64 u-boot-spl-nodtb { 65 }; 66 u-boot-spl-pubkey-dtb { 67 algo = "sha384,rsa4096"; 68 required = "conf"; 69 key-name-hint = "dev"; 70 }; 71 }; 72 }; 73 74 For testing purposes, e.g. if no RSA_EN should be fused, one could add 75 the "bh_auth_enable" flag in the fsbl-config field. This will skip the 76 verification of the ppk fuses and boot the image, even if ppk hash is 77 invalid. 78 79 Example node:: 80 81 xilinx-bootgen { 82 psk-key-name-hint = "psk0"; 83 psk-key-name-hint = "ssk0"; 84 ... 85 fsbl-config = "bh_auth_enable"; 86 ... 87 }; 88 89 [1] https://docs.xilinx.com/r/en-US/ug1283-bootgen-user-guide/Using-Authentication 90 91 """ 92 def __init__(self, section, etype, node): 93 super().__init__(section, etype, node) 94 self._auth_params = None 95 self._entries = OrderedDict() 96 self._filename = None 97 self._fsbl_config = None 98 self._keysrc_enc = None 99 self._pmufw_filename = None 100 self._psk_key_name_hint = None 101 self._ssk_key_name_hint = None 102 self.align_default = None 103 self.bootgen = None 104 self.required_props = ['pmufw-filename', 105 'psk-key-name-hint', 106 'ssk-key-name-hint'] 107 108 def ReadNode(self): 109 """Read properties from the xilinx-bootgen node""" 110 super().ReadNode() 111 self._auth_params = fdt_util.GetStringList(self._node, 112 'auth-params') 113 self._filename = fdt_util.GetString(self._node, 'filename') 114 self._fsbl_config = fdt_util.GetStringList(self._node, 115 'fsbl-config') 116 self._keysrc_enc = fdt_util.GetString(self._node, 117 'keysrc-enc') 118 self._pmufw_filename = fdt_util.GetString(self._node, 'pmufw-filename') 119 self._psk_key_name_hint = fdt_util.GetString(self._node, 120 'psk-key-name-hint') 121 self._ssk_key_name_hint = fdt_util.GetString(self._node, 122 'ssk-key-name-hint') 123 self.ReadEntries() 124 125 @classmethod 126 def _ToElf(cls, data, output_fname): 127 """Convert SPL object file to bootable ELF file 128 129 Args: 130 data (bytearray): u-boot-spl-nodtb + u-boot-spl-pubkey-dtb obj file 131 data 132 output_fname (str): Filename of converted FSBL ELF file 133 """ 134 platform_elfflags = {"aarch64": 135 ["-B", "aarch64", "-O", "elf64-littleaarch64"], 136 # amd64 support makes no sense for the target 137 # platform, but we include it here to enable 138 # testing on hosts 139 "x86_64": 140 ["-B", "i386", "-O", "elf64-x86-64"] 141 } 142 143 gcc, args = tools.get_target_compile_tool('cc') 144 args += ['-dumpmachine'] 145 stdout = command.output(gcc, *args) 146 # split target machine triplet (arch, vendor, os) 147 arch, _, _ = stdout.split('-') 148 149 spl_elf = elf.DecodeElf(tools.read_file( 150 tools.get_input_filename('spl/u-boot-spl')), 0) 151 152 # Obj file to swap data and text section (rename-section) 153 with tempfile.NamedTemporaryFile(prefix="u-boot-spl-pubkey-", 154 suffix=".o.tmp", 155 dir=tools.get_output_dir())\ 156 as tmp_obj: 157 input_objcopy_fname = tmp_obj.name 158 # Align packed content to 4 byte boundary 159 pad = bytearray(tools.align(len(data), 4) - len(data)) 160 tools.write_file(input_objcopy_fname, data + pad) 161 # Final output elf file which contains a valid start address 162 with tempfile.NamedTemporaryFile(prefix="u-boot-spl-pubkey-elf-", 163 suffix=".o.tmp", 164 dir=tools.get_output_dir())\ 165 as tmp_elf_obj: 166 input_ld_fname = tmp_elf_obj.name 167 objcopy, args = tools.get_target_compile_tool('objcopy') 168 args += ["--rename-section", ".data=.text", 169 "-I", "binary"] 170 args += platform_elfflags[arch] 171 args += [input_objcopy_fname, input_ld_fname] 172 command.run(objcopy, *args) 173 174 ld, args = tools.get_target_compile_tool('ld') 175 args += [input_ld_fname, '-o', output_fname, 176 "--defsym", f"_start={hex(spl_elf.entry)}", 177 "-Ttext", hex(spl_elf.entry)] 178 command.run(ld, *args) 179 180 def BuildSectionData(self, required): 181 """Pack node content, and create bootable, signed ZynqMP boot image 182 183 The method collects the content of this node (usually SPL + dtb) and 184 converts them to an ELF file. The ELF file is passed to the 185 Xilinx bootgen tool which packs the SPL ELF file together with 186 Platform Management Unit (PMU) firmware into a bootable image 187 for ZynqMP devices. The image is signed within this step. 188 189 The result is a bootable, signed SPL image for Xilinx ZynqMP devices. 190 """ 191 data = super().BuildSectionData(required) 192 bootbin_fname = self._filename if self._filename else \ 193 tools.get_output_filename( 194 f'boot.{self.GetUniqueName()}.bin') 195 196 pmufw_elf_fname = tools.get_input_filename(self._pmufw_filename) 197 psk_fname = tools.get_input_filename(self._psk_key_name_hint + ".pem") 198 ssk_fname = tools.get_input_filename(self._ssk_key_name_hint + ".pem") 199 fsbl_config = ";".join(self._fsbl_config) if self._fsbl_config else None 200 auth_params = ";".join(self._auth_params) if self._auth_params else None 201 202 spl_elf_fname = tools.get_output_filename('u-boot-spl-pubkey.dtb.elf') 203 204 # We need to convert to node content (see above) into an ELF 205 # file in order to be processed by bootgen. 206 self._ToElf(bytearray(data), spl_elf_fname) 207 208 # Call Bootgen in order to sign the SPL 209 if self.bootgen.sign('zynqmp', spl_elf_fname, pmufw_elf_fname, 210 psk_fname, ssk_fname, fsbl_config, 211 auth_params, self._keysrc_enc, bootbin_fname) is None: 212 # Bintool is missing; just use empty data as the output 213 self.record_missing_bintool(self.bootgen) 214 data = tools.get_bytes(0, 1024) 215 else: 216 data = tools.read_file(bootbin_fname) 217 218 self.SetContents(data) 219 220 return data 221 222 # pylint: disable=C0116 223 def AddBintools(self, btools): 224 super().AddBintools(btools) 225 self.bootgen = self.AddBintool(btools, 'bootgen') 226