mirror of
https://git.yoctoproject.org/meta-arm
synced 2026-06-05 14:30:10 +00:00
arm/uefi_capsule: Switch Capsule generation tool from U-Boot to EDK2
This commit updates the uefi_capsule.bbclass to use the EDK2
GenerateCapsule tool instead of the mkeficapsule utility from U-Boot.
The switch was necessary because the mkeficapsule utility from U-Boot
does not support generating capsules with multiple payloads, whereas
the EDK2 GenerateCapsule tool provides native support for multi-image
capsule creation.
These changes allow building UEFI capsules with multiple firmware
binaries in one step, making the firmware update process
more flexible.
- Switching dependency from u-boot-tools-native to
edk2-basetools-native
- Updating the actual capsule creation command to GenerateCapsule
with the appropriate flags (including hardware instance, lowest
supported version, and monotonic count)
* CAPSULE_HARDWARE_INSTANCE defines which hardware instance
the capsule update is intended for. This can be set
to "1" or "0" indicating the first hardware module or SoC.
For systems with multiple modules, subsequent instances
could be numbered 2, 3, etc.
* CAPSULE_LOWEST_SUPPORTED_VERSION enables roll-back protection
by specifying the minimum firmware version that the platform
accepts. Any firmware update below this version will be
rejected.It should be set 0, 1, 2, etc according to your
firmware security and versioning requirements.
- Combining certificates into the private key file as required
by GenerateCapsule
- Add support for multiple firmware payloads
This update refactors the capsule generation process to support
multiple firmware binaries instead of a single payload.
Key changes include:
- Integration of a JSON generator script to define multiple payloads
- Add default path for JSON config generator and prepare
test infrastructure.
- Introduction of new variables
* CAPSULE_ALL_COMPONENTS: of all available components
to be included in the capsule generation process.
* CAPSULE_SELECTED_COMPONENTS: Subset of components from
CAPSULE_ALL_COMPONENTS that should actually be included
in the final capsule image.
- Replacement of direct GenerateCapsule arguments with JSON input
- Allow passing custom arguments to GenerateCapsule via
`CAPSULE_EXTRA_ARGS` variable
- Cleanup of temporary files used in the capsule generation process
These changes align with EDK2's flexible capsule format and enable
component level filtering for more advanced firmware update scenarios.
Signed-off-by: Ali Can Ozaslan <ali.oezaslan@arm.com>
Signed-off-by: Harsimran Singh Tungal <harsimransingh.tungal@arm.com>
Signed-off-by: Jon Mason <jon.mason@arm.com>
This commit is contained in:
committed by
Jon Mason
parent
5d481fd065
commit
c68fedfbea
@@ -1,51 +1,94 @@
|
||||
# This class generates UEFI capsules
|
||||
# The current class supports generating a capsule with single firmware binary
|
||||
# The current class supports generating a capsule with multiple firmware binaries
|
||||
|
||||
IMAGE_TYPES += "uefi_capsule"
|
||||
|
||||
# u-boot-tools should be installed in the native sysroot directory
|
||||
do_image_uefi_capsule[depends] += "u-boot-tools-native:do_populate_sysroot"
|
||||
# edk2-basetools should be installed in the native sysroot directory
|
||||
do_image_uefi_capsule[depends] += "edk2-basetools-native:do_populate_sysroot"
|
||||
|
||||
# By default the wic image is used to create a capsule
|
||||
CAPSULE_IMGTYPE ?= "wic"
|
||||
|
||||
# IMGDEPLOYDIR is used as the default location of firmware binary for which the capsule needs to be created
|
||||
CAPSULE_IMGLOCATION ?= "${IMGDEPLOYDIR}"
|
||||
CAPSULE_IMG_LOCATION ?= "${IMGDEPLOYDIR}"
|
||||
|
||||
# The generated capsule by default has uefi.capsule extension
|
||||
CAPSULE_EXTENSION ?= "uefi.capsule"
|
||||
|
||||
# The generated capsule's name by default is the same as UEFI_FIRMWARE_BINARY
|
||||
CAPSULE_NAME ?= "${UEFI_FIRMWARE_BINARY}"
|
||||
CAPSULE_NAME ??= "${UEFI_FIRMWARE_BINARY}"
|
||||
|
||||
# The generated capsule configuration file extension
|
||||
CAPSULE_CONFIG_FILE_EXTENSION ?= "json"
|
||||
|
||||
# The generated capsule configuration file
|
||||
CAPSULE_CONFIG_FILE ?= "${IMGDEPLOYDIR}/${CAPSULE_NAME}.${CAPSULE_CONFIG_FILE_EXTENSION}"
|
||||
|
||||
# Path to the script that generates the UEFI capsule payloads JSON
|
||||
UEFI_CAPSULE_CONFIG_GENERATOR_SCRIPT ?= "${META_ARM_LAYER_DIR}/scripts/generate_capsule_json_multiple.py"
|
||||
|
||||
# Additional variables for capsule component filtering
|
||||
CAPSULE_ALL_COMPONENTS ?= ""
|
||||
CAPSULE_SELECTED_COMPONENTS ??= ""
|
||||
|
||||
# Variables required by the EDK2 GenerateCapsule tool.
|
||||
CAPSULE_CERTIFICATE_PATHS ?= ""
|
||||
CAPSULE_FW_VERSIONS ?= ""
|
||||
CAPSULE_GUIDS ?= ""
|
||||
CAPSULE_INDEXES ?= ""
|
||||
CAPSULE_HARDWARE_INSTANCES ?= ""
|
||||
CAPSULE_LOWEST_SUPPORTED_VERSIONS ?= ""
|
||||
CAPSULE_MONOTONIC_COUNTS ?= ""
|
||||
CAPSULE_PRIVATE_KEY_PATHS ?= ""
|
||||
UEFI_FIRMWARE_BINARIES ?= ""
|
||||
PAYLOAD_CERTIFICATE_PATH ?= ""
|
||||
PAYLOAD_PRIVATE_KEY_PATH ?= ""
|
||||
|
||||
# The following variables must be set to be able to generate a capsule update
|
||||
CAPSULE_CERTIFICATE_PATH ?= ""
|
||||
CAPSULE_FW_VERSION ?= ""
|
||||
CAPSULE_GUID ?= ""
|
||||
CAPSULE_INDEX ?= ""
|
||||
CAPSULE_MONOTONIC_COUNT ?= ""
|
||||
CAPSULE_PRIVATE_KEY_PATH ?= ""
|
||||
UEFI_FIRMWARE_BINARY ?= ""
|
||||
|
||||
# Check if the required variables are set
|
||||
python() {
|
||||
for var in ["CAPSULE_CERTIFICATE_PATH", "CAPSULE_FW_VERSION", \
|
||||
"CAPSULE_GUID", "CAPSULE_INDEX", \
|
||||
"CAPSULE_MONOTONIC_COUNT", "CAPSULE_PRIVATE_KEY_PATH", \
|
||||
"UEFI_FIRMWARE_BINARY"]:
|
||||
for var in ["CAPSULE_CERTIFICATE_PATHS", "CAPSULE_FW_VERSIONS", \
|
||||
"CAPSULE_GUIDS", "CAPSULE_INDEXES", \
|
||||
"CAPSULE_HARDWARE_INSTANCES", \
|
||||
"CAPSULE_LOWEST_SUPPORTED_VERSIONS", \
|
||||
"CAPSULE_MONOTONIC_COUNTS", "CAPSULE_PRIVATE_KEY_PATHS", \
|
||||
"UEFI_FIRMWARE_BINARIES", \
|
||||
"UEFI_CAPSULE_CONFIG_GENERATOR_SCRIPT", \
|
||||
"CAPSULE_ALL_COMPONENTS", \
|
||||
"CAPSULE_SELECTED_COMPONENTS", \
|
||||
"PAYLOAD_CERTIFICATE_PATH", \
|
||||
"PAYLOAD_PRIVATE_KEY_PATH"]:
|
||||
if not d.getVar(var):
|
||||
raise bb.parse.SkipRecipe(f"{var} not set")
|
||||
}
|
||||
|
||||
IMAGE_CMD:uefi_capsule(){
|
||||
mkeficapsule --certificate ${CAPSULE_CERTIFICATE_PATH} \
|
||||
--fw-version ${CAPSULE_FW_VERSION} \
|
||||
--guid ${CAPSULE_GUID} \
|
||||
--index ${CAPSULE_INDEX} \
|
||||
--monotonic-count ${CAPSULE_MONOTONIC_COUNT} \
|
||||
--private-key ${CAPSULE_PRIVATE_KEY_PATH} \
|
||||
${UEFI_FIRMWARE_BINARY} \
|
||||
${CAPSULE_IMGLOCATION}/${CAPSULE_NAME}.${CAPSULE_EXTENSION}
|
||||
# Generates the UEFI capsule payloads JSON
|
||||
${PYTHON} ${UEFI_CAPSULE_CONFIG_GENERATOR_SCRIPT} \
|
||||
--selected_components ${CAPSULE_SELECTED_COMPONENTS}\
|
||||
--components ${CAPSULE_ALL_COMPONENTS}\
|
||||
--fw_versions ${CAPSULE_FW_VERSIONS} \
|
||||
--guids ${CAPSULE_GUIDS} \
|
||||
--hardware_instances ${CAPSULE_HARDWARE_INSTANCES} \
|
||||
--lowest_supported_versions ${CAPSULE_LOWEST_SUPPORTED_VERSIONS} \
|
||||
--monotonic_counts ${CAPSULE_MONOTONIC_COUNTS} \
|
||||
--payloads ${UEFI_FIRMWARE_BINARIES} \
|
||||
--update_image_indexes ${CAPSULE_INDEXES} \
|
||||
--private_keys ${CAPSULE_PRIVATE_KEY_PATHS} \
|
||||
--certificates ${CAPSULE_CERTIFICATE_PATHS} \
|
||||
--output ${CAPSULE_CONFIG_FILE}
|
||||
|
||||
# Force the GenerateCapsule script to use python3
|
||||
export PYTHON_COMMAND=${PYTHON}
|
||||
|
||||
# Append the certificate to the private key to create a PEM bundle compatible with EDK2 tools
|
||||
cat ${PAYLOAD_CERTIFICATE_PATH} >> ${PAYLOAD_PRIVATE_KEY_PATH}
|
||||
|
||||
# Generate the UEFI capsule image using the EDK2 GenerateCapsule tool
|
||||
${STAGING_BINDIR_NATIVE}/edk2-BaseTools/BinWrappers/PosixLike/GenerateCapsule \
|
||||
-e -j ${CAPSULE_CONFIG_FILE} \
|
||||
${CAPSULE_EXTRA_ARGS} \
|
||||
-o ${CAPSULE_IMG_LOCATION}/${CAPSULE_NAME}.${CAPSULE_EXTENSION}
|
||||
}
|
||||
|
||||
# The firmware binary should be created before generating the capsule
|
||||
|
||||
@@ -21,3 +21,6 @@ HOSTTOOLS_NONFATAL += "telnet"
|
||||
addpylib ${LAYERDIR}/lib oeqa
|
||||
|
||||
WARN_QA:append:layer-meta-arm = " patch-status"
|
||||
|
||||
# Define base directory for meta-arm
|
||||
META_ARM_LAYER_DIR := "${LAYERDIR}"
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# SPDX-FileCopyrightText: <text>Copyright 2025 Arm Limited and/or its
|
||||
# affiliates <open-source-office@arm.com></text>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Capsule Payload JSON Generator
|
||||
|
||||
This script creates a JSON file that defines multiple capsule payloads.
|
||||
Each payload is constructed using command-line input and includes key
|
||||
metadata like firmware version, GUID, hardware instance, and more.
|
||||
|
||||
Usage:
|
||||
python generate_capsule_json_multiple.py \
|
||||
--fw_versions 1 1 2 \
|
||||
--guids guid1 guid2 guid3 \
|
||||
--hardware_instances 1 1 1 \
|
||||
--lowest_supported_versions 0 0 0 \
|
||||
--monotonic_counts 1 1 1 \
|
||||
--payloads bl2.bin initramfs.bin tfm_s.bin \
|
||||
--update_image_indexes 1 4 2 \
|
||||
--private_keys key.key key.key key.key \
|
||||
--certificates cert.crt cert.crt cert.crt \
|
||||
--components bl2 initramfs tfm_s \
|
||||
--selected_components bl2 \
|
||||
--output capsule_generation_config.json
|
||||
"""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
from typing import List
|
||||
|
||||
|
||||
def parse_arguments() -> argparse.Namespace:
|
||||
"""Parses command-line arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a JSON file for multiple Capsule Payloads."
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--selected_components", default=[], nargs="*", required=False,
|
||||
help=(
|
||||
"Filters the payloads to include only those for the selected "
|
||||
"components (e.g., bl2, initramfs)."
|
||||
"All components are included when not specified."
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output", default="capsule_payloads.json", help="Output JSON file name"
|
||||
)
|
||||
|
||||
# Required arguments for each payload entry
|
||||
required_args = {
|
||||
"components": "List of components",
|
||||
"fw_versions": "List of firmware versions",
|
||||
"guids": "List of GUIDs",
|
||||
"hardware_instances": "List of hardware instances",
|
||||
"lowest_supported_versions": "List of lowest supported firmware versions",
|
||||
"monotonic_counts": "List of monotonic counts",
|
||||
"payloads": "List of payload file paths",
|
||||
"update_image_indexes": "List of update image indexes",
|
||||
"private_keys": "List of private key file paths",
|
||||
"certificates": "List of certificate file paths",
|
||||
}
|
||||
|
||||
for arg, desc in required_args.items():
|
||||
parser.add_argument(f"--{arg}", nargs="+", required=True, help=desc)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def validate_input_lengths(args: argparse.Namespace) -> None:
|
||||
"""Ensures all required input lists have the same length (excluding output and selected_components)."""
|
||||
list_lengths = {
|
||||
attr: len(getattr(args, attr))
|
||||
for attr in vars(args)
|
||||
if attr not in {"output", "selected_components"} # Ignore optional fields
|
||||
}
|
||||
|
||||
for attr, length in list_lengths.items():
|
||||
if length == 0:
|
||||
raise ValueError(f"Input list '{attr}' cannot be empty!")
|
||||
|
||||
if len(set(list_lengths.values())) != 1:
|
||||
raise ValueError("All input lists must have the same length!")
|
||||
|
||||
def create_payloads(args: argparse.Namespace) -> List[dict]:
|
||||
"""Generates the list of payload dictionaries to include in the final JSON."""
|
||||
num_payloads = len(args.components)
|
||||
selected_payloads = []
|
||||
|
||||
for i in range(num_payloads):
|
||||
|
||||
# If filtering is enabled, skip if not in the allowed components list
|
||||
if args.components[i] not in args.selected_components:
|
||||
continue
|
||||
|
||||
payload = {
|
||||
"Component": args.components[i],
|
||||
"FwVersion": args.fw_versions[i],
|
||||
"Guid": args.guids[i],
|
||||
"HardwareInstance": args.hardware_instances[i],
|
||||
"LowestSupportedVersion": args.lowest_supported_versions[i],
|
||||
"MonotonicCount": args.monotonic_counts[i],
|
||||
"Payload": args.payloads[i],
|
||||
"UpdateImageIndex": args.update_image_indexes[i],
|
||||
"OpenSslSignerPrivateCertFile": args.private_keys[i],
|
||||
"OpenSslTrustedPublicCertFile": args.certificates[i],
|
||||
"OpenSslOtherPublicCertFile": args.certificates[i],
|
||||
}
|
||||
|
||||
selected_payloads.append(payload)
|
||||
|
||||
if not selected_payloads:
|
||||
raise ValueError("None of the provided components match the selected_components list!")
|
||||
|
||||
return selected_payloads
|
||||
|
||||
|
||||
def write_json_file(output_path: str, payloads: List[dict]) -> None:
|
||||
"""Writes the list of payloads to a JSON file with basic error handling."""
|
||||
try:
|
||||
with open(output_path, "w", encoding="utf-8") as file:
|
||||
json.dump({"Payloads": payloads}, file, indent=4)
|
||||
print(f"JSON file created: {output_path}")
|
||||
except (OSError) as e:
|
||||
print(f"Failed to write JSON file to {output_path}: {e}")
|
||||
except TypeError as e:
|
||||
print(f"Invalid data format in payloads: {e}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main script entry point."""
|
||||
args = parse_arguments()
|
||||
validate_input_lengths(args)
|
||||
payloads = create_payloads(args)
|
||||
write_json_file(args.output, payloads)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user