@@ -249,3 +249,4 @@ CONFIG_UNIT_TEST=y
CONFIG_SPL_UNIT_TEST=y
CONFIG_UT_TIME=y
CONFIG_UT_DM=y
+CONFIG_TOOLS_MKEFICAPSULE=y
new file mode 100644
@@ -0,0 +1,153 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2023 Linaro Limited
+#
+"""Bintool implementation for mkeficapsule tool
+
+mkeficapsule is a tool used for generating EFI capsules.
+
+The following are the command-line options to be provided
+to the tool
+Usage: mkeficapsule [options] <image blob> <output file>
+Options:
+ -g, --guid <guid string> guid for image blob type
+ -i, --index <index> update image index
+ -I, --instance <instance> update hardware instance
+ -v, --fw-version <version> firmware version
+ -p, --private-key <privkey file> private key file
+ -c, --certificate <cert file> signer's certificate file
+ -m, --monotonic-count <count> monotonic count
+ -d, --dump_sig dump signature (*.p7)
+ -A, --fw-accept firmware accept capsule, requires GUID, no image blob
+ -R, --fw-revert firmware revert capsule, takes no GUID, no image blob
+ -o, --capoemflag Capsule OEM Flag, an integer between 0x0000 and 0xffff
+ -f, --cfg-file <config file> config file with capsule parameters
+ -h, --help print a help message
+"""
+
+from binman import bintool
+
+class Bintoolmkeficapsule(bintool.Bintool):
+ """Handles the 'mkeficapsule' tool
+
+ This bintool is used for generating the EFI capsules. The
+ capsule generation parameters can either be specified through
+ command-line, or through a config file.
+ """
+ def __init__(self, name):
+ super().__init__(name, 'mkeficapsule tool for generating capsules')
+
+ def capsule_cfg_file(self, cfg_file):
+ """Generate a capsule reading parameters from config file
+
+ Args:
+ cfg_file (str): Path to the config file
+
+ Returns:
+ str: Tool output
+ """
+ args = [
+ f'--cfg-file={cfg_file}'
+ ]
+ return self.run_cmd(*args)
+
+ def cmdline_capsule(self, image_index, image_guid, hardware_instance,
+ payload, output_fname, version=0):
+ """Generate a capsule through commandline provided parameters
+
+ Args:
+ image_index (int): Unique number for identifying payload image
+ image_guid (str): GUID used for identifying the image
+ hardware_instance (int): Optional unique hardware instance of
+ a device in the system. 0 if not being used
+ payload (str): Path to the input payload image
+ output_fname (str): Path to the output capsule file
+ version (int): Image version (Optional)
+
+ Returns:
+ str: Tool output
+ """
+ if version:
+ args = [
+ f'--index={image_index}',
+ f'--fw-version={version}',
+ f'--guid={image_guid}',
+ f'--instance={hardware_instance}',
+ payload,
+ output_fname
+ ]
+ else:
+ args = [
+ f'--index={image_index}',
+ f'--guid={image_guid}',
+ f'--instance={hardware_instance}',
+ payload,
+ output_fname
+ ]
+
+ return self.run_cmd(*args)
+
+ def cmdline_auth_capsule(self, image_index, image_guid, hardware_instance,
+ monotonic_count, priv_key, pub_key,
+ payload, output_fname, version=0):
+ """Generate a signed capsule through commandline provided parameters
+
+ Args:
+ image_index (int): Unique number for identifying payload image
+ image_guid (str): GUID used for identifying the image
+ hardware_instance (int): Optional unique hardware instance of
+ a device in the system. 0 if not being used
+ monotonic_count (int): Count used when signing an image
+ priv_key (str): Path to the private key
+ pub_key(str): Path to the public key
+ payload (str): Path to the input payload image
+ output_fname (str): Path to the output capsule file
+ version (int): Image version (Optional)
+
+ Returns:
+ str: Tool output
+ """
+ if version:
+ args = [
+ f'--index={image_index}',
+ f'--guid={image_guid}',
+ f'--instance={hardware_instance}',
+ f'--monotonic-count={monotonic_count}',
+ f'--private-key={priv_key}',
+ f'--certificate={pub_key}',
+ f'--fw-version={version}',
+ payload,
+ output_fname
+ ]
+ else:
+ args = [
+ f'--index={image_index}',
+ f'--guid={image_guid}',
+ f'--instance={hardware_instance}',
+ f'--monotonic-count={monotonic_count}',
+ f'--private-key={priv_key}',
+ f'--certificate={pub_key}',
+ payload,
+ output_fname
+ ]
+
+ return self.run_cmd(*args)
+
+ def fetch(self, method):
+ """Fetch handler for mkeficapsule
+
+ This builds the tool from source
+
+ Returns:
+ tuple:
+ str: Filename of fetched file to copy to a suitable directory
+ str: Name of temp directory to remove, or None
+ """
+ if method != bintool.FETCH_BUILD:
+ return None
+
+ cmd = ['tools-only_defconfig', 'tools']
+ result = self.build_from_git(
+ 'https://source.denx.de/u-boot/u-boot.git',
+ cmd,
+ 'tools/mkeficapsule')
+ return result
@@ -283,6 +283,48 @@ entry; similarly for SPL.
+.. _etype_capsule:
+
+Entry: capsule: Entry for generating EFI Capsule files
+------------------------------------------------------
+
+This is an entry for generating EFI capsules.
+
+The parameters needed for generation of the capsules can either be
+provided separately, or through a config file.
+
+Properties / Entry arguments:
+ - cfg-file: Config file for providing capsule
+ parameters. These are parameters needed for generating the
+ capsules. The parameters can be listed by running the
+ './tools/mkeficapsule -h' command.
+ - image-index: Unique number for identifying corresponding
+ payload image. Number between 1 and descriptor count, i.e.
+ the total number of firmware images that can be updated.
+ - image-type-id: Image GUID which will be used for identifying the
+ updatable image on the board.
+ - hardware-instance: Optional number for identifying unique
+ hardware instance of a device in the system. Default value of 0
+ for images where value is not to be used.
+ - fw-version: Optional value of image version that can be put on
+ the capsule through the Firmware Management Protocol(FMP) header.
+ - monotonic-count: Count used when signing an image.
+ - private-key: Path to PEM formatted .key private key file.
+ - pub-key-cert: Path to PEM formatted .crt public key certificate
+ file.
+ - filename: Path to the input(payload) file. File can be any
+ format, a binary or an elf, platform specific.
+ - capsule: Path to the output capsule file. A capsule is a
+ continuous set of data as defined by the EFI specification. Refer
+ to the specification for more details.
+
+For more details on the description of the capsule format, and the capsule
+update functionality, refer Section 8.5 and Chapter 23 in the UEFI
+specification.
+https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf
+
+
+
.. _etype_cbfs:
Entry: cbfs: Coreboot Filesystem (CBFS)
new file mode 100644
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2023 Linaro Limited
+#
+# Entry-type module for producing a capsule
+#
+
+import os
+
+from binman.entry import Entry
+from dtoc import fdt_util
+from u_boot_pylib import tools
+
+class Entry_capsule(Entry):
+ """Entry for generating EFI capsules
+
+ This is an entry for generating EFI capsules.
+
+ The parameters needed for generation of the capsules can
+ either be provided separately, or through a config file.
+
+ Properties / Entry arguments:
+ - cfg-file: Config file for providing capsule
+ parameters. These are parameters needed for generating the
+ capsules. The parameters can be listed by running the
+ './tools/mkeficapsule -h' command.
+ - image-index: Unique number for identifying corresponding
+ payload image. Number between 1 and descriptor count, i.e.
+ the total number of firmware images that can be updated.
+ - image-type-id: Image GUID which will be used for identifying the
+ updatable image on the board.
+ - hardware-instance: Optional number for identifying unique
+ hardware instance of a device in the system. Default value of 0
+ for images where value is not to be used.
+ - fw-version: Optional value of image version that can be put on
+ the capsule through the Firmware Management Protocol(FMP) header.
+ - monotonic-count: Count used when signing an image.
+ - private-key: Path to PEM formatted .key private key file.
+ - pub-key-cert: Path to PEM formatted .crt public key certificate
+ file.
+ - filename: Path to the input(payload) file. File can be any
+ format, a binary or an elf, platform specific.
+ - capsule: Path to the output capsule file. A capsule is a
+ continuous set of data as defined by the EFI specification. Refer
+ to the specification for more details.
+
+ For more details on the description of the capsule format, and the capsule
+ update functionality, refer Section 8.5 and Chapter 23 in the UEFI
+ specification.
+ https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf
+ """
+ def __init__(self, section, etype, node):
+ super().__init__(section, etype, node)
+ self.image_index = 0
+ self.image_guid = ''
+ self.hardware_instance = 0
+ self.monotonic_count = 0
+ self.fw_version = 0
+ self.private_key = ''
+ self.pub_key_cert = ''
+ self.auth = 0
+ self.payload = ''
+ self.capsule_fname = ''
+
+ def ReadNode(self):
+ super().ReadNode()
+
+ self.cfg_file = fdt_util.GetString(self._node, 'cfg-file')
+ if self.cfg_file:
+ if not os.path.isabs(self.cfg_file):
+ self.cfg_file = tools.get_input_filename(self.cfg_file)
+ else:
+ self.image_index = fdt_util.GetInt(self._node, 'image-index')
+ if not self.image_index:
+ self.Raise('mkeficapsule must be provided an Image Index')
+
+ self.image_guid = fdt_util.GetString(self._node, 'image-type-id')
+ if not self.image_guid:
+ self.Raise('mkeficapsule must be provided an Image GUID')
+
+ self.fw_version = fdt_util.GetInt(self._node, 'fw-version')
+ self.hardware_instance = fdt_util.GetInt(self._node, 'hardware-instance')
+ self.monotonic_count = fdt_util.GetInt(self._node, 'monotonic-count')
+
+ self.private_key = fdt_util.GetString(self._node, 'private-key')
+ self.pub_key_cert = fdt_util.GetString(self._node, 'pub-key-cert')
+
+ if ((self.private_key and not self.pub_key_cert) or (self.pub_key_cert and not self.private_key)):
+ self.Raise('Both private key and public key certificate need to be provided')
+ elif not (self.private_key and self.pub_key_cert):
+ self.auth = 0
+ else:
+ self.auth = 1
+
+ self.payload = fdt_util.GetString(self._node, 'filename')
+ if not self.payload:
+ self.Raise('mkeficapsule must be provided an input filename(payload)')
+ if not os.path.isabs(self.payload):
+ self.payload = tools.get_input_filename(self.payload)
+
+ self.capsule_fname = fdt_util.GetString(self._node, 'capsule')
+ if not self.capsule_fname:
+ self.Raise('Specify the output capsule file')
+ if not os.path.isabs(self.capsule_fname):
+ self.capsule_fname = tools.get_output_filename(self.capsule_fname)
+
+ def _GenCapsule(self):
+ if self.cfg_file:
+ return self.mkeficapsule.capsule_cfg_file(self.cfg_file)
+ elif self.auth:
+ return self.mkeficapsule.cmdline_auth_capsule(self.image_index,
+ self.image_guid,
+ self.hardware_instance,
+ self.monotonic_count,
+ self.private_key,
+ self.pub_key_cert,
+ self.payload,
+ self.capsule_fname,
+ self.fw_version)
+ else:
+ return self.mkeficapsule.cmdline_capsule(self.image_index,
+ self.image_guid,
+ self.hardware_instance,
+ self.payload,
+ self.capsule_fname,
+ self.fw_version)
+
+ def ObtainContents(self):
+ self.SetContents(tools.to_bytes(self._GenCapsule()))
+ return True
+
+ def AddBintools(self, btools):
+ self.mkeficapsule = self.AddBintool(btools, 'mkeficapsule')
@@ -48,6 +48,7 @@ U_BOOT_VPL_DATA = b'vpl76543210fedcbazywxyz_'
BLOB_DATA = b'89'
ME_DATA = b'0abcd'
VGA_DATA = b'vga'
+EFI_DATA = b'efi'
U_BOOT_DTB_DATA = b'udtb'
U_BOOT_SPL_DTB_DATA = b'spldtb'
U_BOOT_TPL_DTB_DATA = b'tpldtb'
@@ -119,6 +120,11 @@ COMP_BINTOOLS = ['bzip2', 'gzip', 'lz4', 'lzma_alone', 'lzop', 'xz', 'zstd']
TEE_ADDR = 0x5678
+# Firmware Management Protocol(FMP) GUID
+FW_MGMT_GUID = 'edd5cb6d2de8444cbda17194199ad92a'
+# Image GUID specified in the DTS
+CAPSULE_IMAGE_GUID = '52cfd7092007104791d108469b7fe9c8'
+
class TestFunctional(unittest.TestCase):
"""Functional tests for binman
@@ -7087,5 +7093,114 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
self.assertEqual(fdt_util.GetString(key_node, "key-name-hint"),
"key")
+ def _CheckCapsule(self, signed_capsule=False, version_check=False):
+ fmp_signature = "4d535331" # 'M', 'S', 'S', '1'
+ fmp_size = "10"
+ fmp_fw_version = "02"
+
+ self.capsule_data = tools.read_file(self.capsule_fname)
+
+ # Firmware Management Protocol(FMP) GUID - offset(0 - 32)
+ self.assertEqual(FW_MGMT_GUID, self.capsule_data.hex()[:32])
+ # Image GUID - offset(96 - 128)
+ self.assertEqual(CAPSULE_IMAGE_GUID, self.capsule_data.hex()[96:128])
+
+ if version_check:
+ # FMP header signature - offset(184 - 192)
+ self.assertEqual(fmp_signature, self.capsule_data.hex()[184:192])
+ # FMP header size - offset(192 - 194)
+ self.assertEqual(fmp_size, self.capsule_data.hex()[192:194])
+ # firmware version - offset(200 - 202)
+ self.assertEqual(fmp_fw_version, self.capsule_data.hex()[200:202])
+
+ if signed_capsule:
+ # payload offset signed capsule(4770 - 4776)
+ self.assertEqual(self.payload_data.hex(), self.capsule_data.hex()[4770:4776])
+ elif version_check:
+ # payload offset for non-signed capsule with version header(216 - 222)
+ self.assertEqual(self.payload_data.hex(), self.capsule_data.hex()[216:222])
+ else:
+ # payload offset for non-signed capsule with no version header(184 - 190)
+ self.assertEqual(self.payload_data.hex(), self.capsule_data.hex()[184:190])
+
+ def testCapsuleGen(self):
+ """Test generation of EFI capsule"""
+ self.payload_data = EFI_DATA
+
+ TestFunctional._MakeInputFile('payload.txt', self.payload_data)
+
+ self._DoReadFile('307_capsule.dts')
+
+ self.capsule_fname = tools.get_output_filename('test.capsule')
+ self.assertTrue(os.path.exists(self.capsule_fname))
+
+ self._CheckCapsule()
+
+ def testSignedCapsuleGen(self):
+ """Test generation of EFI capsule"""
+ self.payload_data = EFI_DATA
+
+ TestFunctional._MakeInputFile('payload.txt', self.payload_data)
+
+ self._DoReadFile('308_capsule_signed.dts')
+
+ self.capsule_fname = tools.get_output_filename('test.capsule')
+ self.assertTrue(os.path.exists(self.capsule_fname))
+
+ self._CheckCapsule(signed_capsule=True)
+
+ def testCapsuleGenVersionSupport(self):
+ """Test generation of EFI capsule with version support"""
+ self.payload_data = EFI_DATA
+
+ TestFunctional._MakeInputFile('payload.txt', self.payload_data)
+
+ self._DoReadFile('309_capsule_version.dts')
+
+ self.capsule_fname = tools.get_output_filename('test.capsule')
+ self.assertTrue(os.path.exists(self.capsule_fname))
+
+ self._CheckCapsule(version_check=True)
+
+ def testCapsuleGenKeyMissing(self):
+ """Test that binman errors out on missing key"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('310_capsule_missing_key.dts')
+
+ self.assertIn("Both private key and public key certificate need to be provided",
+ str(e.exception))
+
+ def testCapsuleGenIndexMissing(self):
+ """Test that binman errors out on missing image index"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('311_capsule_missing_index.dts')
+
+ self.assertIn("mkeficapsule must be provided an Image Index",
+ str(e.exception))
+
+ def testCapsuleGenGuidMissing(self):
+ """Test that binman errors out on missing image GUID"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('312_capsule_missing_guid.dts')
+
+ self.assertIn("mkeficapsule must be provided an Image GUID",
+ str(e.exception))
+
+ def testCapsuleGenPayloadMissing(self):
+ """Test that binman errors out on missing input(payload)image"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('313_capsule_missing_payload.dts')
+
+ self.assertIn("mkeficapsule must be provided an input filename(payload)",
+ str(e.exception))
+
+ def testCapsuleGenCapsuleFileMissing(self):
+ """Test that binman errors out on missing output capsule file"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('314_capsule_missing.dts')
+
+ self.assertIn("Specify the output capsule file",
+ str(e.exception))
+
if __name__ == "__main__":
unittest.main()
new file mode 100644
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ image-index = <0x1>;
+ /* Image GUID for testing capsule update */
+ image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ hardware-instance = <0x0>;
+ filename = "payload.txt";
+ capsule = "test.capsule";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ image-index = <0x1>;
+ /* Image GUID for testing capsule update */
+ image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ hardware-instance = <0x0>;
+ private-key = "tools/binman/test/key.key";
+ pub-key-cert = "tools/binman/test/key.pem";
+ filename = "payload.txt";
+ capsule = "test.capsule";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ image-index = <0x1>;
+ fw-version = <0x2>;
+ /* Image GUID for testing capsule update */
+ image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ hardware-instance = <0x0>;
+ filename = "payload.txt";
+ capsule = "test.capsule";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ image-index = <0x1>;
+ /* Image GUID for testing capsule update */
+ image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ hardware-instance = <0x0>;
+ private-key = "tools/binman/test/key.key";
+ filename = "payload.txt";
+ capsule = "test.capsule";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ /* Image GUID for testing capsule update */
+ image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ hardware-instance = <0x0>;
+ filename = "payload.txt";
+ capsule = "test.capsule";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ image-index = <0x1>;
+ hardware-instance = <0x0>;
+ filename = "payload.txt";
+ capsule = "test.capsule";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ image-index = <0x1>;
+ /* Image GUID for testing capsule update */
+ image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ hardware-instance = <0x0>;
+ capsule = "test.capsule";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ capsule {
+ image-index = <0x1>;
+ /* Image GUID for testing capsule update */
+ image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ hardware-instance = <0x0>;
+ filename = "payload.txt";
+ };
+ };
+};
new file mode 100644
@@ -0,0 +1,6 @@
+{
+ image-index: 1
+ image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8
+ payload: /tmp/capsules/payload.txt
+ capsule: /tmp/capsules/test.capsule
+}
Add support in binman for generating capsules. The capsule parameters can be specified either through a config file or through the capsule binman entry. Also add test cases in binman for capsule generation, and enable this testing on the sandbox_spl variant. Signed-off-by: Sughosh Ganu <sughosh.ganu@linaro.org> --- Changes since V4: * Rebase on top of current HEAD. * Remove blank lines after function comments. * Fix a couple of typos. * Use single quotes for strings. * Put the GUIDs in variables with relevant names. * Declare certain values in local variables instead of member values. * Add comments for explaning the payload offsets in the capsule file. * Drop the test case for generating the capsule from the config file. * Define payload data for the capsule tests. * Add logic to find input and output files in capsule generation in the indir and outdir directories when absolute path is not passed. configs/sandbox_spl_defconfig | 1 + tools/binman/btool/mkeficapsule.py | 153 ++++++++++++++++++ tools/binman/entries.rst | 42 +++++ tools/binman/etype/capsule.py | 132 +++++++++++++++ tools/binman/ftest.py | 115 +++++++++++++ tools/binman/test/307_capsule.dts | 19 +++ tools/binman/test/308_capsule_signed.dts | 21 +++ tools/binman/test/309_capsule_version.dts | 20 +++ tools/binman/test/310_capsule_missing_key.dts | 20 +++ .../binman/test/311_capsule_missing_index.dts | 18 +++ .../binman/test/312_capsule_missing_guid.dts | 17 ++ .../test/313_capsule_missing_payload.dts | 18 +++ tools/binman/test/314_capsule_missing.dts | 18 +++ tools/binman/test/files/capsule_cfg.txt | 6 + 14 files changed, 600 insertions(+) create mode 100644 tools/binman/btool/mkeficapsule.py create mode 100644 tools/binman/etype/capsule.py create mode 100644 tools/binman/test/307_capsule.dts create mode 100644 tools/binman/test/308_capsule_signed.dts create mode 100644 tools/binman/test/309_capsule_version.dts create mode 100644 tools/binman/test/310_capsule_missing_key.dts create mode 100644 tools/binman/test/311_capsule_missing_index.dts create mode 100644 tools/binman/test/312_capsule_missing_guid.dts create mode 100644 tools/binman/test/313_capsule_missing_payload.dts create mode 100644 tools/binman/test/314_capsule_missing.dts create mode 100644 tools/binman/test/files/capsule_cfg.txt