[PATCH v2 0/4] Generate capsules from config files

Add support for generating capsules through a config file. Multiple capsules can be generated through a single invocation of the mkeficapsule tool, where the capsule parameters are fetched from the config file.
The format of the config file pretty similar to the one used by the capsule generation tool in EDKII.
The functionality is enabled on the sandbox variant and the EFI capsule update test is now configured to generate the capsules through a config file. The sandbox_flattree variant generates capsules through binman, thus covering both the paths.
This is a rebased version of the earlier patch series that was sent to the mailing list [1]. That series was not getting merged as Simon had asked for generation of capsules as part of the sandbox platform build. Those patches have since been merged. Moreover, there has been adoption of the capsule generation logic by TI platforms as well, so that should address the concern which Tom had about it's usage.
These patches are more of a step in the direction of adding support for generation of a multi-payload capsule. There has been a request for it's support for a platform [2].
[1] - https://lists.denx.de/pipermail/u-boot/2023-November/538223.html [2] - https://lists.denx.de/pipermail/u-boot/2024-March/548126.html
Sughosh Ganu (4): tools: mkeficapsule: Add support for parsing capsule params from 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
configs/sandbox_defconfig | 2 + doc/develop/uefi/uefi.rst | 70 ++++ .../test_efi_capsule/capsule_gen_binman.dts | 2 + test/py/tests/test_efi_capsule/conftest.py | 7 + .../test_efi_capsule/sandbox_capsule_cfg.txt | 73 ++++ tools/Kconfig | 15 + tools/Makefile | 1 + tools/eficapsule.h | 114 ++++++ tools/mkeficapsule.c | 87 +++-- tools/mkeficapsule_parse.c | 352 ++++++++++++++++++ 10 files changed, 692 insertions(+), 31 deletions(-) create mode 100644 test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt 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.
The config file can be passed to the mkeficapsule tool in such manner
$ ./tools/mkeficapsule -f <path/to/the/config/file>
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org --- tools/Kconfig | 15 ++ tools/Makefile | 1 + tools/eficapsule.h | 114 ++++++++++++ tools/mkeficapsule.c | 87 +++++---- tools/mkeficapsule_parse.c | 352 +++++++++++++++++++++++++++++++++++++ 5 files changed, 538 insertions(+), 31 deletions(-) create mode 100644 tools/mkeficapsule_parse.c
diff --git a/tools/Kconfig b/tools/Kconfig index 667807b331..0362ca8e45 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -104,6 +104,21 @@ 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" + 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 6a4280e366..4311f5914f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -253,6 +253,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 6efd07d2eb..71a08b62e6 100644 --- a/tools/eficapsule.h +++ b/tools/eficapsule.h @@ -54,6 +54,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; @@ -145,4 +151,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 6a261ff549..480cedfa5e 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -28,13 +28,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:dhARD"; - -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:dhARD";
static struct option options[] = { {"guid", required_argument, NULL, 'g'}, @@ -49,11 +43,21 @@ static struct option options[] = { {"fw-revert", no_argument, NULL, 'R'}, {"capoemflag", required_argument, NULL, 'o'}, {"dump-capsule", no_argument, NULL, 'D'}, + {"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" @@ -70,6 +74,7 @@ static void print_usage(void) "\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-D, --dump-capsule dump the contents of the capsule headers\n" + "\t-f, --cfg-file <config file> config file with capsule parameters\n" "\t-h, --help print a help message\n", tool_name); } @@ -389,6 +394,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 @@ -403,11 +409,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; @@ -605,7 +611,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; @@ -878,6 +898,8 @@ int main(int argc, char **argv) unsigned long oemflags; bool capsule_dump; char *privkey_file, *cert_file; + char *cfg_file; + enum capsule_type capsule; int c, idx; struct fmp_payload_header_params fmp_ph_params = { 0 };
@@ -888,8 +910,9 @@ int main(int argc, char **argv) privkey_file = NULL; cert_file = NULL; capsule_dump = false; + 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); @@ -943,20 +966,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); @@ -969,6 +992,10 @@ int main(int argc, char **argv) case 'D': capsule_dump = true; break; + case 'f': + cfg_file = optarg; + capsule_with_cfg_file(cfg_file); + exit(EXIT_SUCCESS); default: print_usage(); exit(EXIT_SUCCESS); @@ -985,21 +1012,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); } @@ -1009,6 +1036,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 19.04.24 08:55, 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.
The config file can be passed to the mkeficapsule tool in such manner
$ ./tools/mkeficapsule -f <path/to/the/config/file>
Please, mention the long option.
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/Kconfig | 15 ++ tools/Makefile | 1 + tools/eficapsule.h | 114 ++++++++++++ tools/mkeficapsule.c | 87 +++++---- tools/mkeficapsule_parse.c | 352 +++++++++++++++++++++++++++++++++++++ 5 files changed, 538 insertions(+), 31 deletions(-) create mode 100644 tools/mkeficapsule_parse.c
diff --git a/tools/Kconfig b/tools/Kconfig index 667807b331..0362ca8e45 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -104,6 +104,21 @@ 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"
- 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.
This help test does not explain if this is a parameter for binman or something built into mkeficapsule.
We should not hard code any path inside mkeficapsule.
I can't see the new CONFIG parameters used within the code changes of this patch. Please, add them into the patches where they are needed.
+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.
Given this help text I would not know if this option changes how mkeficapsule is built or how binman invokes it.
I would expect that mkeficapsule is always built in a way that a configuration file can be passed.
Furthermore I would expect binman to invoke mkeficapsule with the appropriate command line parameters if you have enabled building capsules.
Why do we need this configuration parameter? Just always build mkeficapsule with support for the -f parameter.
Best regards
Heinrich
- menuconfig FSPI_CONF_HEADER bool "FlexSPI Header Configuration" help
diff --git a/tools/Makefile b/tools/Makefile index 6a4280e366..4311f5914f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -253,6 +253,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 6efd07d2eb..71a08b62e6 100644 --- a/tools/eficapsule.h +++ b/tools/eficapsule.h @@ -54,6 +54,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;
@@ -145,4 +151,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 6a261ff549..480cedfa5e 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -28,13 +28,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:dhARD";
-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:dhARD";
static struct option options[] = { {"guid", required_argument, NULL, 'g'}, @@ -49,11 +43,21 @@ static struct option options[] = { {"fw-revert", no_argument, NULL, 'R'}, {"capoemflag", required_argument, NULL, 'o'}, {"dump-capsule", no_argument, NULL, 'D'},
- {"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" @@ -70,6 +74,7 @@ static void print_usage(void) "\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-D, --dump-capsule dump the contents of the capsule headers\n"
"\t-h, --help print a help message\n", tool_name); }"\t-f, --cfg-file <config file> config file with capsule parameters\n"
@@ -389,6 +394,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
@@ -403,11 +409,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,
{ struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule;uint16_t oemflags)
@@ -605,7 +611,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; @@ -878,6 +898,8 @@ int main(int argc, char **argv) unsigned long oemflags; bool capsule_dump; char *privkey_file, *cert_file;
- char *cfg_file;
- enum capsule_type capsule; int c, idx; struct fmp_payload_header_params fmp_ph_params = { 0 };
@@ -888,8 +910,9 @@ int main(int argc, char **argv) privkey_file = NULL; cert_file = NULL; capsule_dump = false;
- 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);
@@ -943,20 +966,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;
case 'R':capsule = CAPSULE_ACCEPT; break;
if (capsule_type) {
if (capsule) { fprintf(stderr, "Select either of Accept or Revert capsule generation\n"); exit(1); }
capsule_type = CAPSULE_REVERT;
case 'o': oemflags = strtoul(optarg, NULL, 0);capsule = CAPSULE_REVERT; break;
@@ -969,6 +992,10 @@ int main(int argc, char **argv) case 'D': capsule_dump = true; break;
case 'f':
cfg_file = optarg;
capsule_with_cfg_file(cfg_file);
default: print_usage(); exit(EXIT_SUCCESS);exit(EXIT_SUCCESS);
@@ -985,21 +1012,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) ||
print_usage(); exit(EXIT_FAILURE); }(capsule == CAPSULE_REVERT && guid)))) {
- 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);
@@ -1009,6 +1036,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));
- }
+}

hi Heinrich,
On Fri, 19 Apr 2024 at 12:44, Heinrich Schuchardt xypron.glpk@gmx.de wrote:
On 19.04.24 08:55, 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.
The config file can be passed to the mkeficapsule tool in such manner
$ ./tools/mkeficapsule -f <path/to/the/config/file>
Please, mention the long option.
Okay
Signed-off-by: Sughosh Ganu sughosh.ganu@linaro.org
tools/Kconfig | 15 ++ tools/Makefile | 1 + tools/eficapsule.h | 114 ++++++++++++ tools/mkeficapsule.c | 87 +++++---- tools/mkeficapsule_parse.c | 352 +++++++++++++++++++++++++++++++++++++ 5 files changed, 538 insertions(+), 31 deletions(-) create mode 100644 tools/mkeficapsule_parse.c
diff --git a/tools/Kconfig b/tools/Kconfig index 667807b331..0362ca8e45 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -104,6 +104,21 @@ 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"
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.
This help test does not explain if this is a parameter for binman or something built into mkeficapsule.
We should not hard code any path inside mkeficapsule.
I can't see the new CONFIG parameters used within the code changes of this patch. Please, add them into the patches where they are needed.
As discussed over IRC, I need these config options only for the CI testing. I will replace these with hard-coded paths for the tests.
+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.
Given this help text I would not know if this option changes how mkeficapsule is built or how binman invokes it.
I would expect that mkeficapsule is always built in a way that a configuration file can be passed.
Furthermore I would expect binman to invoke mkeficapsule with the appropriate command line parameters if you have enabled building capsules.
Why do we need this configuration parameter? Just always build mkeficapsule with support for the -f parameter.
Will remove these config flags.
-sughosh
Best regards
Heinrich
- menuconfig FSPI_CONF_HEADER bool "FlexSPI Header Configuration" help
diff --git a/tools/Makefile b/tools/Makefile index 6a4280e366..4311f5914f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -253,6 +253,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 6efd07d2eb..71a08b62e6 100644 --- a/tools/eficapsule.h +++ b/tools/eficapsule.h @@ -54,6 +54,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;
@@ -145,4 +151,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 6a261ff549..480cedfa5e 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -28,13 +28,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:dhARD";
-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:dhARD";
static struct option options[] = { {"guid", required_argument, NULL, 'g'}, @@ -49,11 +43,21 @@ static struct option options[] = { {"fw-revert", no_argument, NULL, 'R'}, {"capoemflag", required_argument, NULL, 'o'}, {"dump-capsule", no_argument, NULL, 'D'},
};{"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" @@ -70,6 +74,7 @@ static void print_usage(void) "\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-D, --dump-capsule dump the contents of the capsule headers\n"
}"\t-f, --cfg-file <config file> config file with capsule parameters\n" "\t-h, --help print a help message\n", tool_name);
@@ -389,6 +394,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
@@ -403,11 +409,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,
{ struct efi_capsule_header header; struct efi_firmware_management_capsule_header capsule;uint16_t oemflags)
@@ -605,7 +611,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; @@ -878,6 +898,8 @@ int main(int argc, char **argv) unsigned long oemflags; bool capsule_dump; char *privkey_file, *cert_file;
char *cfg_file;
enum capsule_type capsule; int c, idx; struct fmp_payload_header_params fmp_ph_params = { 0 };
@@ -888,8 +910,9 @@ int main(int argc, char **argv) privkey_file = NULL; cert_file = NULL; capsule_dump = false;
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);
@@ -943,20 +966,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);
@@ -969,6 +992,10 @@ int main(int argc, char **argv) case 'D': capsule_dump = true; break;
case 'f':
cfg_file = optarg;
capsule_with_cfg_file(cfg_file);
exit(EXIT_SUCCESS); default: print_usage(); exit(EXIT_SUCCESS);
@@ -985,21 +1012,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); }
@@ -1009,6 +1036,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));
}
+}

The UEFI capsule can now be generated by specifying the capsule parameters through a config file. 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 0389b269c0..8586127a83 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 *****************************

On 19.04.24 08:55, Sughosh Ganu wrote:
The UEFI capsule can now be generated by specifying the capsule parameters through a config file. 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 0389b269c0..8586127a83 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>
The users deserve a man-page mkeficapsule.1 that can be installed by distros as /usr/share/doc/man/man1/mkeficapsule.1.
Do not expect the user to look up the information online.
+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.
Why do we need CONFIG_EFI_CAPSULE_CFG_FILE? You could use a fixed path or an environment parameter.
+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
- }
Is this one file? Are these multiple files? If these are multiple files, please, put them in different code blocks.
What are the curly braces good for? Please, use an established file format like YAML or JSON.
+The following are the keys that specify the capsule parameters
+..code-block:: none
- image-guid: Image GUID
Please use the following formatting:
image-guid Image GUID
fw-version Image version
- 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
Please, explain what a hardware instance is.
- monotonic-count: Monotonic count value
Please, explain what it is used for.
- capsule-type: Specifies capsule type. normal(default), accept or revert
ditto
- oemflags: 16bit Oemflags value to be used(populated in capsule header)
ditto
+When generating capsules through a config file, the command would look +like
+.. code-block:: console
- $ mkeficapsule --cfg-file </path/to/the/config/file>
All available command line parameters of mkeficapsule should be described in one place.
Best regards
Heinrich
- Capsule with firmware version

hi Heinrich,
On Fri, 19 Apr 2024 at 13:01, Heinrich Schuchardt xypron.glpk@gmx.de wrote:
On 19.04.24 08:55, Sughosh Ganu wrote:
The UEFI capsule can now be generated by specifying the capsule parameters through a config file. 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 0389b269c0..8586127a83 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>
The users deserve a man-page mkeficapsule.1 that can be installed by distros as /usr/share/doc/man/man1/mkeficapsule.1.
Do not expect the user to look up the information online.
Will add a man-page.
+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.
Why do we need CONFIG_EFI_CAPSULE_CFG_FILE? You could use a fixed path or an environment parameter.
Will remove the config flags.
+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
}
Is this one file? Are these multiple files? If these are multiple files, please, put them in different code blocks.
These are multiple files. But eventually, we will be using this feature to generate a multi-payload capsule. Again, this is on similar lines to how this is done with the EDKII script.
What are the curly braces good for? Please, use an established file format like YAML or JSON.
As discussed over IRC, I will check if I can use the YAML format and keep the format similar to the one used above -- in key:value pairs. I would prefer keeping the format similar to what is used in the EDKII capsule generation tool. But if I can use the mapping node type in YAML for providing this information, I will explore using YAML with the libcyaml library for parsing the configs.
+The following are the keys that specify the capsule parameters
+..code-block:: none
- image-guid: Image GUID
Please use the following formatting:
image-guid Image GUID
fw-version Image version
I have kept the format similar to what is used in EDKII.
- 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
Please, explain what a hardware instance is.
Okay
- monotonic-count: Monotonic count value
Please, explain what it is used for.
Okay
- capsule-type: Specifies capsule type. normal(default), accept or revert
ditto
- oemflags: 16bit Oemflags value to be used(populated in capsule header)
ditto
+When generating capsules through a config file, the command would look +like
+.. code-block:: console
- $ mkeficapsule --cfg-file </path/to/the/config/file>
All available command line parameters of mkeficapsule should be described in one place.
Will fix
-sughosh
Best regards
Heinrich
- 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 | 73 +++++++++++++++++++ 2 files changed, 75 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 93b52f2de5..b30cd394a1 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -353,6 +353,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..01040eb6b3 --- /dev/null +++ b/test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt @@ -0,0 +1,73 @@ +{ + 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: 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: 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 + 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/py/tests/test_efi_capsule/capsule_gen_binman.dts | 2 ++ test/py/tests/test_efi_capsule/conftest.py | 7 +++++++ 2 files changed, 9 insertions(+)
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 1a62c26047..d1c0067064 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,7 @@ }; };
+#if !defined(CONFIG_EFI_USE_CAPSULE_CFG_FILE) capsule1 { filename = "Test04"; efi-capsule { @@ -165,4 +166,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 80b12977d6..b5abd7ab45 100644 --- a/test/py/tests/test_efi_capsule/conftest.py +++ b/test/py/tests/test_efi_capsule/conftest.py @@ -88,6 +88,13 @@ def efi_capsule_data(request, u_boot_config): check_call('cp %s/Test* %s' % (u_boot_config.build_dir, data_dir), shell=True) os.environ['PYTHONPATH'] = pythonpath
+ capsule_cfg_file_gen = u_boot_config.buildconfig.get('config_efi_use_capsule_cfg_file') + if capsule_cfg_file_gen: + cfg_file = u_boot_config.source_dir + '/' + u_boot_config.buildconfig.get('config_efi_capsule_cfg_file')[1:-1] + check_call('cd %s; ' + '%s/tools/mkeficapsule -f %s' + % (data_dir, u_boot_config.build_dir, cfg_file), shell=True) + # Create a disk image with EFI system partition check_call('virt-make-fs --partition=gpt --size=+1M --type=vfat %s %s' % (mnt_point, image_path), shell=True)

Hi Sughosh,
On Fri, 19 Apr 2024 at 00:56, Sughosh Ganu sughosh.ganu@linaro.org wrote:
Add support for generating capsules through a config file. Multiple capsules can be generated through a single invocation of the mkeficapsule tool, where the capsule parameters are fetched from the config file.
The format of the config file pretty similar to the one used by the capsule generation tool in EDKII.
The functionality is enabled on the sandbox variant and the EFI capsule update test is now configured to generate the capsules through a config file. The sandbox_flattree variant generates capsules through binman, thus covering both the paths.
This is a rebased version of the earlier patch series that was sent to the mailing list [1]. That series was not getting merged as Simon had asked for generation of capsules as part of the sandbox platform build. Those patches have since been merged. Moreover, there has been adoption of the capsule generation logic by TI platforms as well, so that should address the concern which Tom had about it's usage.
These patches are more of a step in the direction of adding support for generation of a multi-payload capsule. There has been a request for it's support for a platform [2].
[1] - https://lists.denx.de/pipermail/u-boot/2023-November/538223.html [2] - https://lists.denx.de/pipermail/u-boot/2024-March/548126.html
Sughosh Ganu (4): tools: mkeficapsule: Add support for parsing capsule params from 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
configs/sandbox_defconfig | 2 + doc/develop/uefi/uefi.rst | 70 ++++ .../test_efi_capsule/capsule_gen_binman.dts | 2 + test/py/tests/test_efi_capsule/conftest.py | 7 + .../test_efi_capsule/sandbox_capsule_cfg.txt | 73 ++++ tools/Kconfig | 15 + tools/Makefile | 1 + tools/eficapsule.h | 114 ++++++ tools/mkeficapsule.c | 87 +++-- tools/mkeficapsule_parse.c | 352 ++++++++++++++++++ 10 files changed, 692 insertions(+), 31 deletions(-) create mode 100644 test/py/tests/test_efi_capsule/sandbox_capsule_cfg.txt create mode 100644 tools/mkeficapsule_parse.c
Can you implement this in binman? I would like to avoid having a separate config file from the image description.
Regards, Simon
participants (3)
-
Heinrich Schuchardt
-
Simon Glass
-
Sughosh Ganu