1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2023 Linaro Limited
3#
4# Entry-type module for producing a EFI capsule
5#
6
7import os
8
9from binman.entry import Entry
10from binman.etype.section import Entry_section
11from dtoc import fdt_util
12from u_boot_pylib import tools
13
14def get_binman_test_guid(type_str):
15    """Get the test image GUID for binman
16
17    Based on the string passed to the function, return
18    the corresponding GUID.
19
20    Args:
21        type_str: Key value of the type of GUID to look for
22
23    Returns:
24        The actual GUID value (str)
25    """
26    TYPE_TO_GUID = {
27        'binman-test' : '985f2937-7c2e-5e9a-8a5e-8e063312964b'
28    }
29
30    return TYPE_TO_GUID[type_str]
31
32class Entry_efi_capsule(Entry_section):
33    """Generate EFI capsules
34
35    The parameters needed for generation of the capsules can
36    be provided as properties in the entry.
37
38    Properties / Entry arguments:
39        - image-index: Unique number for identifying corresponding
40          payload image. Number between 1 and descriptor count, i.e.
41          the total number of firmware images that can be updated. Mandatory
42          property.
43        - image-guid: Image GUID which will be used for identifying the
44          updatable image on the board. Mandatory property.
45        - hardware-instance: Optional number for identifying unique
46          hardware instance of a device in the system. Default value of 0
47          for images where value is not to be used.
48        - fw-version: Value of image version that can be put on the capsule
49          through the Firmware Management Protocol(FMP) header.
50        - monotonic-count: Count used when signing an image.
51        - private-key: Path to PEM formatted .key private key file. Mandatory
52          property for generating signed capsules.
53        - public-key-cert: Path to PEM formatted .crt public key certificate
54          file. Mandatory property for generating signed capsules.
55        - oem-flags - OEM flags to be passed through capsule header.
56
57    Since this is a subclass of Entry_section, all properties of the parent
58    class also apply here. Except for the properties stated as mandatory, the
59    rest of the properties are optional.
60
61    For more details on the description of the capsule format, and the capsule
62    update functionality, refer Section 8.5 and Chapter 23 in the `UEFI
63    specification`_.
64
65    The capsule parameters like image index and image GUID are passed as
66    properties in the entry. The payload to be used in the capsule is to be
67    provided as a subnode of the capsule entry.
68
69    A typical capsule entry node would then look something like this::
70
71        capsule {
72            type = "efi-capsule";
73            image-index = <0x1>;
74            /* Image GUID for testing capsule update */
75            image-guid = SANDBOX_UBOOT_IMAGE_GUID;
76            hardware-instance = <0x0>;
77            private-key = "path/to/the/private/key";
78            public-key-cert = "path/to/the/public-key-cert";
79            oem-flags = <0x8000>;
80
81            u-boot {
82            };
83        };
84
85    In the above example, the capsule payload is the U-Boot image. The
86    capsule entry would read the contents of the payload and put them
87    into the capsule. Any external file can also be specified as the
88    payload using the blob-ext subnode.
89
90    .. _`UEFI specification`: https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf
91    """
92    def __init__(self, section, etype, node):
93        super().__init__(section, etype, node)
94        self.required_props = ['image-index', 'image-guid']
95        self.image_index = 0
96        self.image_guid = ''
97        self.hardware_instance = 0
98        self.monotonic_count = 0
99        self.fw_version = 0
100        self.oem_flags = 0
101        self.private_key = ''
102        self.public_key_cert = ''
103        self.auth = 0
104
105    def ReadNode(self):
106        super().ReadNode()
107
108        self.image_index = fdt_util.GetInt(self._node, 'image-index')
109        self.image_guid = fdt_util.GetString(self._node, 'image-guid')
110        self.fw_version = fdt_util.GetInt(self._node, 'fw-version')
111        self.hardware_instance = fdt_util.GetInt(self._node, 'hardware-instance')
112        self.monotonic_count = fdt_util.GetInt(self._node, 'monotonic-count')
113        self.oem_flags = fdt_util.GetInt(self._node, 'oem-flags')
114
115        self.private_key = fdt_util.GetString(self._node, 'private-key')
116        self.public_key_cert = fdt_util.GetString(self._node, 'public-key-cert')
117        if ((self.private_key and not self.public_key_cert) or (self.public_key_cert and not self.private_key)):
118            self.Raise('Both private key and public key certificate need to be provided')
119        elif not (self.private_key and self.public_key_cert):
120            self.auth = 0
121        else:
122            self.auth = 1
123
124    def BuildSectionData(self, required):
125        private_key = ''
126        public_key_cert = ''
127        if self.auth:
128            if not os.path.isabs(self.private_key):
129                private_key =  tools.get_input_filename(self.private_key)
130            if not os.path.isabs(self.public_key_cert):
131                public_key_cert = tools.get_input_filename(self.public_key_cert)
132        data, payload, uniq = self.collect_contents_to_file(
133            self._entries.values(), 'capsule_in')
134        outfile = self._filename if self._filename else 'capsule.%s' % uniq
135        capsule_fname = tools.get_output_filename(outfile)
136        guid = self.image_guid
137        if self.image_guid == "binman-test":
138            guid = get_binman_test_guid('binman-test')
139
140        ret = self.mkeficapsule.generate_capsule(self.image_index,
141                                                 guid,
142                                                 self.hardware_instance,
143                                                 payload,
144                                                 capsule_fname,
145                                                 private_key,
146                                                 public_key_cert,
147                                                 self.monotonic_count,
148                                                 self.fw_version,
149                                                 self.oem_flags)
150        if ret is not None:
151            return tools.read_file(capsule_fname)
152        else:
153            # Bintool is missing; just use the input data as the output
154            if not self.GetAllowMissing():
155                self.Raise("Missing tool: 'mkeficapsule'")
156            self.record_missing_bintool(self.mkeficapsule)
157            return data
158
159    def AddBintools(self, btools):
160        self.mkeficapsule = self.AddBintool(btools, 'mkeficapsule')
161