[PATCH 0/8] Add some more EFI capsule tooling support

Recently, a set of patches were merged in next, which were adding support for generating capsules as part of the U-Boot build. Mid way through the review of those patches, it was decided to drop the patches for generating capsules through a config file. That was primarily due to the use of absolute paths in binman for testing the capsule genertion through config file. Now that the base set of patches have been merged, this series is picking up the remaining patches for review. This series addresses the concern that Simon Glass had with the use of absolute paths.
The first set of patches are adding support for generating capsules by parsing the capsule parameters through a config file, and adding a binman entry type for this. These are patches 1-5.
The other set of patches is for generating empty accept and revert capsules through binman. These capsules are needed for the FWU A/B update functionality.
Sughosh Ganu (8): tools: mkeficapsule: Add support for parsing capsule params from config file btool: mkeficapsule: Generate capsule through a config file binman: capsule: Generate capsules through config file doc: Document capsule generation through a config file sandbox: capsule: Add a config file for generating capsules test: capsule: Generate capsules through config file btool: mkeficapsule: Add support for EFI empty capsule generation binman: capsule: Add support for generating EFI empty capsules
configs/sandbox_defconfig | 2 + doc/develop/uefi/uefi.rst | 70 ++++ .../test_efi_capsule/capsule_gen_binman.dts | 8 + test/py/tests/test_efi_capsule/conftest.py | 18 +- .../test_efi_capsule/sandbox_capsule_cfg.txt | 162 ++++++++ tools/Kconfig | 16 + tools/Makefile | 1 + tools/binman/btool/mkeficapsule.py | 45 +++ tools/binman/entries.rst | 35 ++ tools/binman/etype/efi_capsule_cfg_file.py | 66 ++++ tools/binman/etype/efi_empty_capsule.py | 91 +++++ tools/binman/ftest.py | 81 ++++ tools/binman/test/319_capsule_cfg.dts | 15 + tools/binman/test/320_capsule_accept.dts | 16 + tools/binman/test/321_capsule_revert.dts | 14 + .../test/322_capsule_accept_missing_guid.dts | 14 + .../binman/test/323_capsule_accept_revert.dts | 17 + tools/eficapsule.h | 115 ++++++ tools/mkeficapsule.c | 87 +++-- tools/mkeficapsule_parse.c | 352 ++++++++++++++++++ 20 files changed, 1190 insertions(+), 35 deletions(-) create mode 100644 test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt create mode 100644 tools/binman/etype/efi_capsule_cfg_file.py create mode 100644 tools/binman/etype/efi_empty_capsule.py create mode 100644 tools/binman/test/319_capsule_cfg.dts create mode 100644 tools/binman/test/320_capsule_accept.dts create mode 100644 tools/binman/test/321_capsule_revert.dts create mode 100644 tools/binman/test/322_capsule_accept_missing_guid.dts create mode 100644 tools/binman/test/323_capsule_accept_revert.dts create mode 100644 tools/mkeficapsule_parse.c

Add support for specifying the parameters needed for capsule generation through a config file, instead of passing them through command-line. Parameters for more than a single capsule file can be specified, resulting in generation of multiple capsules through a single invocation of the command.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- tools/Kconfig | 16 ++ tools/Makefile | 1 + tools/eficapsule.h | 115 ++++++++++++ tools/mkeficapsule.c | 87 +++++---- tools/mkeficapsule_parse.c | 352 +++++++++++++++++++++++++++++++++++++ 5 files changed, 540 insertions(+), 31 deletions(-) create mode 100644 tools/mkeficapsule_parse.c
diff --git a/tools/Kconfig b/tools/Kconfig index 6e23f44d55..88ea3567d0 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -98,6 +98,22 @@ config TOOLS_MKEFICAPSULE optionally sign that file. If you want to enable UEFI capsule update feature on your target, you certainly need this.
+config EFI_CAPSULE_CFG_FILE + string "Path to the EFI Capsule Config File" + default "" + help + Path to the EFI capsule config file which provides the + parameters needed to build capsule(s). Parameters can be + provided for multiple payloads resulting in corresponding + capsule images being generated. + +config EFI_USE_CAPSULE_CFG_FILE + bool "Use the config file for generating capsules" + help + Boolean option used to specify if the EFI capsules are to + be generated through parameters specified via the config + file or through command line. + menuconfig FSPI_CONF_HEADER bool "FlexSPI Header Configuration" help diff --git a/tools/Makefile b/tools/Makefile index 3d0c4b0dd6..eb129e3bb2 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -250,6 +250,7 @@ HOSTLDLIBS_mkeficapsule += \ HOSTLDLIBS_mkeficapsule += \ $(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid") hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule +mkeficapsule-objs := mkeficapsule.o mkeficapsule_parse.o
mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o HOSTLDLIBS_mkfwumdata += -luuid diff --git a/tools/eficapsule.h b/tools/eficapsule.h index 2099a2e9b8..d455ac1d6f 100644 --- a/tools/eficapsule.h +++ b/tools/eficapsule.h @@ -52,6 +52,12 @@ typedef struct { /* flags */ #define CAPSULE_FLAGS_PERSIST_ACROSS_RESET 0x00010000
+enum capsule_type { + CAPSULE_NORMAL_BLOB = 0, + CAPSULE_ACCEPT, + CAPSULE_REVERT, +}; + struct efi_capsule_header { efi_guid_t capsule_guid; uint32_t header_size; @@ -113,6 +119,7 @@ struct efi_firmware_image_authentication { struct win_certificate_uefi_guid auth_info; } __packed;
+ /* fmp payload header */ #define SIGNATURE_16(A, B) ((A) | ((B) << 8)) #define SIGNATURE_32(A, B, C, D) \ @@ -143,4 +150,112 @@ struct fmp_payload_header_params { uint32_t fw_version; };
+/** + * struct efi_capsule_params - Capsule parameters + * @image_guid: Guid value of the payload input image + * @image_index: Image index value + * @hardware_instance: Hardware instance to be used for the image + * @fmp: FMP payload header used for storing firmware version + * @monotonic_count: Monotonic count value to be used for signed capsule + * @privkey_file: Path to private key used in capsule signing + * @cert_file: Path to public key certificate used in capsule signing + * @input_file: Path to payload input image + * @capsule_file: Path to the output capsule file + * @oemflags: Oemflags to be populated in the capsule header + * @capsule: Capsule Type, normal or accept or revert + */ +struct efi_capsule_params { + efi_guid_t *image_guid; + unsigned long image_index; + unsigned long hardware_instance; + struct fmp_payload_header_params fmp; + uint64_t monotonic_count; + char *privkey_file; + char *cert_file; + char *input_file; + char *capsule_file; + unsigned long oemflags; + enum capsule_type capsule; +}; + +/** + * capsule_with_cfg_file() - Generate capsule from config file + * @cfg_file: Path to the config file + * + * Parse the capsule parameters from the config file and use the + * parameters for generating one or more capsules. + * + * Return: None + * + */ +void capsule_with_cfg_file(const char *cfg_file); + +/** + * convert_uuid_to_guid() - convert UUID to GUID + * @buf: UUID binary + * + * UUID and GUID have the same data structure, but their binary + * formats are different due to the endianness. See lib/uuid.c. + * Since uuid_parse() can handle only UUID, this function must + * be called to get correct data for GUID when parsing a string. + * + * The correct data will be returned in @buf. + */ +void convert_uuid_to_guid(unsigned char *buf); + +/** + * create_empty_capsule() - Generate an empty capsule + * @path: Path to the empty capsule file to be generated + * @guid: Guid value of the image for which empty capsule is generated + * @fw_accept: Flag to specify whether to generate accept or revert capsule + * + * Generate an empty capsule, either an accept or a revert capsule to be + * used to flag acceptance or rejection of an earlier executed firmware + * update operation. Being used in the FWU Multi Bank firmware update + * feature. + * + * Return: 0 if OK, -ve on error + * + */ +int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept); + +/** + * create_fwbin - create an uefi capsule file + * @path: Path to a created capsule file + * @bin: Path to a firmware binary to encapsulate + * @guid: GUID of related FMP driver + * @index: Index number in capsule + * @instance: Instance number in capsule + * @fmp: FMP header params + * @mcount: Monotonic count in authentication information + * @private_file: Path to a private key file + * @cert_file: Path to a certificate file + * @oemflags: Capsule OEM Flags, bits 0-15 + * + * This function actually does the job of creating an uefi capsule file. + * All the arguments must be supplied. + * If either @private_file ror @cert_file is NULL, the capsule file + * won't be signed. + * + * Return: + * * 0 - on success + * * -1 - on failure + */ +int create_fwbin(char *path, char *bin, efi_guid_t *guid, + unsigned long index, unsigned long instance, + struct fmp_payload_header_params *fmp_ph_params, + uint64_t mcount, char *privkey_file, char *cert_file, + uint16_t oemflags); + +/** + * print_usage() - Print the command usage string + * + * Prints the standard command usage string. Called in the case + * of incorrect parameters being passed to the tool. + * + * Return: None + * + */ +void print_usage(void); + #endif /* _EFI_CAPSULE_H */ diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 52be1f122e..4058813c98 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -29,13 +29,7 @@ static const char *tool_name = "mkeficapsule"; efi_guid_t efi_guid_fm_capsule = EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID;
-static const char *opts_short = "g:i:I:v:p:c:m:o:dhAR"; - -enum { - CAPSULE_NORMAL_BLOB = 0, - CAPSULE_ACCEPT, - CAPSULE_REVERT, -} capsule_type; +static const char *opts_short = "g:i:I:v:p:c:m:o:f:dhAR";
static struct option options[] = { {"guid", required_argument, NULL, 'g'}, @@ -49,11 +43,21 @@ static struct option options[] = { {"fw-accept", no_argument, NULL, 'A'}, {"fw-revert", no_argument, NULL, 'R'}, {"capoemflag", required_argument, NULL, 'o'}, + {"cfg-file", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}, };
-static void print_usage(void) +/** + * print_usage() - Print the command usage string + * + * Prints the standard command usage string. Called in the case + * of incorrect parameters being passed to the tool. + * + * Return: None + * + */ +void print_usage(void) { fprintf(stderr, "Usage: %s [options] <image blob> <output file>\n" "Options:\n" @@ -69,6 +73,7 @@ static void print_usage(void) "\t-A, --fw-accept firmware accept capsule, requires GUID, no image blob\n" "\t-R, --fw-revert firmware revert capsule, takes no GUID, no image blob\n" "\t-o, --capoemflag Capsule OEM Flag, an integer between 0x0000 and 0xffff\n" + "\t-f, --cfg-file <config file> config file with capsule parameters\n" "\t-h, --help print a help message\n", tool_name); } @@ -388,6 +393,7 @@ static void free_sig_data(struct auth_context *ctx) * @guid: GUID of related FMP driver * @index: Index number in capsule * @instance: Instance number in capsule + * @fmp: FMP header params * @mcount: Monotonic count in authentication information * @private_file: Path to a private key file * @cert_file: Path to a certificate file @@ -402,11 +408,11 @@ static void free_sig_data(struct auth_context *ctx) * * 0 - on success * * -1 - on failure */ -static int create_fwbin(char *path, char *bin, efi_guid_t *guid, - unsigned long index, unsigned long instance, - struct fmp_payload_header_params *fmp_ph_params, - uint64_t mcount, char *privkey_file, char *cert_file, - uint16_t oemflags) +int create_fwbin(char *path, char *bin, efi_guid_t *guid, + unsigned long index, unsigned long instance, + struct fmp_payload_header_params *fmp_ph_params, + uint64_t mcount, char *privkey_file, char *cert_file, + uint16_t oemflags) { struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule; @@ -604,7 +610,21 @@ void convert_uuid_to_guid(unsigned char *buf) buf[7] = c; }
-static int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept) +/** + * create_empty_capsule() - Generate an empty capsule + * @path: Path to the empty capsule file to be generated + * @guid: Guid value of the image for which empty capsule is generated + * @fw_accept: Flag to specify whether to generate accept or revert capsule + * + * Generate an empty capsule, either an accept or a revert capsule to be + * used to flag acceptance or rejection of an earlier executed firmware + * update operation. Being used in the FWU Multi Bank firmware update + * feature. + * + * Return: 0 if OK, -ve on error + * + */ +int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept) { struct efi_capsule_header header = { 0 }; FILE *f = NULL; @@ -667,6 +687,8 @@ int main(int argc, char **argv) uint64_t mcount; unsigned long oemflags; char *privkey_file, *cert_file; + char *cfg_file; + enum capsule_type capsule; int c, idx; struct fmp_payload_header_params fmp_ph_params = { 0 };
@@ -676,8 +698,9 @@ int main(int argc, char **argv) mcount = 0; privkey_file = NULL; cert_file = NULL; + cfg_file = NULL; dump_sig = 0; - capsule_type = CAPSULE_NORMAL_BLOB; + capsule = CAPSULE_NORMAL_BLOB; oemflags = 0; for (;;) { c = getopt_long(argc, argv, opts_short, options, &idx); @@ -731,20 +754,20 @@ int main(int argc, char **argv) dump_sig = 1; break; case 'A': - if (capsule_type) { + if (capsule) { fprintf(stderr, "Select either of Accept or Revert capsule generation\n"); exit(1); } - capsule_type = CAPSULE_ACCEPT; + capsule = CAPSULE_ACCEPT; break; case 'R': - if (capsule_type) { + if (capsule) { fprintf(stderr, "Select either of Accept or Revert capsule generation\n"); exit(1); } - capsule_type = CAPSULE_REVERT; + capsule = CAPSULE_REVERT; break; case 'o': oemflags = strtoul(optarg, NULL, 0); @@ -754,6 +777,10 @@ int main(int argc, char **argv) exit(1); } break; + case 'f': + cfg_file = optarg; + capsule_with_cfg_file(cfg_file); + exit(EXIT_SUCCESS); default: print_usage(); exit(EXIT_SUCCESS); @@ -761,21 +788,21 @@ int main(int argc, char **argv) }
/* check necessary parameters */ - if ((capsule_type == CAPSULE_NORMAL_BLOB && - ((argc != optind + 2) || !guid || - ((privkey_file && !cert_file) || - (!privkey_file && cert_file)))) || - (capsule_type != CAPSULE_NORMAL_BLOB && - ((argc != optind + 1) || - ((capsule_type == CAPSULE_ACCEPT) && !guid) || - ((capsule_type == CAPSULE_REVERT) && guid)))) { + if ((capsule == CAPSULE_NORMAL_BLOB && + ((argc != optind + 2) || !guid || + ((privkey_file && !cert_file) || + (!privkey_file && cert_file)))) || + (capsule != CAPSULE_NORMAL_BLOB && + ((argc != optind + 1) || + (capsule == CAPSULE_ACCEPT && !guid) || + (capsule == CAPSULE_REVERT && guid)))) { print_usage(); exit(EXIT_FAILURE); }
- if (capsule_type != CAPSULE_NORMAL_BLOB) { + if (capsule != CAPSULE_NORMAL_BLOB) { if (create_empty_capsule(argv[argc - 1], guid, - capsule_type == CAPSULE_ACCEPT) < 0) { + capsule == CAPSULE_ACCEPT) < 0) { fprintf(stderr, "Creating empty capsule failed\n"); exit(EXIT_FAILURE); } @@ -785,6 +812,4 @@ int main(int argc, char **argv) fprintf(stderr, "Creating firmware capsule failed\n"); exit(EXIT_FAILURE); } - - exit(EXIT_SUCCESS); } diff --git a/tools/mkeficapsule_parse.c b/tools/mkeficapsule_parse.c new file mode 100644 index 0000000000..0b010706d5 --- /dev/null +++ b/tools/mkeficapsule_parse.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2023 Linaro Limited + */ + +/* + * The code in this file adds parsing ability to the mkeficapsule + * tool. This allows specifying parameters needed to build the capsule + * through the config file instead of specifying them on the command-line. + * Parameters can be specified for more than one payload, generating the + * corresponding capsule files. + * + * The parameters are specified in a "key:value" pair. All the parameters + * that are currently supported by the mkeficapsule tool can be specified + * in the config file. + * + * The example below shows four payloads. The first payload is an example + * of generating a signed capsule. The second payload is an example of + * generating an unsigned capsule. The third payload is an accept empty + * capsule, while the fourth payload is the revert empty capsule, used + * for the multi-bank firmware update feature. + * + * This functionality can be easily extended to generate a single capsule + * comprising multiple payloads. + + { + image-guid: 02f4d760-cfd5-43bd-8e2d-a42acb33c660 + hardware-instance: 0 + monotonic-count: 1 + payload: u-boot.bin + fw-version: 2 + image-index: 1 + private-key: /path/to/priv/key + pub-key-cert: /path/to/pub/key + capsule: u-boot.capsule + } + { + image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e + hardware-instance: 0 + payload: u-boot.itb + image-index: 2 + fw-version: 10 + oemflags: 0x8000 + capsule: fit.capsule + } + { + capsule-type: accept + image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e + capsule: accept.capsule + } + { + capsule-type: revert + capsule: revert.capsule + } +*/ + +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <uuid/uuid.h> + +#include "eficapsule.h" + +#define PARAMS_START "{" +#define PARAMS_END "}" + +#define PSTART 2 +#define PEND 3 + +#define MALLOC_FAIL_STR "Unable to allocate memory\n" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +const char *capsule_params[] = { + "image-guid", "image-index", "private-key", + "pub-key-cert", "payload", "capsule", + "hardware-instance", "monotonic-count", + "capsule-type", "oemflags", "fw-version" }; + +static unsigned char params_start; +static unsigned char params_end; + +static void print_and_exit(const char *str) +{ + fprintf(stderr, "%s", str); + exit(EXIT_FAILURE); +} + +static int param_delim_checks(char *line, unsigned char *token) +{ + if (!strcmp(line, PARAMS_START)) { + if (params_start || !params_end) { + fprintf(stderr, "Earlier params processing still in progress. "); + fprintf(stderr, "Can't start processing a new params.\n"); + exit(EXIT_FAILURE); + } else { + params_start = 1; + params_end = 0; + *token = PSTART; + return 1; + } + } else if (!strcmp(line, PARAMS_END)) { + if (!params_start) { + fprintf(stderr, "Cannot put end braces without start braces. "); + fprintf(stderr, "Please check the documentation for reference config file syntax\n"); + exit(EXIT_FAILURE); + } else { + params_start = 0; + params_end = 1; + *token = PEND; + return 1; + } + } else if (!params_start) { + fprintf(stderr, "Params should be passed within braces. "); + fprintf(stderr, "Please check the documentation for reference config file syntax\n"); + exit(EXIT_FAILURE); + } + + return 0; +} + +static void add_guid(efi_guid_t **guid_param, char *guid) +{ + unsigned char uuid_buf[16]; + + *guid_param = malloc(sizeof(efi_guid_t)); + if (!*guid_param) + print_and_exit(MALLOC_FAIL_STR); + + if (uuid_parse(guid, uuid_buf)) + print_and_exit("Wrong guid format\n"); + + convert_uuid_to_guid(uuid_buf); + memcpy(*guid_param, uuid_buf, sizeof(efi_guid_t)); +} + +static void add_string(char **dst, char *val) +{ + *dst = strdup(val); + if (!*dst) + print_and_exit(MALLOC_FAIL_STR); +} + +static void match_and_populate_param(char *key, char *val, + struct efi_capsule_params *param) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(capsule_params); i++) { + if (!strcmp(key, capsule_params[i])) { + switch (i) { + case 0: + add_guid(¶m->image_guid, val); + return; + case 1: + param->image_index = strtoul(val, NULL, 0); + if (param->image_index == ULONG_MAX) + print_and_exit("Enter a valid value of index bewtween 1-255"); + return; + case 2: + add_string(¶m->privkey_file, val); + return; + case 3: + add_string(¶m->cert_file, val); + return; + case 4: + add_string(¶m->input_file, val); + return; + case 5: + add_string(¶m->capsule_file, val); + return; + case 6: + param->hardware_instance = strtoul(val, NULL, 0); + if (param->hardware_instance == ULONG_MAX) + print_and_exit("Enter a valid hardware instance value"); + return; + case 7: + param->monotonic_count = strtoull(val, NULL, 0); + if (param->monotonic_count == ULLONG_MAX) + print_and_exit("Enter a valid monotonic count value"); + return; + case 8: + if (!strcmp(val, "normal")) + param->capsule = CAPSULE_NORMAL_BLOB; + else if (!strcmp(val, "accept")) + param->capsule = CAPSULE_ACCEPT; + else if (!strcmp(val, "revert")) + param->capsule = CAPSULE_REVERT; + else + print_and_exit("Invalid type of capsule"); + + return; + case 9: + param->oemflags = strtoul(val, NULL, 0); + if (param->oemflags > 0xffff) + print_and_exit("OemFlags must be between 0x0 and 0xffff\n"); + return; + case 10: + param->fmp.fw_version = strtoul(val, NULL, 0); + param->fmp.have_header = true; + return; + } + } + } + + fprintf(stderr, "Undefined param %s specified. ", key); + fprintf(stderr, "Please check the documentation for reference config file syntax\n"); + exit(EXIT_FAILURE); +} + +static int get_capsule_params(char *line, struct efi_capsule_params *params) +{ + char *key = NULL; + char *val = NULL; + unsigned char token; + + if (param_delim_checks(line, &token)) + return token; + + key = strtok(line, ":"); + if (key) + val = strtok(NULL, "\0"); + else + print_and_exit("Expect the params in a key:value pair\n"); + + match_and_populate_param(key, val, params); + + return 0; +} + +static char *skip_whitespace(char *line) +{ + char *ptr, *newline; + + ptr = malloc(strlen(line) + 1); + if (!ptr) + print_and_exit(MALLOC_FAIL_STR); + + for (newline = ptr; *line; line++) + if (!isblank(*line)) + *ptr++ = *line; + *ptr = '\0'; + return newline; +} + +static int parse_capsule_payload_params(FILE *fp, struct efi_capsule_params *params) +{ + char *line = NULL; + char *newline; + size_t n = 0; + ssize_t len; + + while ((len = getline(&line, &n, fp)) != -1) { + if (len == 1 && line[len - 1] == '\n') + continue; + + line[len - 1] = '\0'; + + newline = skip_whitespace(line); + + if (newline[0] == '#') + continue; + + if (get_capsule_params(newline, params) == PEND) + return 0; + } + + if (errno == EINVAL || errno == ENOMEM) { + fprintf(stderr, "getline() returned an error %s reading the line\n", + strerror(errno)); + exit(EXIT_FAILURE); + } else if (params_start == 1 || params_end == 0) { + fprintf(stderr, "Params should be passed within braces. "); + fprintf(stderr, "Please check the documentation for reference config file syntax\n"); + exit(EXIT_FAILURE); + } else { + return -1; + } +} + +static void params_dependency_check(struct efi_capsule_params *params) +{ + /* check necessary parameters */ + if ((params->capsule == CAPSULE_NORMAL_BLOB && + ((!params->input_file || !params->capsule_file || + !params->image_guid) || + ((params->privkey_file && !params->cert_file) || + (!params->privkey_file && params->cert_file)))) || + (params->capsule != CAPSULE_NORMAL_BLOB && + (!params->capsule_file || + (params->capsule == CAPSULE_ACCEPT && !params->image_guid) || + (params->capsule == CAPSULE_REVERT && params->image_guid)))) { + print_usage(); + exit(EXIT_FAILURE); + } +} + +static void generate_capsule(struct efi_capsule_params *params) +{ + if (params->capsule != CAPSULE_NORMAL_BLOB) { + if (create_empty_capsule(params->capsule_file, + params->image_guid, + params->capsule == + CAPSULE_ACCEPT) < 0) + print_and_exit("Creating empty capsule failed\n"); + } else if (create_fwbin(params->capsule_file, params->input_file, + params->image_guid, params->image_index, + params->hardware_instance, + ¶ms->fmp, + params->monotonic_count, + params->privkey_file, + params->cert_file, + (uint16_t)params->oemflags) < 0) { + print_and_exit("Creating firmware capsule failed\n"); + } +} + +/** + * capsule_with_cfg_file() - Generate capsule from config file + * @cfg_file: Path to the config file + * + * Parse the capsule parameters from the config file and use the + * parameters for generating one or more capsules. + * + * Return: None + * + */ +void capsule_with_cfg_file(const char *cfg_file) +{ + FILE *fp; + struct efi_capsule_params params = { 0 }; + + fp = fopen(cfg_file, "r"); + if (!fp) { + fprintf(stderr, "Unable to open the capsule config file %s\n", + cfg_file); + exit(EXIT_FAILURE); + } + + params_start = 0; + params_end = 1; + + while (parse_capsule_payload_params(fp, ¶ms) != -1) { + params_dependency_check(¶ms); + generate_capsule(¶ms); + + memset(¶ms, 0, sizeof(struct efi_capsule_params)); + } +}

On Fri, Sep 08, 2023 at 05:29:55PM +0530, Sughosh Ganu wrote:
Add support for specifying the parameters needed for capsule generation through a config file, instead of passing them through command-line. Parameters for more than a single capsule file can be specified, resulting in generation of multiple capsules through a single invocation of the command.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
Is this config file format used in any other project / form?
[snip]
+config EFI_CAPSULE_CFG_FILE
- string "Path to the EFI Capsule Config File"
- default ""
No empty defaults.

hi Tom.
On Thu, 14 Sept 2023 at 00:54, Tom Rini trini@konsulko.com wrote:
On Fri, Sep 08, 2023 at 05:29:55PM +0530, Sughosh Ganu wrote:
Add support for specifying the parameters needed for capsule generation through a config file, instead of passing them through command-line. Parameters for more than a single capsule file can be specified, resulting in generation of multiple capsules through a single invocation of the command.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
Is this config file format used in any other project / form?
Yes, I have kept the format on the same lines as the one used in the EDKII capsule generation tool.
[snip]
+config EFI_CAPSULE_CFG_FILE
string "Path to the EFI Capsule Config File"
default ""
No empty defaults.
Okay
-sughosh

Hi Sughosh,
On Fri, 8 Sept 2023 at 21:00, Sughosh Ganu sughosh.ganu@linaro.org wrote:
Add support for specifying the parameters needed for capsule generation through a config file, instead of passing them through command-line. Parameters for more than a single capsule file can be specified, resulting in generation of multiple capsules through a single invocation of the command.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/Kconfig | 16 ++ tools/Makefile | 1 + tools/eficapsule.h | 115 ++++++++++++ tools/mkeficapsule.c | 87 +++++---- tools/mkeficapsule_parse.c | 352 +++++++++++++++++++++++++++++++++++++ 5 files changed, 540 insertions(+), 31 deletions(-) create mode 100644 tools/mkeficapsule_parse.c
diff --git a/tools/Kconfig b/tools/Kconfig index 6e23f44d55..88ea3567d0 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -98,6 +98,22 @@ config TOOLS_MKEFICAPSULE optionally sign that file. If you want to enable UEFI capsule update feature on your target, you certainly need this.
+config EFI_CAPSULE_CFG_FILE
string "Path to the EFI Capsule Config File"
default ""
help
Path to the EFI capsule config file which provides the
parameters needed to build capsule(s). Parameters can be
provided for multiple payloads resulting in corresponding
capsule images being generated.
+config EFI_USE_CAPSULE_CFG_FILE
bool "Use the config file for generating capsules"
help
Boolean option used to specify if the EFI capsules are to
be generated through parameters specified via the config
file or through command line.
menuconfig FSPI_CONF_HEADER bool "FlexSPI Header Configuration" help diff --git a/tools/Makefile b/tools/Makefile index 3d0c4b0dd6..eb129e3bb2 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -250,6 +250,7 @@ HOSTLDLIBS_mkeficapsule += \ HOSTLDLIBS_mkeficapsule += \ $(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid") hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule +mkeficapsule-objs := mkeficapsule.o mkeficapsule_parse.o
mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o HOSTLDLIBS_mkfwumdata += -luuid diff --git a/tools/eficapsule.h b/tools/eficapsule.h index 2099a2e9b8..d455ac1d6f 100644 --- a/tools/eficapsule.h +++ b/tools/eficapsule.h @@ -52,6 +52,12 @@ typedef struct { /* flags */ #define CAPSULE_FLAGS_PERSIST_ACROSS_RESET 0x00010000
+enum capsule_type {
CAPSULE_NORMAL_BLOB = 0,
CAPSULE_ACCEPT,
CAPSULE_REVERT,
+};
struct efi_capsule_header { efi_guid_t capsule_guid; uint32_t header_size; @@ -113,6 +119,7 @@ struct efi_firmware_image_authentication { struct win_certificate_uefi_guid auth_info; } __packed;
/* fmp payload header */ #define SIGNATURE_16(A, B) ((A) | ((B) << 8)) #define SIGNATURE_32(A, B, C, D) \ @@ -143,4 +150,112 @@ struct fmp_payload_header_params { uint32_t fw_version; };
+/**
- struct efi_capsule_params - Capsule parameters
- @image_guid: Guid value of the payload input image
- @image_index: Image index value
- @hardware_instance: Hardware instance to be used for the image
- @fmp: FMP payload header used for storing firmware version
- @monotonic_count: Monotonic count value to be used for signed capsule
- @privkey_file: Path to private key used in capsule signing
- @cert_file: Path to public key certificate used in capsule signing
- @input_file: Path to payload input image
- @capsule_file: Path to the output capsule file
- @oemflags: Oemflags to be populated in the capsule header
- @capsule: Capsule Type, normal or accept or revert
- */
+struct efi_capsule_params {
efi_guid_t *image_guid;
unsigned long image_index;
unsigned long hardware_instance;
struct fmp_payload_header_params fmp;
uint64_t monotonic_count;
char *privkey_file;
char *cert_file;
char *input_file;
char *capsule_file;
unsigned long oemflags;
enum capsule_type capsule;
+};
+/**
- capsule_with_cfg_file() - Generate capsule from config file
- @cfg_file: Path to the config file
- Parse the capsule parameters from the config file and use the
- parameters for generating one or more capsules.
- Return: None
- */
+void capsule_with_cfg_file(const char *cfg_file);
+/**
- convert_uuid_to_guid() - convert UUID to GUID
- @buf: UUID binary
- UUID and GUID have the same data structure, but their binary
- formats are different due to the endianness. See lib/uuid.c.
- Since uuid_parse() can handle only UUID, this function must
- be called to get correct data for GUID when parsing a string.
- The correct data will be returned in @buf.
- */
+void convert_uuid_to_guid(unsigned char *buf);
+/**
- create_empty_capsule() - Generate an empty capsule
- @path: Path to the empty capsule file to be generated
- @guid: Guid value of the image for which empty capsule is generated
- @fw_accept: Flag to specify whether to generate accept or revert capsule
- Generate an empty capsule, either an accept or a revert capsule to be
- used to flag acceptance or rejection of an earlier executed firmware
- update operation. Being used in the FWU Multi Bank firmware update
- feature.
- Return: 0 if OK, -ve on error
- */
+int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept);
+/**
- create_fwbin - create an uefi capsule file
- @path: Path to a created capsule file
- @bin: Path to a firmware binary to encapsulate
- @guid: GUID of related FMP driver
- @index: Index number in capsule
- @instance: Instance number in capsule
- @fmp: FMP header params
- @mcount: Monotonic count in authentication information
- @private_file: Path to a private key file
- @cert_file: Path to a certificate file
- @oemflags: Capsule OEM Flags, bits 0-15
- This function actually does the job of creating an uefi capsule file.
- All the arguments must be supplied.
- If either @private_file ror @cert_file is NULL, the capsule file
- won't be signed.
- Return:
- 0 - on success
- -1 - on failure
- */
+int create_fwbin(char *path, char *bin, efi_guid_t *guid,
unsigned long index, unsigned long instance,
struct fmp_payload_header_params *fmp_ph_params,
uint64_t mcount, char *privkey_file, char *cert_file,
uint16_t oemflags);
+/**
- print_usage() - Print the command usage string
- Prints the standard command usage string. Called in the case
- of incorrect parameters being passed to the tool.
- Return: None
- */
+void print_usage(void);
#endif /* _EFI_CAPSULE_H */ diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 52be1f122e..4058813c98 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -29,13 +29,7 @@ static const char *tool_name = "mkeficapsule"; efi_guid_t efi_guid_fm_capsule = EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID;
-static const char *opts_short = "g:i:I:v:p:c:m:o:dhAR";
-enum {
CAPSULE_NORMAL_BLOB = 0,
CAPSULE_ACCEPT,
CAPSULE_REVERT,
-} capsule_type; +static const char *opts_short = "g:i:I:v:p:c:m:o:f:dhAR";
static struct option options[] = { {"guid", required_argument, NULL, 'g'}, @@ -49,11 +43,21 @@ static struct option options[] = { {"fw-accept", no_argument, NULL, 'A'}, {"fw-revert", no_argument, NULL, 'R'}, {"capoemflag", required_argument, NULL, 'o'},
{"cfg-file", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0},
};
-static void print_usage(void) +/**
- print_usage() - Print the command usage string
- Prints the standard command usage string. Called in the case
- of incorrect parameters being passed to the tool.
- Return: None
- */
+void print_usage(void) { fprintf(stderr, "Usage: %s [options] <image blob> <output file>\n" "Options:\n" @@ -69,6 +73,7 @@ static void print_usage(void) "\t-A, --fw-accept firmware accept capsule, requires GUID, no image blob\n" "\t-R, --fw-revert firmware revert capsule, takes no GUID, no image blob\n" "\t-o, --capoemflag Capsule OEM Flag, an integer between 0x0000 and 0xffff\n"
"\t-f, --cfg-file <config file> config file with capsule parameters\n" "\t-h, --help print a help message\n", tool_name);
} @@ -388,6 +393,7 @@ static void free_sig_data(struct auth_context *ctx)
- @guid: GUID of related FMP driver
- @index: Index number in capsule
- @instance: Instance number in capsule
- @fmp: FMP header params
- @mcount: Monotonic count in authentication information
- @private_file: Path to a private key file
- @cert_file: Path to a certificate file
@@ -402,11 +408,11 @@ static void free_sig_data(struct auth_context *ctx)
- 0 - on success
- -1 - on failure
*/ -static int create_fwbin(char *path, char *bin, efi_guid_t *guid,
unsigned long index, unsigned long instance,
struct fmp_payload_header_params *fmp_ph_params,
uint64_t mcount, char *privkey_file, char *cert_file,
uint16_t oemflags)
+int create_fwbin(char *path, char *bin, efi_guid_t *guid,
unsigned long index, unsigned long instance,
struct fmp_payload_header_params *fmp_ph_params,
uint64_t mcount, char *privkey_file, char *cert_file,
uint16_t oemflags)
{ struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule; @@ -604,7 +610,21 @@ void convert_uuid_to_guid(unsigned char *buf) buf[7] = c; }
-static int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept) +/**
- create_empty_capsule() - Generate an empty capsule
- @path: Path to the empty capsule file to be generated
- @guid: Guid value of the image for which empty capsule is generated
- @fw_accept: Flag to specify whether to generate accept or revert capsule
- Generate an empty capsule, either an accept or a revert capsule to be
- used to flag acceptance or rejection of an earlier executed firmware
- update operation. Being used in the FWU Multi Bank firmware update
- feature.
- Return: 0 if OK, -ve on error
- */
+int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept) { struct efi_capsule_header header = { 0 }; FILE *f = NULL; @@ -667,6 +687,8 @@ int main(int argc, char **argv) uint64_t mcount; unsigned long oemflags; char *privkey_file, *cert_file;
char *cfg_file;
enum capsule_type capsule; int c, idx; struct fmp_payload_header_params fmp_ph_params = { 0 };
@@ -676,8 +698,9 @@ int main(int argc, char **argv) mcount = 0; privkey_file = NULL; cert_file = NULL;
cfg_file = NULL; dump_sig = 0;
capsule_type = CAPSULE_NORMAL_BLOB;
capsule = CAPSULE_NORMAL_BLOB; oemflags = 0; for (;;) { c = getopt_long(argc, argv, opts_short, options, &idx);
@@ -731,20 +754,20 @@ int main(int argc, char **argv) dump_sig = 1; break; case 'A':
if (capsule_type) {
if (capsule) { fprintf(stderr, "Select either of Accept or Revert capsule generation\n"); exit(1); }
capsule_type = CAPSULE_ACCEPT;
capsule = CAPSULE_ACCEPT; break; case 'R':
if (capsule_type) {
if (capsule) { fprintf(stderr, "Select either of Accept or Revert capsule generation\n"); exit(1); }
capsule_type = CAPSULE_REVERT;
capsule = CAPSULE_REVERT; break; case 'o': oemflags = strtoul(optarg, NULL, 0);
@@ -754,6 +777,10 @@ int main(int argc, char **argv) exit(1); } break;
case 'f':
cfg_file = optarg;
capsule_with_cfg_file(cfg_file);
exit(EXIT_SUCCESS); default: print_usage(); exit(EXIT_SUCCESS);
@@ -761,21 +788,21 @@ int main(int argc, char **argv) }
/* check necessary parameters */
if ((capsule_type == CAPSULE_NORMAL_BLOB &&
((argc != optind + 2) || !guid ||
((privkey_file && !cert_file) ||
(!privkey_file && cert_file)))) ||
(capsule_type != CAPSULE_NORMAL_BLOB &&
((argc != optind + 1) ||
((capsule_type == CAPSULE_ACCEPT) && !guid) ||
((capsule_type == CAPSULE_REVERT) && guid)))) {
if ((capsule == CAPSULE_NORMAL_BLOB &&
((argc != optind + 2) || !guid ||
((privkey_file && !cert_file) ||
(!privkey_file && cert_file)))) ||
(capsule != CAPSULE_NORMAL_BLOB &&
((argc != optind + 1) ||
(capsule == CAPSULE_ACCEPT && !guid) ||
(capsule == CAPSULE_REVERT && guid)))) { print_usage(); exit(EXIT_FAILURE); }
if (capsule_type != CAPSULE_NORMAL_BLOB) {
if (capsule != CAPSULE_NORMAL_BLOB) { if (create_empty_capsule(argv[argc - 1], guid,
capsule_type == CAPSULE_ACCEPT) < 0) {
capsule == CAPSULE_ACCEPT) < 0) { fprintf(stderr, "Creating empty capsule failed\n"); exit(EXIT_FAILURE); }
@@ -785,6 +812,4 @@ int main(int argc, char **argv) fprintf(stderr, "Creating firmware capsule failed\n"); exit(EXIT_FAILURE); }
exit(EXIT_SUCCESS);
} diff --git a/tools/mkeficapsule_parse.c b/tools/mkeficapsule_parse.c new file mode 100644 index 0000000000..0b010706d5 --- /dev/null +++ b/tools/mkeficapsule_parse.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright 2023 Linaro Limited
- */
+/*
- The code in this file adds parsing ability to the mkeficapsule
- tool. This allows specifying parameters needed to build the capsule
- through the config file instead of specifying them on the command-line.
- Parameters can be specified for more than one payload, generating the
- corresponding capsule files.
- The parameters are specified in a "key:value" pair. All the parameters
- that are currently supported by the mkeficapsule tool can be specified
- in the config file.
- The example below shows four payloads. The first payload is an example
- of generating a signed capsule. The second payload is an example of
- generating an unsigned capsule. The third payload is an accept empty
- capsule, while the fourth payload is the revert empty capsule, used
- for the multi-bank firmware update feature.
- This functionality can be easily extended to generate a single capsule
- comprising multiple payloads.
{
image-guid: 02f4d760-cfd5-43bd-8e2d-a42acb33c660
hardware-instance: 0
monotonic-count: 1
payload: u-boot.bin
fw-version: 2
image-index: 1
private-key: /path/to/priv/key
pub-key-cert: /path/to/pub/key
capsule: u-boot.capsule
}
{
image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e
hardware-instance: 0
payload: u-boot.itb
image-index: 2
fw-version: 10
oemflags: 0x8000
capsule: fit.capsule
}
{
capsule-type: accept
image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e
capsule: accept.capsule
}
{
capsule-type: revert
capsule: revert.capsule
}
+*/
The documentation patch #4[1] has almost the same contents and examples, I think it is duplicated.
[1] https://lore.kernel.org/u-boot/20230908120002.29851-5-sughosh.ganu@linaro.or...
+#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h>
+#include <uuid/uuid.h>
+#include "eficapsule.h"
+#define PARAMS_START "{" +#define PARAMS_END "}"
+#define PSTART 2 +#define PEND 3
+#define MALLOC_FAIL_STR "Unable to allocate memory\n"
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+const char *capsule_params[] = {
"image-guid", "image-index", "private-key",
"pub-key-cert", "payload", "capsule",
"hardware-instance", "monotonic-count",
"capsule-type", "oemflags", "fw-version" };
+static unsigned char params_start; +static unsigned char params_end;
+static void print_and_exit(const char *str) +{
fprintf(stderr, "%s", str);
exit(EXIT_FAILURE);
+}
+static int param_delim_checks(char *line, unsigned char *token) +{
if (!strcmp(line, PARAMS_START)) {
if (params_start || !params_end) {
fprintf(stderr, "Earlier params processing still in progress. ");
fprintf(stderr, "Can't start processing a new params.\n");
exit(EXIT_FAILURE);
} else {
params_start = 1;
params_end = 0;
*token = PSTART;
return 1;
}
} else if (!strcmp(line, PARAMS_END)) {
if (!params_start) {
fprintf(stderr, "Cannot put end braces without start braces. ");
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
} else {
params_start = 0;
params_end = 1;
*token = PEND;
return 1;
}
} else if (!params_start) {
fprintf(stderr, "Params should be passed within braces. ");
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
}
return 0;
+}
+static void add_guid(efi_guid_t **guid_param, char *guid) +{
unsigned char uuid_buf[16];
*guid_param = malloc(sizeof(efi_guid_t));
if (!*guid_param)
print_and_exit(MALLOC_FAIL_STR);
if (uuid_parse(guid, uuid_buf))
print_and_exit("Wrong guid format\n");
convert_uuid_to_guid(uuid_buf);
memcpy(*guid_param, uuid_buf, sizeof(efi_guid_t));
+}
+static void add_string(char **dst, char *val) +{
*dst = strdup(val);
if (!*dst)
print_and_exit(MALLOC_FAIL_STR);
+}
+static void match_and_populate_param(char *key, char *val,
struct efi_capsule_params *param)
+{
int i;
for (i = 0; i < ARRAY_SIZE(capsule_params); i++) {
if (!strcmp(key, capsule_params[i])) {
switch (i) {
case 0:
add_guid(¶m->image_guid, val);
return;
case 1:
param->image_index = strtoul(val, NULL, 0);
if (param->image_index == ULONG_MAX)
print_and_exit("Enter a valid value of index bewtween 1-255");
return;
case 2:
add_string(¶m->privkey_file, val);
return;
case 3:
add_string(¶m->cert_file, val);
return;
case 4:
add_string(¶m->input_file, val);
return;
case 5:
add_string(¶m->capsule_file, val);
return;
case 6:
param->hardware_instance = strtoul(val, NULL, 0);
if (param->hardware_instance == ULONG_MAX)
print_and_exit("Enter a valid hardware instance value");
return;
case 7:
param->monotonic_count = strtoull(val, NULL, 0);
if (param->monotonic_count == ULLONG_MAX)
print_and_exit("Enter a valid monotonic count value");
return;
case 8:
if (!strcmp(val, "normal"))
param->capsule = CAPSULE_NORMAL_BLOB;
else if (!strcmp(val, "accept"))
param->capsule = CAPSULE_ACCEPT;
else if (!strcmp(val, "revert"))
param->capsule = CAPSULE_REVERT;
else
print_and_exit("Invalid type of capsule");
return;
case 9:
param->oemflags = strtoul(val, NULL, 0);
if (param->oemflags > 0xffff)
print_and_exit("OemFlags must be between 0x0 and 0xffff\n");
return;
case 10:
param->fmp.fw_version = strtoul(val, NULL, 0);
param->fmp.have_header = true;
return;
}
}
}
fprintf(stderr, "Undefined param %s specified. ", key);
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
+}
+static int get_capsule_params(char *line, struct efi_capsule_params *params) +{
char *key = NULL;
char *val = NULL;
unsigned char token;
if (param_delim_checks(line, &token))
return token;
key = strtok(line, ":");
if (key)
val = strtok(NULL, "\0");
else
print_and_exit("Expect the params in a key:value pair\n");
match_and_populate_param(key, val, params);
return 0;
+}
+static char *skip_whitespace(char *line) +{
char *ptr, *newline;
ptr = malloc(strlen(line) + 1);
if (!ptr)
print_and_exit(MALLOC_FAIL_STR);
for (newline = ptr; *line; line++)
if (!isblank(*line))
*ptr++ = *line;
*ptr = '\0';
return newline;
+}
+static int parse_capsule_payload_params(FILE *fp, struct efi_capsule_params *params) +{
char *line = NULL;
char *newline;
size_t n = 0;
ssize_t len;
while ((len = getline(&line, &n, fp)) != -1) {
if (len == 1 && line[len - 1] == '\n')
continue;
line[len - 1] = '\0';
newline = skip_whitespace(line);
if (newline[0] == '#')
continue;
if (get_capsule_params(newline, params) == PEND)
return 0;
}
if (errno == EINVAL || errno == ENOMEM) {
fprintf(stderr, "getline() returned an error %s reading the line\n",
strerror(errno));
exit(EXIT_FAILURE);
} else if (params_start == 1 || params_end == 0) {
fprintf(stderr, "Params should be passed within braces. ");
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
} else {
return -1;
}
+}
+static void params_dependency_check(struct efi_capsule_params *params) +{
/* check necessary parameters */
if ((params->capsule == CAPSULE_NORMAL_BLOB &&
((!params->input_file || !params->capsule_file ||
!params->image_guid) ||
((params->privkey_file && !params->cert_file) ||
(!params->privkey_file && params->cert_file)))) ||
(params->capsule != CAPSULE_NORMAL_BLOB &&
(!params->capsule_file ||
(params->capsule == CAPSULE_ACCEPT && !params->image_guid) ||
(params->capsule == CAPSULE_REVERT && params->image_guid)))) {
print_usage();
exit(EXIT_FAILURE);
The original command line parameter tool does the same, print_usage() when some parameters are missing. But this case is slightly different, usage is correct but the contents of the config file is wrong. It would be helpful to print the error message at least to indicate there is something wrong in the config file.
Thanks, Masahisa Kojima
}
+}
+static void generate_capsule(struct efi_capsule_params *params) +{
if (params->capsule != CAPSULE_NORMAL_BLOB) {
if (create_empty_capsule(params->capsule_file,
params->image_guid,
params->capsule ==
CAPSULE_ACCEPT) < 0)
print_and_exit("Creating empty capsule failed\n");
} else if (create_fwbin(params->capsule_file, params->input_file,
params->image_guid, params->image_index,
params->hardware_instance,
¶ms->fmp,
params->monotonic_count,
params->privkey_file,
params->cert_file,
(uint16_t)params->oemflags) < 0) {
print_and_exit("Creating firmware capsule failed\n");
}
+}
+/**
- capsule_with_cfg_file() - Generate capsule from config file
- @cfg_file: Path to the config file
- Parse the capsule parameters from the config file and use the
- parameters for generating one or more capsules.
- Return: None
- */
+void capsule_with_cfg_file(const char *cfg_file) +{
FILE *fp;
struct efi_capsule_params params = { 0 };
fp = fopen(cfg_file, "r");
if (!fp) {
fprintf(stderr, "Unable to open the capsule config file %s\n",
cfg_file);
exit(EXIT_FAILURE);
}
params_start = 0;
params_end = 1;
while (parse_capsule_payload_params(fp, ¶ms) != -1) {
params_dependency_check(¶ms);
generate_capsule(¶ms);
memset(¶ms, 0, sizeof(struct efi_capsule_params));
}
+}
2.34.1

Hi Sughosh,
On Fri, 20 Oct 2023 at 15:02, Masahisa Kojima masahisa.kojima@linaro.org wrote:
Hi Sughosh,
On Fri, 8 Sept 2023 at 21:00, Sughosh Ganu sughosh.ganu@linaro.org wrote:
Add support for specifying the parameters needed for capsule generation through a config file, instead of passing them through command-line. Parameters for more than a single capsule file can be specified, resulting in generation of multiple capsules through a single invocation of the command.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/Kconfig | 16 ++ tools/Makefile | 1 + tools/eficapsule.h | 115 ++++++++++++ tools/mkeficapsule.c | 87 +++++---- tools/mkeficapsule_parse.c | 352 +++++++++++++++++++++++++++++++++++++ 5 files changed, 540 insertions(+), 31 deletions(-) create mode 100644 tools/mkeficapsule_parse.c
diff --git a/tools/Kconfig b/tools/Kconfig index 6e23f44d55..88ea3567d0 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -98,6 +98,22 @@ config TOOLS_MKEFICAPSULE optionally sign that file. If you want to enable UEFI capsule update feature on your target, you certainly need this.
+config EFI_CAPSULE_CFG_FILE
string "Path to the EFI Capsule Config File"
default ""
help
Path to the EFI capsule config file which provides the
parameters needed to build capsule(s). Parameters can be
provided for multiple payloads resulting in corresponding
capsule images being generated.
+config EFI_USE_CAPSULE_CFG_FILE
bool "Use the config file for generating capsules"
help
Boolean option used to specify if the EFI capsules are to
be generated through parameters specified via the config
file or through command line.
menuconfig FSPI_CONF_HEADER bool "FlexSPI Header Configuration" help diff --git a/tools/Makefile b/tools/Makefile index 3d0c4b0dd6..eb129e3bb2 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -250,6 +250,7 @@ HOSTLDLIBS_mkeficapsule += \ HOSTLDLIBS_mkeficapsule += \ $(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid") hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule +mkeficapsule-objs := mkeficapsule.o mkeficapsule_parse.o
mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o HOSTLDLIBS_mkfwumdata += -luuid diff --git a/tools/eficapsule.h b/tools/eficapsule.h index 2099a2e9b8..d455ac1d6f 100644 --- a/tools/eficapsule.h +++ b/tools/eficapsule.h @@ -52,6 +52,12 @@ typedef struct { /* flags */ #define CAPSULE_FLAGS_PERSIST_ACROSS_RESET 0x00010000
+enum capsule_type {
CAPSULE_NORMAL_BLOB = 0,
CAPSULE_ACCEPT,
CAPSULE_REVERT,
+};
struct efi_capsule_header { efi_guid_t capsule_guid; uint32_t header_size; @@ -113,6 +119,7 @@ struct efi_firmware_image_authentication { struct win_certificate_uefi_guid auth_info; } __packed;
/* fmp payload header */ #define SIGNATURE_16(A, B) ((A) | ((B) << 8)) #define SIGNATURE_32(A, B, C, D) \ @@ -143,4 +150,112 @@ struct fmp_payload_header_params { uint32_t fw_version; };
+/**
- struct efi_capsule_params - Capsule parameters
- @image_guid: Guid value of the payload input image
- @image_index: Image index value
- @hardware_instance: Hardware instance to be used for the image
- @fmp: FMP payload header used for storing firmware version
- @monotonic_count: Monotonic count value to be used for signed capsule
- @privkey_file: Path to private key used in capsule signing
- @cert_file: Path to public key certificate used in capsule signing
- @input_file: Path to payload input image
- @capsule_file: Path to the output capsule file
- @oemflags: Oemflags to be populated in the capsule header
- @capsule: Capsule Type, normal or accept or revert
- */
+struct efi_capsule_params {
efi_guid_t *image_guid;
unsigned long image_index;
unsigned long hardware_instance;
struct fmp_payload_header_params fmp;
uint64_t monotonic_count;
char *privkey_file;
char *cert_file;
char *input_file;
char *capsule_file;
unsigned long oemflags;
enum capsule_type capsule;
+};
+/**
- capsule_with_cfg_file() - Generate capsule from config file
- @cfg_file: Path to the config file
- Parse the capsule parameters from the config file and use the
- parameters for generating one or more capsules.
- Return: None
- */
+void capsule_with_cfg_file(const char *cfg_file);
+/**
- convert_uuid_to_guid() - convert UUID to GUID
- @buf: UUID binary
- UUID and GUID have the same data structure, but their binary
- formats are different due to the endianness. See lib/uuid.c.
- Since uuid_parse() can handle only UUID, this function must
- be called to get correct data for GUID when parsing a string.
- The correct data will be returned in @buf.
- */
+void convert_uuid_to_guid(unsigned char *buf);
+/**
- create_empty_capsule() - Generate an empty capsule
- @path: Path to the empty capsule file to be generated
- @guid: Guid value of the image for which empty capsule is generated
- @fw_accept: Flag to specify whether to generate accept or revert capsule
- Generate an empty capsule, either an accept or a revert capsule to be
- used to flag acceptance or rejection of an earlier executed firmware
- update operation. Being used in the FWU Multi Bank firmware update
- feature.
- Return: 0 if OK, -ve on error
- */
+int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept);
+/**
- create_fwbin - create an uefi capsule file
- @path: Path to a created capsule file
- @bin: Path to a firmware binary to encapsulate
- @guid: GUID of related FMP driver
- @index: Index number in capsule
- @instance: Instance number in capsule
- @fmp: FMP header params
- @mcount: Monotonic count in authentication information
- @private_file: Path to a private key file
- @cert_file: Path to a certificate file
- @oemflags: Capsule OEM Flags, bits 0-15
- This function actually does the job of creating an uefi capsule file.
- All the arguments must be supplied.
- If either @private_file ror @cert_file is NULL, the capsule file
- won't be signed.
- Return:
- 0 - on success
- -1 - on failure
- */
+int create_fwbin(char *path, char *bin, efi_guid_t *guid,
unsigned long index, unsigned long instance,
struct fmp_payload_header_params *fmp_ph_params,
uint64_t mcount, char *privkey_file, char *cert_file,
uint16_t oemflags);
+/**
- print_usage() - Print the command usage string
- Prints the standard command usage string. Called in the case
- of incorrect parameters being passed to the tool.
- Return: None
- */
+void print_usage(void);
#endif /* _EFI_CAPSULE_H */ diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 52be1f122e..4058813c98 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -29,13 +29,7 @@ static const char *tool_name = "mkeficapsule"; efi_guid_t efi_guid_fm_capsule = EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID;
-static const char *opts_short = "g:i:I:v:p:c:m:o:dhAR";
-enum {
CAPSULE_NORMAL_BLOB = 0,
CAPSULE_ACCEPT,
CAPSULE_REVERT,
-} capsule_type; +static const char *opts_short = "g:i:I:v:p:c:m:o:f:dhAR";
static struct option options[] = { {"guid", required_argument, NULL, 'g'}, @@ -49,11 +43,21 @@ static struct option options[] = { {"fw-accept", no_argument, NULL, 'A'}, {"fw-revert", no_argument, NULL, 'R'}, {"capoemflag", required_argument, NULL, 'o'},
{"cfg-file", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0},
};
-static void print_usage(void) +/**
- print_usage() - Print the command usage string
- Prints the standard command usage string. Called in the case
- of incorrect parameters being passed to the tool.
- Return: None
- */
+void print_usage(void) { fprintf(stderr, "Usage: %s [options] <image blob> <output file>\n" "Options:\n" @@ -69,6 +73,7 @@ static void print_usage(void) "\t-A, --fw-accept firmware accept capsule, requires GUID, no image blob\n" "\t-R, --fw-revert firmware revert capsule, takes no GUID, no image blob\n" "\t-o, --capoemflag Capsule OEM Flag, an integer between 0x0000 and 0xffff\n"
"\t-f, --cfg-file <config file> config file with capsule parameters\n" "\t-h, --help print a help message\n", tool_name);
} @@ -388,6 +393,7 @@ static void free_sig_data(struct auth_context *ctx)
- @guid: GUID of related FMP driver
- @index: Index number in capsule
- @instance: Instance number in capsule
- @fmp: FMP header params
- @mcount: Monotonic count in authentication information
- @private_file: Path to a private key file
- @cert_file: Path to a certificate file
@@ -402,11 +408,11 @@ static void free_sig_data(struct auth_context *ctx)
- 0 - on success
- -1 - on failure
*/ -static int create_fwbin(char *path, char *bin, efi_guid_t *guid,
unsigned long index, unsigned long instance,
struct fmp_payload_header_params *fmp_ph_params,
uint64_t mcount, char *privkey_file, char *cert_file,
uint16_t oemflags)
+int create_fwbin(char *path, char *bin, efi_guid_t *guid,
unsigned long index, unsigned long instance,
struct fmp_payload_header_params *fmp_ph_params,
uint64_t mcount, char *privkey_file, char *cert_file,
uint16_t oemflags)
{ struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule; @@ -604,7 +610,21 @@ void convert_uuid_to_guid(unsigned char *buf) buf[7] = c; }
-static int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept) +/**
- create_empty_capsule() - Generate an empty capsule
- @path: Path to the empty capsule file to be generated
- @guid: Guid value of the image for which empty capsule is generated
- @fw_accept: Flag to specify whether to generate accept or revert capsule
- Generate an empty capsule, either an accept or a revert capsule to be
- used to flag acceptance or rejection of an earlier executed firmware
- update operation. Being used in the FWU Multi Bank firmware update
- feature.
- Return: 0 if OK, -ve on error
- */
+int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept) { struct efi_capsule_header header = { 0 }; FILE *f = NULL; @@ -667,6 +687,8 @@ int main(int argc, char **argv) uint64_t mcount; unsigned long oemflags; char *privkey_file, *cert_file;
char *cfg_file;
enum capsule_type capsule; int c, idx; struct fmp_payload_header_params fmp_ph_params = { 0 };
@@ -676,8 +698,9 @@ int main(int argc, char **argv) mcount = 0; privkey_file = NULL; cert_file = NULL;
cfg_file = NULL; dump_sig = 0;
capsule_type = CAPSULE_NORMAL_BLOB;
capsule = CAPSULE_NORMAL_BLOB; oemflags = 0; for (;;) { c = getopt_long(argc, argv, opts_short, options, &idx);
@@ -731,20 +754,20 @@ int main(int argc, char **argv) dump_sig = 1; break; case 'A':
if (capsule_type) {
if (capsule) { fprintf(stderr, "Select either of Accept or Revert capsule generation\n"); exit(1); }
capsule_type = CAPSULE_ACCEPT;
capsule = CAPSULE_ACCEPT; break; case 'R':
if (capsule_type) {
if (capsule) { fprintf(stderr, "Select either of Accept or Revert capsule generation\n"); exit(1); }
capsule_type = CAPSULE_REVERT;
capsule = CAPSULE_REVERT; break; case 'o': oemflags = strtoul(optarg, NULL, 0);
@@ -754,6 +777,10 @@ int main(int argc, char **argv) exit(1); } break;
case 'f':
cfg_file = optarg;
capsule_with_cfg_file(cfg_file);
exit(EXIT_SUCCESS); default: print_usage(); exit(EXIT_SUCCESS);
@@ -761,21 +788,21 @@ int main(int argc, char **argv) }
/* check necessary parameters */
if ((capsule_type == CAPSULE_NORMAL_BLOB &&
((argc != optind + 2) || !guid ||
((privkey_file && !cert_file) ||
(!privkey_file && cert_file)))) ||
(capsule_type != CAPSULE_NORMAL_BLOB &&
((argc != optind + 1) ||
((capsule_type == CAPSULE_ACCEPT) && !guid) ||
((capsule_type == CAPSULE_REVERT) && guid)))) {
if ((capsule == CAPSULE_NORMAL_BLOB &&
((argc != optind + 2) || !guid ||
((privkey_file && !cert_file) ||
(!privkey_file && cert_file)))) ||
(capsule != CAPSULE_NORMAL_BLOB &&
((argc != optind + 1) ||
(capsule == CAPSULE_ACCEPT && !guid) ||
(capsule == CAPSULE_REVERT && guid)))) { print_usage(); exit(EXIT_FAILURE); }
if (capsule_type != CAPSULE_NORMAL_BLOB) {
if (capsule != CAPSULE_NORMAL_BLOB) { if (create_empty_capsule(argv[argc - 1], guid,
capsule_type == CAPSULE_ACCEPT) < 0) {
capsule == CAPSULE_ACCEPT) < 0) { fprintf(stderr, "Creating empty capsule failed\n"); exit(EXIT_FAILURE); }
@@ -785,6 +812,4 @@ int main(int argc, char **argv) fprintf(stderr, "Creating firmware capsule failed\n"); exit(EXIT_FAILURE); }
exit(EXIT_SUCCESS);
} diff --git a/tools/mkeficapsule_parse.c b/tools/mkeficapsule_parse.c new file mode 100644 index 0000000000..0b010706d5 --- /dev/null +++ b/tools/mkeficapsule_parse.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright 2023 Linaro Limited
- */
+/*
- The code in this file adds parsing ability to the mkeficapsule
- tool. This allows specifying parameters needed to build the capsule
- through the config file instead of specifying them on the command-line.
- Parameters can be specified for more than one payload, generating the
- corresponding capsule files.
- The parameters are specified in a "key:value" pair. All the parameters
- that are currently supported by the mkeficapsule tool can be specified
- in the config file.
- The example below shows four payloads. The first payload is an example
- of generating a signed capsule. The second payload is an example of
- generating an unsigned capsule. The third payload is an accept empty
- capsule, while the fourth payload is the revert empty capsule, used
- for the multi-bank firmware update feature.
- This functionality can be easily extended to generate a single capsule
- comprising multiple payloads.
{
image-guid: 02f4d760-cfd5-43bd-8e2d-a42acb33c660
hardware-instance: 0
monotonic-count: 1
payload: u-boot.bin
fw-version: 2
image-index: 1
private-key: /path/to/priv/key
pub-key-cert: /path/to/pub/key
capsule: u-boot.capsule
}
{
image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e
hardware-instance: 0
payload: u-boot.itb
image-index: 2
fw-version: 10
oemflags: 0x8000
capsule: fit.capsule
}
{
capsule-type: accept
image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e
capsule: accept.capsule
}
{
capsule-type: revert
capsule: revert.capsule
}
+*/
The documentation patch #4[1] has almost the same contents and examples, I think it is duplicated.
[1] https://lore.kernel.org/u-boot/20230908120002.29851-5-sughosh.ganu@linaro.or...
As we discussed offline, it is better to keep the comment here.
+#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h>
+#include <uuid/uuid.h>
+#include "eficapsule.h"
+#define PARAMS_START "{" +#define PARAMS_END "}"
+#define PSTART 2 +#define PEND 3
+#define MALLOC_FAIL_STR "Unable to allocate memory\n"
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+const char *capsule_params[] = {
"image-guid", "image-index", "private-key",
"pub-key-cert", "payload", "capsule",
"hardware-instance", "monotonic-count",
"capsule-type", "oemflags", "fw-version" };
+static unsigned char params_start; +static unsigned char params_end;
+static void print_and_exit(const char *str) +{
fprintf(stderr, "%s", str);
exit(EXIT_FAILURE);
+}
+static int param_delim_checks(char *line, unsigned char *token) +{
if (!strcmp(line, PARAMS_START)) {
if (params_start || !params_end) {
fprintf(stderr, "Earlier params processing still in progress. ");
fprintf(stderr, "Can't start processing a new params.\n");
exit(EXIT_FAILURE);
} else {
params_start = 1;
params_end = 0;
*token = PSTART;
return 1;
}
} else if (!strcmp(line, PARAMS_END)) {
if (!params_start) {
fprintf(stderr, "Cannot put end braces without start braces. ");
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
} else {
params_start = 0;
params_end = 1;
*token = PEND;
return 1;
}
} else if (!params_start) {
fprintf(stderr, "Params should be passed within braces. ");
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
}
return 0;
+}
+static void add_guid(efi_guid_t **guid_param, char *guid) +{
unsigned char uuid_buf[16];
*guid_param = malloc(sizeof(efi_guid_t));
if (!*guid_param)
print_and_exit(MALLOC_FAIL_STR);
if (uuid_parse(guid, uuid_buf))
print_and_exit("Wrong guid format\n");
convert_uuid_to_guid(uuid_buf);
memcpy(*guid_param, uuid_buf, sizeof(efi_guid_t));
+}
+static void add_string(char **dst, char *val) +{
*dst = strdup(val);
if (!*dst)
print_and_exit(MALLOC_FAIL_STR);
+}
+static void match_and_populate_param(char *key, char *val,
struct efi_capsule_params *param)
+{
int i;
for (i = 0; i < ARRAY_SIZE(capsule_params); i++) {
if (!strcmp(key, capsule_params[i])) {
switch (i) {
case 0:
add_guid(¶m->image_guid, val);
return;
case 1:
param->image_index = strtoul(val, NULL, 0);
if (param->image_index == ULONG_MAX)
print_and_exit("Enter a valid value of index bewtween 1-255");
return;
case 2:
add_string(¶m->privkey_file, val);
return;
case 3:
add_string(¶m->cert_file, val);
return;
case 4:
add_string(¶m->input_file, val);
return;
case 5:
add_string(¶m->capsule_file, val);
return;
case 6:
param->hardware_instance = strtoul(val, NULL, 0);
if (param->hardware_instance == ULONG_MAX)
print_and_exit("Enter a valid hardware instance value");
return;
case 7:
param->monotonic_count = strtoull(val, NULL, 0);
if (param->monotonic_count == ULLONG_MAX)
print_and_exit("Enter a valid monotonic count value");
return;
case 8:
if (!strcmp(val, "normal"))
param->capsule = CAPSULE_NORMAL_BLOB;
else if (!strcmp(val, "accept"))
param->capsule = CAPSULE_ACCEPT;
else if (!strcmp(val, "revert"))
param->capsule = CAPSULE_REVERT;
else
print_and_exit("Invalid type of capsule");
return;
case 9:
param->oemflags = strtoul(val, NULL, 0);
if (param->oemflags > 0xffff)
print_and_exit("OemFlags must be between 0x0 and 0xffff\n");
return;
case 10:
param->fmp.fw_version = strtoul(val, NULL, 0);
param->fmp.have_header = true;
return;
}
}
}
fprintf(stderr, "Undefined param %s specified. ", key);
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
+}
+static int get_capsule_params(char *line, struct efi_capsule_params *params) +{
char *key = NULL;
char *val = NULL;
unsigned char token;
if (param_delim_checks(line, &token))
return token;
key = strtok(line, ":");
if (key)
val = strtok(NULL, "\0");
else
print_and_exit("Expect the params in a key:value pair\n");
match_and_populate_param(key, val, params);
return 0;
+}
+static char *skip_whitespace(char *line) +{
char *ptr, *newline;
ptr = malloc(strlen(line) + 1);
if (!ptr)
print_and_exit(MALLOC_FAIL_STR);
for (newline = ptr; *line; line++)
if (!isblank(*line))
*ptr++ = *line;
*ptr = '\0';
return newline;
+}
+static int parse_capsule_payload_params(FILE *fp, struct efi_capsule_params *params) +{
char *line = NULL;
char *newline;
size_t n = 0;
ssize_t len;
while ((len = getline(&line, &n, fp)) != -1) {
if (len == 1 && line[len - 1] == '\n')
continue;
line[len - 1] = '\0';
newline = skip_whitespace(line);
if (newline[0] == '#')
continue;
if (get_capsule_params(newline, params) == PEND)
return 0;
}
if (errno == EINVAL || errno == ENOMEM) {
fprintf(stderr, "getline() returned an error %s reading the line\n",
strerror(errno));
exit(EXIT_FAILURE);
} else if (params_start == 1 || params_end == 0) {
fprintf(stderr, "Params should be passed within braces. ");
fprintf(stderr, "Please check the documentation for reference config file syntax\n");
exit(EXIT_FAILURE);
} else {
return -1;
}
+}
+static void params_dependency_check(struct efi_capsule_params *params) +{
/* check necessary parameters */
if ((params->capsule == CAPSULE_NORMAL_BLOB &&
((!params->input_file || !params->capsule_file ||
!params->image_guid) ||
((params->privkey_file && !params->cert_file) ||
(!params->privkey_file && params->cert_file)))) ||
(params->capsule != CAPSULE_NORMAL_BLOB &&
(!params->capsule_file ||
(params->capsule == CAPSULE_ACCEPT && !params->image_guid) ||
(params->capsule == CAPSULE_REVERT && params->image_guid)))) {
print_usage();
exit(EXIT_FAILURE);
The original command line parameter tool does the same, print_usage() when some parameters are missing. But this case is slightly different, usage is correct but the contents of the config file is wrong. It would be helpful to print the error message at least to indicate there is something wrong in the config file.
On second thought, there is not much difference between specifying the parameters one by one and using the config file. Please ignore my comment.
Thanks, Masahisa Kojima
Thanks, Masahisa Kojima
}
+}
+static void generate_capsule(struct efi_capsule_params *params) +{
if (params->capsule != CAPSULE_NORMAL_BLOB) {
if (create_empty_capsule(params->capsule_file,
params->image_guid,
params->capsule ==
CAPSULE_ACCEPT) < 0)
print_and_exit("Creating empty capsule failed\n");
} else if (create_fwbin(params->capsule_file, params->input_file,
params->image_guid, params->image_index,
params->hardware_instance,
¶ms->fmp,
params->monotonic_count,
params->privkey_file,
params->cert_file,
(uint16_t)params->oemflags) < 0) {
print_and_exit("Creating firmware capsule failed\n");
}
+}
+/**
- capsule_with_cfg_file() - Generate capsule from config file
- @cfg_file: Path to the config file
- Parse the capsule parameters from the config file and use the
- parameters for generating one or more capsules.
- Return: None
- */
+void capsule_with_cfg_file(const char *cfg_file) +{
FILE *fp;
struct efi_capsule_params params = { 0 };
fp = fopen(cfg_file, "r");
if (!fp) {
fprintf(stderr, "Unable to open the capsule config file %s\n",
cfg_file);
exit(EXIT_FAILURE);
}
params_start = 0;
params_end = 1;
while (parse_capsule_payload_params(fp, ¶ms) != -1) {
params_dependency_check(¶ms);
generate_capsule(¶ms);
memset(¶ms, 0, sizeof(struct efi_capsule_params));
}
+}
2.34.1

Add support to the mkeficapsule bintool for generating EFI capsules through a config file.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- tools/binman/btool/mkeficapsule.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
diff --git a/tools/binman/btool/mkeficapsule.py b/tools/binman/btool/mkeficapsule.py index 61179747ff..b0599aa506 100644 --- a/tools/binman/btool/mkeficapsule.py +++ b/tools/binman/btool/mkeficapsule.py @@ -20,6 +20,7 @@ Options: -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 """
@@ -80,6 +81,21 @@ class Bintoolmkeficapsule(bintool.Bintool):
return self.run_cmd(*args)
+ def generate_capsule_cfg_file(self, cfg_file): + """Generate a capsule reading parameters from config file + + Args: + cfg_file (str): Path to the config file + + Returns: + None + """ + args = [ + f'--cfg-file={cfg_file}' + ] + + self.run_cmd(*args) + def fetch(self, method): """Fetch handler for mkeficapsule

Add support in binman for generating capsules by reading the capsule parameters through a config file. Also add a test case in binman for this mode of capsule generation.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org ---
tools/binman/entries.rst | 35 ++++++++++++ tools/binman/etype/efi_capsule_cfg_file.py | 66 ++++++++++++++++++++++ tools/binman/ftest.py | 29 ++++++++++ tools/binman/test/319_capsule_cfg.dts | 15 +++++ 4 files changed, 145 insertions(+) create mode 100644 tools/binman/etype/efi_capsule_cfg_file.py create mode 100644 tools/binman/test/319_capsule_cfg.dts
diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index 801bd94674..a68ea2cb21 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -532,6 +532,41 @@ payload using the blob-ext subnode.
+.. _etype_efi_capsule_cfg_file: + +Entry: capsule: Entry for generating EFI Capsule files through config file +-------------------------------------------------------------------------- + +This is an entry for generating EFI capsules through a config +file. The parameters needed for generation of the capsules are +provided through a config file. This results in generation of one or +multiple capsules, corresponding to the entries in the 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. + +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`_. + +A typical capsule entry node would then look something like this:: + + capsule { + type = "efi-capsule-cfg-file"; + cfg-file = "path/to/the/config/file"; + }; + +In the above example, the entry only contains the path to the config file. +All parameters needed for generation of the capsule, including the input +payload image and the output capsule file are specified through the entries +in the config file. + +.. _`UEFI specification`: https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf + + + .. _etype_encrypted:
Entry: encrypted: Externally built encrypted binary blob diff --git a/tools/binman/etype/efi_capsule_cfg_file.py b/tools/binman/etype/efi_capsule_cfg_file.py new file mode 100644 index 0000000000..ccf27077ec --- /dev/null +++ b/tools/binman/etype/efi_capsule_cfg_file.py @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2023 Linaro Limited +# +# Entry-type module for producing a EFI capsule through a +# config file. +# + +import os + +from binman.entry import Entry +from dtoc import fdt_util +from u_boot_pylib import tools + +class Entry_efi_capsule_cfg_file(Entry): + """Entry for generating EFI capsules through config file + + This is an entry for generating EFI capsules through a + config file. + + The parameters needed for generation of the capsules are + provided through a config file. This results in generation + of one or multiple capsules, corresponding to the entries + in the 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. + + 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`_. + + A typical capsule entry node would then look something like this + + capsule { + type = "efi-capsule-cfg-file"; + cfg-file = "path/to/the/config/file"; + }; + + In the above example, the entry only contains the path to the config file. + All parameters needed for generation of the capsule, including the input + payload image and the output capsule file are specified through the entries + in the config file. + + .. _`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.required_props = ['cfg-file'] + + def ReadNode(self): + super().ReadNode() + + self.cfg_file = fdt_util.GetString(self._node, 'cfg-file') + if not os.path.isabs(self.cfg_file): + self.cfg_file = tools.get_input_filename(self.cfg_file) + + def _GenCapsule(self): + self.mkeficapsule.generate_capsule_cfg_file(self.cfg_file) + + def ObtainContents(self): + self._GenCapsule() + + def AddBintools(self, btools): + self.mkeficapsule = self.AddBintool(btools, 'mkeficapsule') diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 8e419645a6..654af2c617 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -7334,5 +7334,34 @@ fdt fdtmap Extract the devicetree blob from the fdtmap self.assertIn("entry is missing properties: image-guid", str(e.exception))
+ def _SetupTmpOutDir(self, outfname): + self.tmpdir = tempfile.mkdtemp(prefix='binman.') + self.capsule_fname = os.path.join(self.tmpdir, outfname) + + def _BuildCapsuleCfgFile(self): + cfg_file = self._MakeInputFile('capsule_cfg_file.txt', b'') + payload_fname = self._MakeInputFile('capsule_input.bin', EFI_CAPSULE_DATA) + self._SetupTmpOutDir('image.bin') + + with open(f'{cfg_file}', 'w') as fd: + fd.write('{\n') + fd.write('\timage-index: 0x1\n') + fd.write('\timage-guid: 09d7cf52-0720-4710-91d1-08469b7fe9c8\n') + fd.write(f'\tpayload: {payload_fname}\n') + fd.write(f'\tcapsule: {self.capsule_fname}\n') + fd.write('}\n') + + def testCapsuleGenCfgFile(self): + """Test generation of EFI capsule through config file""" + self._BuildCapsuleCfgFile() + + self._DoReadFile('319_capsule_cfg.dts') + + data = tools.read_file(self.capsule_fname) + self._CheckCapsule(data) + + if not self.preserve_outdirs: + shutil.rmtree(self.tmpdir) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/319_capsule_cfg.dts b/tools/binman/test/319_capsule_cfg.dts new file mode 100644 index 0000000000..3e07bdd962 --- /dev/null +++ b/tools/binman/test/319_capsule_cfg.dts @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + efi-capsule { + type = "efi-capsule-cfg-file"; + cfg-file = "capsule_cfg_file.txt"; + }; + }; +};

Hi Sughosh,
On Fri, 8 Sept 2023 at 06:00, Sughosh Ganu sughosh.ganu@linaro.org wrote:
Add support in binman for generating capsules by reading the capsule parameters through a config file. Also add a test case in binman for this mode of capsule generation.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/binman/entries.rst | 35 ++++++++++++ tools/binman/etype/efi_capsule_cfg_file.py | 66 ++++++++++++++++++++++ tools/binman/ftest.py | 29 ++++++++++ tools/binman/test/319_capsule_cfg.dts | 15 +++++ 4 files changed, 145 insertions(+) create mode 100644 tools/binman/etype/efi_capsule_cfg_file.py create mode 100644 tools/binman/test/319_capsule_cfg.dts
I think we discussed this some time ago. The problem I have with this is that it adds another way of specifying the image description, separate from the binman definition. This introduces all sorts of problems:
- the question of where the filenames are located, something which binman tries to handle cohesively - binman has no knowledge of what is being put in the capsules - the resulting fdtmap lacks any information about what is in the capsules - it seems to create two ways of doing the same thing
Binman can presumably already create multiple capsule files, just by adding to the description.
What need does this feature meet?
Regards, Simon

hi Simon,
On Mon, 11 Sept 2023 at 00:44, Simon Glass sjg@chromium.org wrote:
Hi Sughosh,
On Fri, 8 Sept 2023 at 06:00, Sughosh Ganu sughosh.ganu@linaro.org wrote:
Add support in binman for generating capsules by reading the capsule parameters through a config file. Also add a test case in binman for this mode of capsule generation.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/binman/entries.rst | 35 ++++++++++++ tools/binman/etype/efi_capsule_cfg_file.py | 66 ++++++++++++++++++++++ tools/binman/ftest.py | 29 ++++++++++ tools/binman/test/319_capsule_cfg.dts | 15 +++++ 4 files changed, 145 insertions(+) create mode 100644 tools/binman/etype/efi_capsule_cfg_file.py create mode 100644 tools/binman/test/319_capsule_cfg.dts
I think we discussed this some time ago. The problem I have with this is that it adds another way of specifying the image description, separate from the binman definition. This introduces all sorts of problems:
- the question of where the filenames are located, something which
binman tries to handle cohesively
- binman has no knowledge of what is being put in the capsules
- the resulting fdtmap lacks any information about what is in the capsules
- it seems to create two ways of doing the same thing
Binman can presumably already create multiple capsule files, just by adding to the description.
What need does this feature meet?
We already had this discussion earlier about the need for generating capsules through the config file, where I had explained the need for this [1]. This is functionality which is supported by the specification, and we have folks who are interested in having the capsules generated through config file, and also in generation of a single capsule file consisting of multiple input payloads.
I understand that this model does not fit with the usual way in binman of generating an output image by specifying it's inputs as DT nodes. But just because this does not fit or align with the design of the tool does not mean that we do not add support for the functionality. Why can't we support this in binman as long as it is properly documented as to what is being done. Moreover, my initial versions of this patchset were attempting to achieve this by adding a make target, but you had shot it down [2]. So it either has to be done through binman, or through a make target.
-sughosh
[1] - https://lists.denx.de/pipermail/u-boot/2023-July/524849.html [2] - https://lists.denx.de/pipermail/u-boot/2023-June/521103.html

Hi Sughosh,
On Mon, 11 Sept 2023 at 08:13, Sughosh Ganu sughosh.ganu@linaro.org wrote:
hi Simon,
On Mon, 11 Sept 2023 at 00:44, Simon Glass sjg@chromium.org wrote:
Hi Sughosh,
On Fri, 8 Sept 2023 at 06:00, Sughosh Ganu sughosh.ganu@linaro.org wrote:
Add support in binman for generating capsules by reading the capsule parameters through a config file. Also add a test case in binman for this mode of capsule generation.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/binman/entries.rst | 35 ++++++++++++ tools/binman/etype/efi_capsule_cfg_file.py | 66 ++++++++++++++++++++++ tools/binman/ftest.py | 29 ++++++++++ tools/binman/test/319_capsule_cfg.dts | 15 +++++ 4 files changed, 145 insertions(+) create mode 100644 tools/binman/etype/efi_capsule_cfg_file.py create mode 100644 tools/binman/test/319_capsule_cfg.dts
I think we discussed this some time ago. The problem I have with this is that it adds another way of specifying the image description, separate from the binman definition. This introduces all sorts of problems:
- the question of where the filenames are located, something which
binman tries to handle cohesively
- binman has no knowledge of what is being put in the capsules
- the resulting fdtmap lacks any information about what is in the capsules
- it seems to create two ways of doing the same thing
Binman can presumably already create multiple capsule files, just by adding to the description.
What need does this feature meet?
We already had this discussion earlier about the need for generating capsules through the config file, where I had explained the need for this [1]. This is functionality which is supported by the specification, and we have folks who are interested in having the capsules generated through config file, and also in generation of a single capsule file consisting of multiple input payloads.
I understand that this model does not fit with the usual way in binman of generating an output image by specifying it's inputs as DT nodes. But just because this does not fit or align with the design of the tool does not mean that we do not add support for the functionality. Why can't we support this in binman as long as it is properly documented as to what is being done. Moreover, my initial versions of this patchset were attempting to achieve this by adding a make target, but you had shot it down [2]. So it either has to be done through binman, or through a make target.
We have a lot of balls in the air right now...I would like to pause this one until we get the other things sorted out:
- simple capsule update in sandbox (see my other email) - example of how to use all this on a real board (Tom is going to make a proposal there) - tidy up the testing so that it is easier to understand and fits better with the test framework - figure out the DT story, since we are still struggling to align there (Binman bindings, removing nodes, etc)
We don't need to get all those done, but we do need to at least agree on the direction.
Regards, Simon
-sughosh
[1] - https://lists.denx.de/pipermail/u-boot/2023-July/524849.html [2] - https://lists.denx.de/pipermail/u-boot/2023-June/521103.html

hi Simon,
On Wed, 13 Sept 2023 at 23:37, Simon Glass sjg@chromium.org wrote:
Hi Sughosh,
On Mon, 11 Sept 2023 at 08:13, Sughosh Ganu sughosh.ganu@linaro.org wrote:
hi Simon,
On Mon, 11 Sept 2023 at 00:44, Simon Glass sjg@chromium.org wrote:
Hi Sughosh,
On Fri, 8 Sept 2023 at 06:00, Sughosh Ganu sughosh.ganu@linaro.org wrote:
Add support in binman for generating capsules by reading the capsule parameters through a config file. Also add a test case in binman for this mode of capsule generation.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/binman/entries.rst | 35 ++++++++++++ tools/binman/etype/efi_capsule_cfg_file.py | 66 ++++++++++++++++++++++ tools/binman/ftest.py | 29 ++++++++++ tools/binman/test/319_capsule_cfg.dts | 15 +++++ 4 files changed, 145 insertions(+) create mode 100644 tools/binman/etype/efi_capsule_cfg_file.py create mode 100644 tools/binman/test/319_capsule_cfg.dts
I think we discussed this some time ago. The problem I have with this is that it adds another way of specifying the image description, separate from the binman definition. This introduces all sorts of problems:
- the question of where the filenames are located, something which
binman tries to handle cohesively
- binman has no knowledge of what is being put in the capsules
- the resulting fdtmap lacks any information about what is in the capsules
- it seems to create two ways of doing the same thing
Binman can presumably already create multiple capsule files, just by adding to the description.
What need does this feature meet?
We already had this discussion earlier about the need for generating capsules through the config file, where I had explained the need for this [1]. This is functionality which is supported by the specification, and we have folks who are interested in having the capsules generated through config file, and also in generation of a single capsule file consisting of multiple input payloads.
I understand that this model does not fit with the usual way in binman of generating an output image by specifying it's inputs as DT nodes. But just because this does not fit or align with the design of the tool does not mean that we do not add support for the functionality. Why can't we support this in binman as long as it is properly documented as to what is being done. Moreover, my initial versions of this patchset were attempting to achieve this by adding a make target, but you had shot it down [2]. So it either has to be done through binman, or through a make target.
We have a lot of balls in the air right now...I would like to pause this one until we get the other things sorted out:
Okay, fair enough. I will check if the capsule generation through config file can be added to the mkeficapsule tool in the meantime. Also, can you review the last two patches of this series [1] [2] which are adding support for empty capsules. I don't think they are at odds with the idea of binman. Thanks.
-sughosh
[1] - https://patchwork.ozlabs.org/project/uboot/patch/20230908120002.29851-8-sugh... [2] - https://patchwork.ozlabs.org/project/uboot/patch/20230908120002.29851-9-sugh...
- simple capsule update in sandbox (see my other email)
- example of how to use all this on a real board (Tom is going to make
a proposal there)
- tidy up the testing so that it is easier to understand and fits
better with the test framework
- figure out the DT story, since we are still struggling to align
there (Binman bindings, removing nodes, etc)
We don't need to get all those done, but we do need to at least agree on the direction.
Regards, Simon
-sughosh
[1] - https://lists.denx.de/pipermail/u-boot/2023-July/524849.html [2] - https://lists.denx.de/pipermail/u-boot/2023-June/521103.html

The UEFI capsule can now be generate by specifying the capsule parameters through a config file. Additionally, the capsules can be generated as part of u-boot build, through binman. Highlight these changes in the documentation.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- doc/develop/uefi/uefi.rst | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+)
diff --git a/doc/develop/uefi/uefi.rst b/doc/develop/uefi/uefi.rst index 68f9b332d1..2e27ddc9c0 100644 --- a/doc/develop/uefi/uefi.rst +++ b/doc/develop/uefi/uefi.rst @@ -318,6 +318,76 @@ Run the following command --guid <image GUID> \ <capsule_file_name>
+Alternatively, the capsules can be generated through a config +file. When generating the capsules through a config file, the Kconfig +symbol CONFIG_EFI_CAPSULE_CFG_FILE is to be used for specifying the +path to the config file. + +The config file describes the parameters that are used for generating +one or more capsules. The parameters for a given capsule file are +specified within curly braces, in the form of "key:value" pairs. All +the parameters that are currently supported by the mkeficapsule tool +can be specified through the config file. + +The following are some example payload parameters specified through +the config file. + +.. code-block:: none + + { + image-guid: 02f4d760-cfd5-43bd-8e2d-a42acb33c660 + hardware-instance: 0 + monotonic-count: 1 + payload: u-boot.bin + image-index: 1 + fw-version: 2 + private-key: /path/to/priv/key + pub-key-cert: /path/to/pub/key + capsule: u-boot.capsule + } + { + image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e + hardware-instance: 0 + payload: u-boot.itb + image-index: 2 + fw-version: 7 + oemflags: 0x8000 + capsule: fit.capsule + } + { + capsule-type: accept + image-guid: 4ce292da-1dd8-428d-a1c2-77743ef8b96e + capsule: accept.capsule + } + { + capsule-type: revert + capsule: revert.capsule + } + +The following are the keys that specify the capsule parameters + +..code-block:: none + + image-guid: Image GUID + image-index: Image index value + fw-version: Image version + private-key: Path to the private key file used for capsule signing + pub-key-cert: Path to the public key crt file used for capsule signing + payload: Path to the capsule payload file + capsule: Path to the output capsule file that is generated + hardware-instance: Hardware Instance value + monotonic-count: Monotonic count value + capsule-type: Specifies capsule type. normal(default), accept or revert + oemflags: 16bit Oemflags value to be used(populated in capsule header) + +When generating capsules through a config file, the command would look +like + +.. code-block:: console + + $ mkeficapsule --cfg-file </path/to/the/config/file> + + Capsule with firmware version *****************************

Support has been added to the mkeficapsule tool to generate capsules by parsing the capsule parameters through a config file. Add a config file for generating capsules. These capsules will be used for testing the capsule update feature on sandbox platform.
Enable generation of capsules through the config file on the sandbox variant.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- configs/sandbox_defconfig | 2 + .../test_efi_capsule/sandbox_capsule_cfg.txt | 162 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 98f0bc13a5..34f4adc10f 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -342,6 +342,8 @@ CONFIG_EFI_CAPSULE_ON_DISK=y CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y CONFIG_EFI_CAPSULE_AUTHENTICATE=y CONFIG_EFI_CAPSULE_ESL_FILE="board/sandbox/capsule_pub_esl_good.esl" +CONFIG_EFI_CAPSULE_CFG_FILE="test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt" +CONFIG_EFI_USE_CAPSULE_CFG_FILE=y CONFIG_EFI_SECURE_BOOT=y CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y diff --git a/test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt b/test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt new file mode 100644 index 0000000000..2897c1904e --- /dev/null +++ b/test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt @@ -0,0 +1,162 @@ +{ + image-index: 1 + image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8 + payload: u-boot.bin.new + capsule: Test01 +} +{ + image-index: 2 + image-guid: 5A7021F5-FEF2-48B4-AABA-832E777418C0 + payload: u-boot.env.new + capsule: Test02 +} +{ + image-index: 1 + image-guid: 058B7D83-50D5-4C47-A195-60D86AD341C4 + payload: u-boot.bin.new + capsule: Test03 + +} +{ + image-index: 1 + image-guid: 3673B45D-6A7C-46F3-9E60-ADABB03F7937 + payload: u-boot_bin_env.itb + capsule: Test04 + +} +{ + image-index: 1 + image-guid: 058B7D83-50D5-4C47-A195-60D86AD341C4 + payload: u-boot_bin_env.itb + capsule: Test05 + +} +{ + image-index: 1 + image-guid: 058B7D83-50D5-4C47-A195-60D86AD341C4 + payload: u-boot_bin_env.itb + capsule: Test05 +} +{ + image-index: 1 + monotonic-count: 1 + private-key: SIGNER.key + pub-key-cert: SIGNER.crt + image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8 + payload: u-boot.bin.new + capsule: Test11 +} +{ + image-index: 1 + monotonic-count: 1 + private-key: SIGNER2.key + pub-key-cert: SIGNER2.crt + image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8 + payload: u-boot.bin.new + capsule: Test12 +} +{ + image-index: 1 + monotonic-count: 1 + private-key: SIGNER.key + pub-key-cert: SIGNER.crt + image-guid: 3673B45D-6A7C-46F3-9E60-ADABB03F7937 + payload: u-boot_bin_env.itb + capsule: Test13 +} +{ + image-index: 1 + monotonic-count: 1 + private-key: SIGNER2.key + pub-key-cert: SIGNER2.crt + image-guid: 3673B45D-6A7C-46F3-9E60-ADABB03F7937 + payload: u-boot_bin_env.itb + capsule: Test14 +} +{ + image-index: 1 + fw-version: 5 + image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8 + payload: u-boot.bin.new + capsule: Test101 +} +{ + image-index: 2 + fw-version: 10 + image-guid: 5A7021F5-FEF2-48B4-AABA-832E777418C0 + payload: u-boot.env.new + capsule: Test102 +} +{ + image-index: 1 + fw-version: 2 + image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8 + payload: u-boot.bin.new + capsule: Test103 + +} +{ + image-index: 1 + fw-version: 5 + image-guid: 3673B45D-6A7C-46F3-9E60-ADABB03F7937 + payload: u-boot_bin_env.itb + capsule: Test104 +} +{ + image-index: 1 + fw-version: 2 + image-guid: 3673B45D-6A7C-46F3-9E60-ADABB03F7937 + payload: u-boot_bin_env.itb + capsule: Test105 + +} +{ + image-index: 1 + monotonic-count: 1 + fw-version: 5 + private-key: SIGNER.key + pub-key-cert: SIGNER.crt + image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8 + payload: u-boot.bin.new + capsule: Test111 +} +{ + image-index: 2 + monotonic-count: 1 + fw-version: 10 + private-key: SIGNER.key + pub-key-cert: SIGNER.crt + image-guid: 5A7021F5-FEF2-48B4-AABA-832E777418C0 + payload: u-boot.env.new + capsule: Test112 +} +{ + image-index: 1 + monotonic-count: 1 + fw-version: 2 + private-key: SIGNER.key + pub-key-cert: SIGNER.crt + image-guid: 09D7CF52-0720-4710-91D1-08469B7FE9C8 + payload: u-boot.bin.new + capsule: Test113 +} +{ + image-index: 1 + fw-version: 5 + monotonic-count: 1 + private-key: SIGNER.key + pub-key-cert: SIGNER.crt + image-guid: 3673B45D-6A7C-46F3-9E60-ADABB03F7937 + payload: u-boot_bin_env.itb + capsule: Test114 +} +{ + image-index: 1 + fw-version: 2 + monotonic-count: 1 + private-key: SIGNER.key + pub-key-cert: SIGNER.crt + image-guid: 3673B45D-6A7C-46F3-9E60-ADABB03F7937 + payload: u-boot_bin_env.itb + capsule: Test115 +}

Add logic to generate capsules through a config file while testing the EFI capsule update functionality. Capsule generation through config file is enabled on the sandbox variant.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- .../test_efi_capsule/capsule_gen_binman.dts | 8 ++++++++ test/py/tests/test_efi_capsule/conftest.py | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/test/py/tests/test_efi_capsule/capsule_gen_binman.dts b/test/py/tests/test_efi_capsule/capsule_gen_binman.dts index e8a1858509..9bde81cbad 100644 --- a/test/py/tests/test_efi_capsule/capsule_gen_binman.dts +++ b/test/py/tests/test_efi_capsule/capsule_gen_binman.dts @@ -54,6 +54,13 @@ }; };
+#ifdef CONFIG_EFI_USE_CAPSULE_CFG_FILE + capsule { + efi-capsule-cfg-file { + cfg-file = "sandbox_capsule_cfg.txt"; + }; + }; +#else capsule1 { filename = "Test01"; efi-capsule { @@ -318,4 +325,5 @@ }; }; }; +#endif /* !CONFIG_EFI_USE_CAPSULE_CFG_FILE */ }; diff --git a/test/py/tests/test_efi_capsule/conftest.py b/test/py/tests/test_efi_capsule/conftest.py index dd41da9284..dba3114e58 100644 --- a/test/py/tests/test_efi_capsule/conftest.py +++ b/test/py/tests/test_efi_capsule/conftest.py @@ -76,15 +76,25 @@ def efi_capsule_data(request, u_boot_config): check_call('cd %s; echo -n u-boot:Old > u-boot.bin.old; echo -n u-boot:New > u-boot.bin.new; echo -n u-boot-env:Old > u-boot.env.old; echo -n u-boot-env:New > u-boot.env.new' % data_dir, shell=True)
+ capsule_cfg_file_gen = u_boot_config.buildconfig.get('config_efi_use_capsule_cfg_file') + capsule_cfg_file_flag="" + if capsule_cfg_file_gen: + capsule_cfg_flag="-DCONFIG_EFI_USE_CAPSULE_CFG_FILE" + check_call('cd %s; ' + 'cp u-boot.bin.* %s; cp u-boot.env.* %s' + % (data_dir, u_boot_config.build_dir, u_boot_config.build_dir), shell=True) + check_call('cp %s/test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt %s' + % (u_boot_config.source_dir, data_dir), shell=True) + pythonpath = os.environ.get('PYTHONPATH', '') os.environ['PYTHONPATH'] = pythonpath + ':' + '%s/scripts/dtc/pylibfdt' % u_boot_config.build_dir check_call('cd %s; ' - 'cc -E -I %s/include -x assembler-with-cpp -o capsule_gen_tmp.dts %s/test/py/tests/test_efi_capsule/capsule_gen_binman.dts; ' + 'cc -E -I %s/include -x assembler-with-cpp %s -o capsule_gen_tmp.dts %s/test/py/tests/test_efi_capsule/capsule_gen_binman.dts; ' 'dtc -I dts -O dtb capsule_gen_tmp.dts -o capsule_binman.dtb;' - % (data_dir, u_boot_config.source_dir, u_boot_config.source_dir), shell=True) + % (data_dir, u_boot_config.source_dir, capsule_cfg_file_flag, u_boot_config.source_dir), shell=True) check_call('cd %s; ' - './tools/binman/binman --toolpath %s/tools build -u -d %s/capsule_binman.dtb -O %s -m --allow-missing -I %s -I ./board/sandbox -I ./arch/sandbox/dts' - % (u_boot_config.source_dir, u_boot_config.build_dir, data_dir, data_dir, data_dir), shell=True) + '%s/tools/binman/binman --toolpath %s/tools build -u -d capsule_binman.dtb -O . -m --allow-missing -I . -I %s/board/sandbox -I %s/arch/sandbox/dts' + % (data_dir, u_boot_config.source_dir, u_boot_config.build_dir, u_boot_config.source_dir, u_boot_config.source_dir), shell=True) os.environ['PYTHONPATH'] = pythonpath
# Create a disk image with EFI system partition

Add a method to the mkeficapsule bintool to generate empty capsules. These are capsules needed for the FWU A/B update feature.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- tools/binman/btool/mkeficapsule.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+)
diff --git a/tools/binman/btool/mkeficapsule.py b/tools/binman/btool/mkeficapsule.py index b0599aa506..061463418f 100644 --- a/tools/binman/btool/mkeficapsule.py +++ b/tools/binman/btool/mkeficapsule.py @@ -96,6 +96,35 @@ class Bintoolmkeficapsule(bintool.Bintool):
self.run_cmd(*args)
+ def generate_empty_capsule(self, accept, revert, image_guid, + output_fname): + """Generate empty capsules for FWU A/B updates + + Args: + accept (int): Generate an accept capsule + revert (int): Generate a revert capsule + image_guid (str): GUID used for identifying the image + output_fname (str): Path to the output capsule file + + Returns: + str: Tool output + """ + if accept: + args = [ + f'--guid={image_guid}', + '--fw-accept' + ] + elif revert: + args = [ + '--fw-revert' + ] + + args += [ + output_fname + ] + + return self.run_cmd(*args) + def fetch(self, method): """Fetch handler for mkeficapsule

Add support in binman for generating EFI empty capsules. These capsules are used in the FWU A/B update feature. Also add test cases in binman for the corresponding code coverage.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- Note: Simon, I have not forgotten your comment on the earlier series to add support for dumping the contents of a capsule. I will be working on that task once I am done with these patches.
tools/binman/etype/efi_empty_capsule.py | 91 +++++++++++++++++++ tools/binman/ftest.py | 52 +++++++++++ tools/binman/test/320_capsule_accept.dts | 16 ++++ tools/binman/test/321_capsule_revert.dts | 14 +++ .../test/322_capsule_accept_missing_guid.dts | 14 +++ .../binman/test/323_capsule_accept_revert.dts | 17 ++++ 6 files changed, 204 insertions(+) create mode 100644 tools/binman/etype/efi_empty_capsule.py create mode 100644 tools/binman/test/320_capsule_accept.dts create mode 100644 tools/binman/test/321_capsule_revert.dts create mode 100644 tools/binman/test/322_capsule_accept_missing_guid.dts create mode 100644 tools/binman/test/323_capsule_accept_revert.dts
diff --git a/tools/binman/etype/efi_empty_capsule.py b/tools/binman/etype/efi_empty_capsule.py new file mode 100644 index 0000000000..d2c781627b --- /dev/null +++ b/tools/binman/etype/efi_empty_capsule.py @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2023 Linaro Limited +# +# Entry-type module for producing an empty EFI capsule +# + +import os + +from binman.entry import Entry +from binman.etype.section import Entry_section +from dtoc import fdt_util +from u_boot_pylib import tools + +class Entry_efi_empty_capsule(Entry_section): + """Generate EFI empty capsules + + The parameters needed for generation of the empty capsules can + be provided as properties in the entry. + + Properties / Entry arguments: + - image-guid: Image GUID which will be used for identifying the + updatable image on the board. Mandatory for accept capsule. + - accept-capsule - Boolean property to generate an accept capsule. + image-type-id + - revert-capsule - Boolean property to generate a revert capsule + + 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`_. For more information on the empty capsule, refer the + sections 2.3.2 and 2.3.3 in the `Dependable Boot specification`_. + + A typical accept empty capsule entry node would then look something like this + + empty-capsule { + type = "efi-empty-capsule"; + /* Image GUID for testing capsule update */ + image-type-id = SANDBOX_UBOOT_IMAGE_GUID; + accept-capsule; + }; + + A typical revert empty capsule entry node would then look something like this + + empty-capsule { + type = "efi-empty-capsule"; + revert-capsule; + }; + + The empty capsules do not have any input payload image. + + .. _`UEFI specification`: https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf + .. _`Dependable Boot specification`: https://git.codelinaro.org/linaro/dependable-boot/mbfw/uploads/6f7ddfe3be24e... + """ + def __init__(self, section, etype, node): + super().__init__(section, etype, node) + self.accept = 0 + self.revert = 0 + + def ReadNode(self): + super().ReadNode() + + self.image_guid = fdt_util.GetString(self._node, 'image-guid') + self.accept = fdt_util.GetBool(self._node, 'accept-capsule') + self.revert = fdt_util.GetBool(self._node, 'revert-capsule') + + if self.accept and not self.image_guid: + self.Raise('Image GUID needed for generating accept capsule') + + if self.accept and self.revert: + self.Raise('Need to enable either Accept or Revert capsule') + + def BuildSectionData(self, required): + def get_binman_test_guid(type_str): + TYPE_TO_GUID = { + 'binman-test' : '09d7cf52-0720-4710-91d1-08469b7fe9c8' + } + return TYPE_TO_GUID[type_str] + + uniq = self.GetUniqueName() + outfile = self._filename if self._filename else 'capsule.%s' % uniq + capsule_fname = tools.get_output_filename(outfile) + guid = self.image_guid + if self.image_guid == "binman-test": + guid = get_binman_test_guid('binman-test') + + ret = self.mkeficapsule.generate_empty_capsule(self.accept, self.revert, + guid, capsule_fname) + if ret is not None: + return tools.read_file(capsule_fname) + + def AddBintools(self, btools): + self.mkeficapsule = self.AddBintool(btools, 'mkeficapsule') diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 654af2c617..dbb3e3f1f8 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -124,6 +124,9 @@ TEE_ADDR = 0x5678 FW_MGMT_GUID = 'edd5cb6d2de8444cbda17194199ad92a' # Image GUID specified in the DTS CAPSULE_IMAGE_GUID = '52cfd7092007104791d108469b7fe9c8' +# Empty capsule GUIDs +EMPTY_CAPSULE_ACCEPT_GUID = '4660990cc0bc044d85ece1fcedf1c6f8' +EMPTY_CAPSULE_REVERT_GUID = '4b8bd5ace8c05f4799b56b3f7e07aaf0'
class TestFunctional(unittest.TestCase): """Functional tests for binman @@ -7270,6 +7273,27 @@ fdt fdtmap Extract the devicetree blob from the fdtmap # payload offset for non-signed capsule with no version header(184 - 190) self.assertEqual(payload_data.hex(), data.hex()[184:190])
+ def _CheckEmptyCapsule(self, data, accept_capsule=False): + if accept_capsule: + capsule_hdr_guid = EMPTY_CAPSULE_ACCEPT_GUID + else: + capsule_hdr_guid = EMPTY_CAPSULE_REVERT_GUID + + # Empty Capsule Header GUID - offset(0 - 32) + self.assertEqual(capsule_hdr_guid, data.hex()[:32]) + + if accept_capsule: + capsule_size = "2c" + else: + capsule_size = "1c" + + # size of the capsule header + contents - offset(48 - 50) + self.assertEqual(capsule_size, data.hex()[48:50]) + + if accept_capsule: + # capsule contents - offset(56 - 88) + self.assertEqual(CAPSULE_IMAGE_GUID, data.hex()[56:88]) + def testCapsuleGen(self): """Test generation of EFI capsule""" data = self._DoReadFile('311_capsule.dts') @@ -7363,5 +7387,33 @@ fdt fdtmap Extract the devicetree blob from the fdtmap if not self.preserve_outdirs: shutil.rmtree(self.tmpdir)
+ def testCapsuleGenAcceptCapsule(self): + """Test generationg of accept EFI capsule""" + data = self._DoReadFile('320_capsule_accept.dts') + + self._CheckEmptyCapsule(data, accept_capsule=True) + + def testCapsuleGenRevertCapsule(self): + """Test generationg of revert EFI capsule""" + data = self._DoReadFile('321_capsule_revert.dts') + + self._CheckEmptyCapsule(data) + + def testCapsuleGenAcceptGuidMissing(self): + """Test that binman errors out on missing image GUID for accept capsule""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('322_capsule_accept_missing_guid.dts') + + self.assertIn("Image GUID needed for generating accept capsule", + str(e.exception)) + + def testCapsuleGenAcceptOrRevert(self): + """Test that both accept and revert capsule are not specified""" + with self.assertRaises(ValueError) as e: + self._DoReadFile('323_capsule_accept_revert.dts') + + self.assertIn("Need to enable either Accept or Revert capsule", + str(e.exception)) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/test/320_capsule_accept.dts b/tools/binman/test/320_capsule_accept.dts new file mode 100644 index 0000000000..4d6c005019 --- /dev/null +++ b/tools/binman/test/320_capsule_accept.dts @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + efi-empty-capsule { + /* Image GUID for testing capsule update */ + image-guid = "binman-test"; + accept-capsule; + }; + }; +}; diff --git a/tools/binman/test/321_capsule_revert.dts b/tools/binman/test/321_capsule_revert.dts new file mode 100644 index 0000000000..eeaa2793a5 --- /dev/null +++ b/tools/binman/test/321_capsule_revert.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + efi-empty-capsule { + revert-capsule; + }; + }; +}; diff --git a/tools/binman/test/322_capsule_accept_missing_guid.dts b/tools/binman/test/322_capsule_accept_missing_guid.dts new file mode 100644 index 0000000000..6f7062e83e --- /dev/null +++ b/tools/binman/test/322_capsule_accept_missing_guid.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + efi-empty-capsule { + accept-capsule; + }; + }; +}; diff --git a/tools/binman/test/323_capsule_accept_revert.dts b/tools/binman/test/323_capsule_accept_revert.dts new file mode 100644 index 0000000000..c68e76a669 --- /dev/null +++ b/tools/binman/test/323_capsule_accept_revert.dts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + efi-empty-capsule { + /* Image GUID for testing capsule update */ + image-guid = "binman-test"; + accept-capsule; + revert-capsule; + }; + }; +};

On Fri, Sep 08, 2023 at 05:29:54PM +0530, Sughosh Ganu wrote:
Recently, a set of patches were merged in next, which were adding support for generating capsules as part of the U-Boot build. Mid way through the review of those patches, it was decided to drop the patches for generating capsules through a config file. That was primarily due to the use of absolute paths in binman for testing the capsule genertion through config file. Now that the base set of patches have been merged, this series is picking up the remaining patches for review. This series addresses the concern that Simon Glass had with the use of absolute paths.
The first set of patches are adding support for generating capsules by parsing the capsule parameters through a config file, and adding a binman entry type for this. These are patches 1-5.
The other set of patches is for generating empty accept and revert capsules through binman. These capsules are needed for the FWU A/B update functionality.
I think what we really need next is to be able to implement and document how to use the capsule (and FWU!) spec / mechanisms on another platform and I think the Synquacer DeveloperBox would be a good target here as I believe you have access to one and so does Simon to we can deal with feedback on a real platform that both of you have access to.

hi Tom,
On Thu, 14 Sept 2023 at 00:54, Tom Rini trini@konsulko.com wrote:
On Fri, Sep 08, 2023 at 05:29:54PM +0530, Sughosh Ganu wrote:
Recently, a set of patches were merged in next, which were adding support for generating capsules as part of the U-Boot build. Mid way through the review of those patches, it was decided to drop the patches for generating capsules through a config file. That was primarily due to the use of absolute paths in binman for testing the capsule genertion through config file. Now that the base set of patches have been merged, this series is picking up the remaining patches for review. This series addresses the concern that Simon Glass had with the use of absolute paths.
The first set of patches are adding support for generating capsules by parsing the capsule parameters through a config file, and adding a binman entry type for this. These are patches 1-5.
The other set of patches is for generating empty accept and revert capsules through binman. These capsules are needed for the FWU A/B update functionality.
I think what we really need next is to be able to implement and document how to use the capsule (and FWU!) spec / mechanisms on another platform and I think the Synquacer DeveloperBox would be a good target here as I believe you have access to one and so does Simon to we can deal with feedback on a real platform that both of you have access to.
That should be possible. And although I personally don't have access to the board, folks in the team do(including Ilias). I will work on this once the patches for supporting empty capsules in binman get merged.
-sughosh

On Thu, Sep 14, 2023 at 05:42:49PM +0530, Sughosh Ganu wrote:
hi Tom,
On Thu, 14 Sept 2023 at 00:54, Tom Rini trini@konsulko.com wrote:
On Fri, Sep 08, 2023 at 05:29:54PM +0530, Sughosh Ganu wrote:
Recently, a set of patches were merged in next, which were adding support for generating capsules as part of the U-Boot build. Mid way through the review of those patches, it was decided to drop the patches for generating capsules through a config file. That was primarily due to the use of absolute paths in binman for testing the capsule genertion through config file. Now that the base set of patches have been merged, this series is picking up the remaining patches for review. This series addresses the concern that Simon Glass had with the use of absolute paths.
The first set of patches are adding support for generating capsules by parsing the capsule parameters through a config file, and adding a binman entry type for this. These are patches 1-5.
The other set of patches is for generating empty accept and revert capsules through binman. These capsules are needed for the FWU A/B update functionality.
I think what we really need next is to be able to implement and document how to use the capsule (and FWU!) spec / mechanisms on another platform and I think the Synquacer DeveloperBox would be a good target here as I believe you have access to one and so does Simon to we can deal with feedback on a real platform that both of you have access to.
That should be possible. And although I personally don't have access to the board, folks in the team do(including Ilias). I will work on this once the patches for supporting empty capsules in binman get merged.
We need to make this the next task as not being able to put this to use on a real platform today is blocking understanding of what abstractions do and don't make real sense. To tie this to another thread, since it's not really clear how a real platform would make use of capsules it's not really clear what sandbox should make every time.
participants (4)
-
Masahisa Kojima
-
Simon Glass
-
Sughosh Ganu
-
Tom Rini