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