Add a new fwumdata tool to allows users to read, display, and modify FWU (Firmware Update) metadata from Linux userspace. It provides functionality similar to fw_printenv/fw_setenv but for FWU metadata. Users can view metadata, change active/previous bank indices, modify bank states, and set image acceptance flags. Configuration is done via fwumdata.config file.
Signed-off-by: Kory Maincent <[email protected]> --- MAINTAINERS | 4 + doc/develop/uefi/fwu_updates.rst | 4 +- doc/fwumdata.1 | 222 ++++++++++ tools/.gitignore | 1 + tools/fwumdata_src/Kconfig | 11 + tools/fwumdata_src/fwumdata.c | 854 +++++++++++++++++++++++++++++++++++++ tools/fwumdata_src/fwumdata.config | 33 ++ tools/fwumdata_src/fwumdata.h | 138 ++++++ tools/fwumdata_src/fwumdata.mk | 3 + 9 files changed, 1269 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 5f560f4a94e..0ee5f6ea26f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1248,11 +1248,15 @@ F: drivers/watchdog/sbsa_gwdt.c FWU Multi Bank Update M: Sughosh Ganu <[email protected]> +M: Kory Maincent <[email protected]> S: Maintained T: git https://source.denx.de/u-boot/custodians/u-boot-efi.git +F: doc/fwumdata.1 F: doc/mkfwumdata.1 F: lib/fwu_updates/* F: drivers/fwu-mdata/* +F: tools/fwumdata_src/fwumdata.c +F: tools/fwumdata_src/fwumdata.h F: tools/fwumdata_src/mkfwumdata.c GATEWORKS_SC diff --git a/doc/develop/uefi/fwu_updates.rst b/doc/develop/uefi/fwu_updates.rst index 84713581459..c592106f8a8 100644 --- a/doc/develop/uefi/fwu_updates.rst +++ b/doc/develop/uefi/fwu_updates.rst @@ -66,7 +66,9 @@ FWU Metadata U-Boot supports both versions(1 and 2) of the FWU metadata defined in the two revisions of the specification. Support can be enabled for either of the two versions through a config flag. The mkfwumdata tool -can generate metadata for both the supported versions. +can generate metadata for both the supported versions. On the target side, +the fwumdata tool can read and update FWU metadata located in memory, +similarly to how fw_printenv/fw_setenv works. Setting up the device for GPT partitioned storage ------------------------------------------------- diff --git a/doc/fwumdata.1 b/doc/fwumdata.1 new file mode 100644 index 00000000000..66a53fc9403 --- /dev/null +++ b/doc/fwumdata.1 @@ -0,0 +1,222 @@ +.\" SPDX-License-Identifier: GPL-2.0-or-later +.\" Copyright (C) 2025 Kory Maincent <[email protected]> +.TH FWUMDATA 1 2025 U-Boot +.SH NAME +fwumdata \- read, display, and modify FWU metadata +. +.SH SYNOPSIS +.SY fwumdata +.OP \-c config +.OP \-l +.OP \-u +.OP \-a bankid +.OP \-p bankid +.RB [ \-s +.IR bankid " " state ] +.OP \-i imageid +.OP \-b bankid +.OP \-A +.OP \-C +.OP \-B num_banks +.OP \-I num_images +.YS +.SY fwumdata +.B \-h +.YS +. +.SH DESCRIPTION +.B fwumdata +reads, displays, and modifies FWU (Firmware Update) metadata from Linux +userspace. +.PP +The tool operates on FWU metadata stored on block or MTD devices, allowing +userspace manipulation of firmware update state including active bank +selection, image acceptance, and bank state management. +. +.SH OPTIONS +.TP +.BR \-c ", " \-\-config " \fIfile\fR" +Use custom configuration file. By default, the tool searches for +.I ./fwumdata.config +then +.IR /etc/fwumdata.config . +. +.TP +.BR \-l ", " \-\-list +Display detailed metadata information including all GUIDs, image entries, +and bank information. Without this option, only a summary is shown. +. +.TP +.BR \-u ", " \-\-update +Update metadata if CRC validation fails. Useful for recovering from corrupted +metadata. +. +.TP +.BR \-a ", " \-\-active " \fIbankid\fR" +Set the active bank index to +.IR bank . +. +.TP +.BR \-p ", " \-\-previous " \fIbankid\fR" +Set the previous active bank index to +.IR bank . +. +.TP +.BR \-s ", " \-\-state " \fIbankid state\fR" +Set bank index +.I bankid +to the specified +.IR state . +Valid states are: +.BR accepted , +.BR valid , +or +.BR invalid . +Supported only with version 2 metadata. When setting a bank to accepted state, +all firmware images in that bank are automatically marked as accepted. +. +.TP +.BR \-i ", " \-\-image " \fIimageid\fR" +Specify image number (used with +.B \-A +or +.BR \-C ). +. +.TP +.BR \-b ", " \-\-bank " \fIbankid\fR" +Specify bank number (used with +.B \-A +or +.BR \-C ). +. +.TP +.BR \-A ", " \-\-accept +Accept the image specified by +.B \-i +in the bank specified by +.BR \-b . +Sets the FWU_IMAGE_ACCEPTED flag for the image. +. +.TP +.BR \-C ", " \-\-clear +Clear the acceptance flag for the image specified by +.B \-i +in the bank specified by +.BR \-b . +According to the FWU specification, the bank state is automatically set to +invalid before clearing the acceptance flag. +. +.TP +.BR \-B ", " \-\-nbanks " \fInum_banks\fR" +Specify total number of banks (required for V1 metadata). +. +.TP +.BR \-I ", " \-\-nimages " \fInum_images\fR" +Specify total number of images (required for V1 metadata). +. +.TP +.BR \-h ", " \-\-help +Print usage information and exit. +. +.SH CONFIGURATION FILE +The configuration file specifies the location of FWU metadata on storage +devices. The format is: +.PP +.EX +.in +4 +# Device Name Device Offset Metadata Size Erase Size +/dev/mtd0 0x0 0x78 0x1000 +/dev/mtd1 0x0 0x78 0x1000 +.in +.EE +.PP +Lines starting with +.B # +are comments. +.I Erase Size +is optional and only applies to MTD devices; if omitted, it defaults to the +metadata size. +.PP +Specifying two devices enables redundant metadata support. +. +.SH BUGS +Please report bugs to the +.UR https://\:source\:.denx\:.de/\:u-boot/\:u-boot/\:issues +U-Boot bug tracker +.UE . +. +.SH EXAMPLES +Display FWU metadata summary: +.PP +.EX +.in +4 +$ \c +.B fwumdata +.in +.EE +.PP +Display detailed metadata with all GUIDs: +.PP +.EX +.in +4 +$ \c +.B fwumdata \-l +.in +.EE +.PP +Set active bank to 1: +.PP +.EX +.in +4 +$ \c +.B fwumdata \-a 1 +.in +.EE +.PP +Set bank 1 to accepted state (automatically accepts all images in that bank): +.PP +.EX +.in +4 +$ \c +.B fwumdata \-s 1 accepted +.in +.EE +.PP +Accept image 0 in bank 0: +.PP +.EX +.in +4 +$ \c +.B fwumdata \-i 0 \-b 0 \-A \-l +.in +.EE +.PP +Clear acceptance for image 0 in bank 1: +.PP +.EX +.in +4 +$ \c +.B fwumdata \-i 0 \-b 1 \-C \-l +.in +.EE +.PP +Clear acceptance for image 1 in bank 1 with metadata V1: +.PP +.EX +.in +4 +$ \c +.B fwumdata \-B 2 \-I 2 \-i 1 \-b 1 \-C \-l +.in +.EE +.PP +Use custom configuration file: +.PP +.EX +.in +4 +$ \c +.B fwumdata \-c /path/to/custom.config +.in +.EE +. +.SH SEE ALSO +.BR mkfwumdata (1) diff --git a/tools/.gitignore b/tools/.gitignore index e8daa24a52d..49943d2cf3a 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -11,6 +11,7 @@ /file2include /fit_check_sign /fit_info +/fwumdata /gdb/gdbcont /gdb/gdbsend /gen_eth_addr diff --git a/tools/fwumdata_src/Kconfig b/tools/fwumdata_src/Kconfig index c033c560e8d..af1f3bb3f57 100644 --- a/tools/fwumdata_src/Kconfig +++ b/tools/fwumdata_src/Kconfig @@ -6,3 +6,14 @@ config TOOLS_MKFWUMDATA metadata for initial installation of the FWU multi bank update on the board. The installation method depends on the platform. + +config TOOLS_FWUMDATA + bool "Build fwumdata command" + default y if FWU_MULTI_BANK_UPDATE + help + This command allows users to read, display, and modify FWU + (Firmware Update) metadata from Linux userspace. It provides + functionality similar to fw_printenv/fw_setenv but for FWU + metadata. Users can view metadata, change active/previous + bank indices, modify bank states, and set image acceptance + flags. Configuration is done via fwumdata.config file. diff --git a/tools/fwumdata_src/fwumdata.c b/tools/fwumdata_src/fwumdata.c new file mode 100644 index 00000000000..c5b0f56842d --- /dev/null +++ b/tools/fwumdata_src/fwumdata.c @@ -0,0 +1,854 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * FWU Metadata Read/Write Tool + * Copyright (c) 2025, Kory Maincent <[email protected]> + * + * Tool to read, display, and modify FWU (Firmware Update) metadata + * from Linux userspace. Similar to fw_printenv/fw_setenv for U-Boot + * environment, but for FWU metadata. + * + * Usage: + * fwumdata - Print all metadata + * fwumdata -u - Print metadata and update it if CRC corrupted + * fwumdata -c <config> - Use custom config file + * fwumdata -a <bank> - Set active bank + * fwumdata -p <bank> - Set previous bank + * fwumdata -s <bank> <state> - Set bank state (V2 only) + * fwumdata -i <id> -b <bank> -A - Accept image + * fwumdata -i <id> -b <bank> -C - Clear image acceptance + * fwumdata -i <id> -b <bank> + * -B <num_banks> + * -I <num_images> -C - Clear image acceptance (V1 only) + * fwumdata -l - List detailed info with GUIDs + */ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <unistd.h> +#include <mtd/mtd-user.h> +#include <sys/ioctl.h> +#include <u-boot/crc.h> +#include "fwumdata.h" + +/* Device configuration */ +struct fwumdata_device { + const char *devname; + long long devoff; + unsigned long mdata_size; + unsigned long erase_size; + int fd; + bool is_mtd; +}; + +/* Global state */ +static struct fwumdata_device devices[2]; /* Primary and secondary */ +static struct fwu_mdata *mdata; +static int have_redundant; +static struct fwu_mdata *valid_mdata; +static bool mdata_mod; +static const char *config_file; +static int nbanks, nimages; /* For V1 only */ +static const char * const default_config_files[] = { + "./fwumdata.config", + "/etc/fwumdata.config", + NULL +}; + +/* GUID/UUID utilities */ +static void guid_to_string(const struct efi_guid *guid, char *str) +{ + sprintf(str, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid->time_high, guid->time_low, guid->reserved, + guid->family, guid->node[0], + guid->node[1], guid->node[2], guid->node[3], + guid->node[4], guid->node[5], guid->node[6]); +} + +/* Config file parsing */ +static int parse_config(const char *fname) +{ + size_t linesize = 0; + char *line = NULL; + char *devname; + int i = 0; + FILE *fp; + int rc; + + fp = fopen(fname, "r"); + if (!fp) + return -ENOENT; + + while (i < 2 && getline(&line, &linesize, fp) != -1) { + /* Skip comments and empty lines */ + if (line[0] == '#' || line[0] == '\n') + continue; + + rc = sscanf(line, "%ms %lli %lx %lx", + &devname, + &devices[i].devoff, + &devices[i].mdata_size, + &devices[i].erase_size); + + if (rc < 3) { + free(devname); + continue; + } + + if (rc < 4) + devices[i].erase_size = devices[i].mdata_size; + + devices[i].devname = devname; + i++; + } + + free(line); + fclose(fp); + + if (i == 2) { + have_redundant = true; + if (devices[0].mdata_size != devices[1].mdata_size) { + fprintf(stderr, + "Size mismatch between the two metadata\n"); + return -EINVAL; + } + } + + if (!i) { + fprintf(stderr, + "Can't read config %s content\n", fname); + return -EINVAL; + } + + return 0; +} + +static int find_parse_config(void) +{ + int i; + + if (config_file) + return parse_config(config_file); + + for (i = 0; default_config_files[i]; i++) { + int ret; + + ret = parse_config(default_config_files[i]); + if (ret == -ENOENT) + continue; + if (ret) + return ret; + + config_file = default_config_files[i]; + return 0; + } + + fprintf(stderr, "Error: Cannot find config file\n"); + return -ENOENT; +} + +static int open_device(struct fwumdata_device *dev) +{ + if (strstr(dev->devname, "/dev/mtd")) + dev->is_mtd = true; + + dev->fd = open(dev->devname, O_RDWR | O_SYNC); + if (dev->fd < 0) { + fprintf(stderr, "Cannot open %s: %s\n", dev->devname, + strerror(errno)); + return -ENODEV; + } + + return 0; +} + +static int mtd_erase(int fd, unsigned long offset, unsigned long size) +{ + struct erase_info_user erase; + int ret; + + erase.start = offset; + erase.length = size; + + ret = ioctl(fd, MEMERASE, &erase); + if (ret < 0) { + fprintf(stderr, "MTD erase failed: %s\n", strerror(errno)); + return -errno; + } + + return 0; +} + +static int read_device(struct fwumdata_device *dev, void *buf, size_t count) +{ + if (lseek(dev->fd, dev->devoff, SEEK_SET) < 0) { + fprintf(stderr, "Seek failed: %s\n", strerror(errno)); + return -errno; + } + + if (read(dev->fd, buf, count) < 0) { + fprintf(stderr, "Read failed: %s\n", strerror(errno)); + return -errno; + } + + return 0; +} + +static int write_device(struct fwumdata_device *dev, const void *buf, + size_t count) +{ + int ret; + + /* Erase if MTD device */ + if (dev->is_mtd) { + ret = mtd_erase(dev->fd, dev->devoff, dev->erase_size); + if (ret) + return ret; + } + + if (lseek(dev->fd, dev->devoff, SEEK_SET) < 0) { + fprintf(stderr, "Seek failed: %s\n", strerror(errno)); + return -errno; + } + + if (write(dev->fd, buf, count) < 0) { + fprintf(stderr, "Write failed: %s\n", strerror(errno)); + return -errno; + } + + return 0; +} + +/* Metadata operations */ +static int validate_crc(struct fwu_mdata *mdata, size_t size) +{ + u32 calc_crc, stored_crc; + + stored_crc = mdata->crc32; + calc_crc = crc32(0, (const u8 *)&mdata->version, size - sizeof(u32)); + + if (calc_crc != stored_crc) { + fprintf(stderr, + "CRC mismatch: calculated 0x%08x, stored 0x%08x\n", + calc_crc, stored_crc); + if (mdata->version == 1) + fprintf(stderr, + "Metadata is V1, this may be size description issue\n"); + return -1; + } + + return 0; +} + +static void update_crc(struct fwu_mdata *mdata, size_t size) +{ + mdata->crc32 = crc32(0, (const u8 *)&mdata->version, size - sizeof(u32)); +} + +static int read_one_metadata(int mdata_id, size_t size) +{ + int ret; + + ret = open_device(&devices[mdata_id]); + if (ret) + return ret; + + ret = read_device(&devices[mdata_id], &mdata[mdata_id], size); + if (ret) + return ret; + + if (mdata[mdata_id].version != 1 && mdata[mdata_id].version != 2) { + fprintf(stderr, "Invalid metadata %d version: %u\n", + mdata_id, mdata[mdata_id].version); + } + + return 0; +} + +static int read_metadata(bool update) +{ + size_t alloc_size; + int ret; + + /* Allocate initial buffer */ + alloc_size = devices[0].mdata_size; + mdata = calloc(have_redundant ? 2 : 1, alloc_size); + if (!mdata) { + fprintf(stderr, "Memory allocation failed\n"); + return -ENOMEM; + } + + ret = read_one_metadata(0, alloc_size); + if (ret) + return ret; + + if (validate_crc(&mdata[0], alloc_size) < 0) { + fprintf(stderr, + "Warning: Primary metadata CRC validation failed\n"); + mdata_mod = update; + } else { + valid_mdata = &mdata[0]; + } + + if (have_redundant) { + ret = read_one_metadata(1, alloc_size); + if (ret) + return ret; + + if (validate_crc(&mdata[1], alloc_size) < 0) { + fprintf(stderr, + "Warning: Secondary metadata CRC validation failed\n"); + mdata_mod = update; + } else if (valid_mdata && mdata[0].crc32 != mdata[1].crc32) { + fprintf(stderr, + "Metadatas valid but not equal, use first one as default\n"); + mdata_mod = update; + } else { + valid_mdata = &mdata[1]; + } + } + + if (!valid_mdata) { + fprintf(stderr, + "No metadata valid, use first one as default\n"); + mdata_mod = update; + valid_mdata = &mdata[0]; + } + + if (valid_mdata->version == 2) { + struct fwu_mdata_ext *mdata_ext; + + mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); + if (mdata_ext->metadata_size != alloc_size) { + fprintf(stderr, + "Metadata real size 0x%x mismatch with the config 0x%zx\n", + mdata_ext->metadata_size, alloc_size); + return -EINVAL; + } + } + + return 0; +} + +static int write_metadata(void) +{ + size_t write_size = devices[0].mdata_size; + int ret; + + if (!mdata_mod) + return 0; + + /* Update CRC */ + update_crc(valid_mdata, write_size); + + /* Write primary */ + ret = write_device(&devices[0], valid_mdata, write_size); + if (ret < 0) { + fprintf(stderr, "Failed to write primary metadata\n"); + return ret; + } + + /* Write secondary if redundant */ + if (have_redundant) { + ret = write_device(&devices[1], valid_mdata, write_size); + if (ret < 0) { + fprintf(stderr, "Failed to write secondary metadata\n"); + return -1; + } + } + + printf("FWU metadata updated successfully\n"); + mdata_mod = 0; + + return 0; +} + +/* Display functions */ +static const char *bank_state_to_string(u8 state) +{ + switch (state) { + case FWU_BANK_ACCEPTED: + return "accepted"; + case FWU_BANK_VALID: + return "valid"; + case FWU_BANK_INVALID: + return "invalid"; + default: + return "unknown"; + } +} + +static void print_metadata_summary(void) +{ + int i; + + printf("FWU Metadata:\n"); + printf("\tVersion: %u\n", valid_mdata->version); + printf("\tActive Index: %u\n", valid_mdata->active_index); + printf("\tPrevious Index: %u\n", valid_mdata->previous_active_index); + printf("\tCRC32: 0x%08x\n", valid_mdata->crc32); + + if (valid_mdata->version == 2) { + struct fwu_fw_store_desc *fw_desc; + struct fwu_mdata_ext *mdata_ext; + + mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); + printf("\tMetadata Size: %u bytes\n", mdata_ext->metadata_size); + printf("\tDescriptor Offset: %u\n", mdata_ext->desc_offset); + printf("\tBank States:\n"); + + fw_desc = fwu_get_fw_desc(valid_mdata); + for (i = 0; i < fw_desc->num_banks && i < MAX_BANKS_V2; i++) { + printf("\t\tBank %d: %s (0x%02x)\n", i, + bank_state_to_string(mdata_ext->bank_state[i]), + mdata_ext->bank_state[i]); + } + } +} + +static void print_metadata_detailed(void) +{ + struct fwu_fw_store_desc *fw_desc = NULL; + struct fwu_image_bank_info *bank_info; + struct fwu_image_entry *img_entry; + int num_images, num_banks; + char guid_str[64]; + int i, j; + + print_metadata_summary(); + + if (valid_mdata->version == 1) { + num_images = nimages; + num_banks = nbanks; + } else { + fw_desc = fwu_get_fw_desc(valid_mdata); + num_images = fw_desc->num_images; + num_banks = fw_desc->num_banks; + } + + if (fw_desc) { + printf("\n\tFirmware Store Descriptor:\n"); + printf("\t\tNumber of Banks: %u\n", num_banks); + printf("\t\tNumber of Images: %u\n", num_images); + printf("\t\tImage Entry Size: %u\n", fw_desc->img_entry_size); + printf("\t\tBank Info Entry Size: %u\n", fw_desc->bank_info_entry_size); + } + + printf("\n\tImages:\n"); + for (i = 0; i < num_images; i++) { + img_entry = fwu_get_image_entry(valid_mdata, valid_mdata->version, + num_banks, i); + + printf("\t\tImage %d:\n", i); + + guid_to_string(&img_entry->image_type_guid, guid_str); + printf("\t\t\tImage Type GUID: %s\n", guid_str); + + guid_to_string(&img_entry->location_guid, guid_str); + printf("\t\t\tLocation GUID: %s\n", guid_str); + + printf("\t\t\tBanks:\n"); + for (j = 0; j < num_banks; j++) { + bank_info = fwu_get_bank_info(valid_mdata, + valid_mdata->version, + num_banks, i, j); + + guid_to_string(&bank_info->image_guid, guid_str); + printf("\t\t\t\tBank %d:\n", j); + printf("\t\t\t\t\tImage GUID: %s\n", guid_str); + printf("\t\t\t\t\tAccepted: %s (%u)\n", + (bank_info->accepted & FWU_IMAGE_ACCEPTED) ? "yes" : "no", + bank_info->accepted); + } + } +} + +/* Modification functions */ +static int set_active_index(int bank) +{ + struct fwu_fw_store_desc *fw_desc; + int num_banks; + + if (valid_mdata->version == 2) { + fw_desc = fwu_get_fw_desc(valid_mdata); + num_banks = fw_desc->num_banks; + } else { + num_banks = nbanks; + } + + if (bank < 0 || bank >= num_banks) { + fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", + bank, num_banks - 1); + return -EINVAL; + } + + if (valid_mdata->active_index == bank) + return 0; + + valid_mdata->active_index = bank; + mdata_mod = 1; + + printf("Active bank set to %d\n", bank); + return 0; +} + +static int set_previous_index(int bank) +{ + struct fwu_fw_store_desc *fw_desc; + int num_banks; + + if (valid_mdata->version == 2) { + fw_desc = fwu_get_fw_desc(valid_mdata); + num_banks = fw_desc->num_banks; + } else { + num_banks = nbanks; + } + + if (bank < 0 || bank >= num_banks) { + fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", + bank, num_banks - 1); + return -EINVAL; + } + + if (valid_mdata->previous_active_index == bank) + return 0; + + valid_mdata->previous_active_index = bank; + mdata_mod = 1; + + printf("Previous bank set to %d\n", bank); + return 0; +} + +static int set_image_accepted(int image, int bank, int accept) +{ + struct fwu_image_bank_info *bank_info; + int num_images, num_banks; + + if (valid_mdata->version == 1) { + num_images = nimages; + num_banks = nbanks; + } else { + struct fwu_fw_store_desc *fw_desc; + + fw_desc = fwu_get_fw_desc(valid_mdata); + num_images = fw_desc->num_images; + num_banks = fw_desc->num_banks; + } + + if (bank < 0 || bank >= num_banks) { + fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", + bank, num_banks - 1); + return -EINVAL; + } + + if (image < 0 || image >= num_images) { + fprintf(stderr, "Error: Invalid image %d (must be 0-%d)\n", + image, num_images - 1); + return -EINVAL; + } + + bank_info = fwu_get_bank_info(valid_mdata, valid_mdata->version, + num_banks, image, bank); + if (accept == bank_info->accepted) + return 0; + + if (accept) { + bank_info->accepted = FWU_IMAGE_ACCEPTED; + } else { + bank_info->accepted = 0; + + /* According to the spec: bank_state[index] have to be set + * to invalid before any content in the img_bank_info[index] + * is overwritten. + */ + if (valid_mdata->version == 2) { + struct fwu_mdata_ext *mdata_ext; + + mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); + mdata_ext->bank_state[bank] = FWU_BANK_INVALID; + } + } + + mdata_mod = 1; + printf("Image %d in bank %d: acceptance %s\n", + image, bank, accept ? "set" : "cleared"); + + return 0; +} + +static int set_bank_state(int bank, const char *state_str) +{ + struct fwu_fw_store_desc *fw_desc; + struct fwu_mdata_ext *mdata_ext; + u8 state; + int i; + + if (valid_mdata->version != 2) { + fprintf(stderr, + "Error: Bank state is only supported in V2 metadata\n"); + return -EINVAL; + } + + fw_desc = fwu_get_fw_desc(valid_mdata); + mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); + + if (bank < 0 || bank >= fw_desc->num_banks || bank >= MAX_BANKS_V2) { + fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", + bank, fw_desc->num_banks - 1); + return -EINVAL; + } + + /* Parse state string */ + if (!strcmp(state_str, "accepted")) { + state = FWU_BANK_ACCEPTED; + } else if (!strcmp(state_str, "valid")) { + state = FWU_BANK_VALID; + } else if (!strcmp(state_str, "invalid")) { + state = FWU_BANK_INVALID; + } else { + fprintf(stderr, + "Error: Invalid state '%s' (must be accepted/valid/invalid)\n", + state_str); + return -EINVAL; + } + + if (mdata_ext->bank_state[bank] == state) + return 0; + + /* If a bank is set in a accepted state all firmware images in + * that bank must be marked as accepted as described in the spec. + */ + if (state == FWU_BANK_ACCEPTED) { + for (i = 0; i < fw_desc->num_images; i++) { + int ret; + + ret = set_image_accepted(i, bank, true); + if (ret) + return ret; + } + } + mdata_ext->bank_state[bank] = state; + mdata_mod = 1; + + printf("Bank %d state set to %s (0x%02x)\n", bank, state_str, state); + return 0; +} + +static int metadata_v1_validate_size(void) +{ + int calc_size; + + calc_size = sizeof(struct fwu_mdata) + + (sizeof(struct fwu_image_entry) + + sizeof(struct fwu_image_bank_info) * nbanks) * nimages; + + if (devices[0].mdata_size != calc_size) { + fprintf(stderr, + "Metadata calculate size (-B and -I options) 0x%x mismatch with the config 0x%zx\n", + calc_size, devices[0].mdata_size); + return -EINVAL; + } + + return 0; +} + +/* Command-line interface */ +static void print_usage(void) +{ + fprintf(stderr, "Usage: fwumdata [options]\n\n"); + fprintf(stderr, "Options:\n" + "\t-c, --config <file> Use custom config file, defaults:\n" + "\t ./fwumdata.config or /etc/fwumdata.config\n" + "\t-l, --list List detailed metadata with GUIDs\n" + "\t-a, --active <bank> Set active bank index\n" + "\t-p, --previous <bank> Set previous bank index\n" + "\t-s, --state <bank> <state> Set bank state (V2 only)\n" + "\t state: accepted|valid|invalid\n" + "\t-i, --image <id> Image number (for -A/-C)\n" + "\t-b, --bank <bank> Bank number (for -A/-C)\n" + "\t-A, --accept Accept image (requires -i and -b)\n" + "\t-C, --clear Clear image acceptance (requires -i and -b)\n" + "\t-u, --update Update metadata if there is a checksum issue\n" + "\t-B, --nbanks <num_banks> Number of banks (required for V1 metadata)\n" + "\t-I, --nimages <num_images> Number of images (required for V1 metadata)\n" + "\t-h, --help Print this help\n\n"); + fprintf(stderr, "Config file format (fwumdata.config):\n" + "\t# Device Name Device Offset Metadata Size Erase Size\n" + "\t/dev/mtd0 0x0 0x78 0x1000\n" + "\t/dev/mtd1 0x0 0x78 0x1000\n\n"); + fprintf(stderr, "Examples:\n" + "\tfwumdata # Print metadata summary\n" + "\tfwumdata -l # Print detailed metadata\n" + "\tfwumdata -a 1 # Set active bank to 1\n" + "\tfwumdata -s 1 accepted # Set bank 1 to accepted state\n" + "\tfwumdata -i 0 -b 0 -A # Accept image in bank 0\n" + "\tfwumdata -B 2 -I 2 -i 1 -b 1 -A -l # Accept image 1 in bank 1 with metadata V1\n"); +} + +int main(int argc, char *argv[]) +{ + char *bank_state_str = NULL; + bool list_detailed = false; + int bank_state_num = -1; + int active_index = -1; + int bank_id = -1; + int prev_index = -1; + bool do_accept = 0; + bool do_clear = 0; + bool do_update = 0; + int image_id = -1; + int ret = 0; + int opt; + + static struct option long_options[] = { + {"config", required_argument, 0, 'c'}, + {"list", no_argument, 0, 'l'}, + {"active", required_argument, 0, 'a'}, + {"previous", required_argument, 0, 'p'}, + {"state", required_argument, 0, 's'}, + {"image", required_argument, 0, 'i'}, + {"bank", required_argument, 0, 'b'}, + {"accept", no_argument, 0, 'A'}, + {"clear", no_argument, 0, 'C'}, + {"update", no_argument, 0, 'u'}, + {"nbanks", required_argument, 0, 'B'}, + {"nimages", required_argument, 0, 'I'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + /* Parse arguments */ + while ((opt = getopt_long(argc, argv, "c:la:p:s:i:b:ACuB:I:h", long_options, NULL)) != -1) { + switch (opt) { + case 'c': + config_file = optarg; + break; + case 'l': + list_detailed = 1; + break; + case 'a': + active_index = atoi(optarg); + break; + case 'p': + prev_index = atoi(optarg); + break; + case 's': + bank_state_num = atoi(optarg); + if (optind < argc && argv[optind][0] != '-') { + bank_state_str = argv[optind++]; + } else { + fprintf(stderr, + "Error: -s requires bank number and state\n"); + return 1; + } + break; + case 'i': + image_id = atoi(optarg); + break; + case 'b': + bank_id = atoi(optarg); + break; + case 'A': + do_accept = 1; + break; + case 'C': + do_clear = 1; + break; + case 'u': + do_update = 1; + break; + case 'B': + nbanks = atoi(optarg); + break; + case 'I': + nimages = atoi(optarg); + break; + case 'h': + print_usage(); + return 0; + default: + print_usage(); + return 1; + } + } + + ret = find_parse_config(); + if (ret < 0) { + fprintf(stderr, "Error: Cannot read configuration\n"); + return ret; + } + + ret = read_metadata(do_update); + if (ret < 0) { + fprintf(stderr, "Error: Cannot read metadata\n"); + goto cleanup; + } + + if (valid_mdata->version == 1) { + ret = metadata_v1_validate_size(); + if (ret) + goto cleanup; + } + + /* Perform operations */ + if (active_index >= 0) { + ret = set_active_index(active_index); + if (ret < 0) + goto cleanup; + } + + if (prev_index >= 0) { + ret = set_previous_index(prev_index); + if (ret < 0) + goto cleanup; + } + + if (do_accept || do_clear) { + if (image_id < 0 || bank_id < 0) { + fprintf(stderr, + "Error: -A/-C requires both -i <guid> and -b <bank>\n"); + ret = -EINVAL; + goto cleanup; + } + + ret = set_image_accepted(image_id, bank_id, do_accept); + if (ret < 0) + goto cleanup; + } + + if (bank_state_num >= 0 && bank_state_str) { + ret = set_bank_state(bank_state_num, bank_state_str); + if (ret < 0) + goto cleanup; + } + + /* Write back if modified */ + if (mdata_mod) { + ret = write_metadata(); + if (ret) + goto cleanup; + } + + /* Display metadata if no modifications or list requested */ + if (list_detailed) + print_metadata_detailed(); + else + print_metadata_summary(); + +cleanup: + /* Close devices and free memory */ + if (devices[0].fd) + close(devices[0].fd); + if (devices[1].fd) + close(devices[1].fd); + + free(mdata); + + for (int i = 0; i < 2; i++) { + if (devices[i].devname) + free((void *)devices[i].devname); + } + + return ret; +} diff --git a/tools/fwumdata_src/fwumdata.config b/tools/fwumdata_src/fwumdata.config new file mode 100644 index 00000000000..7e83f7a5909 --- /dev/null +++ b/tools/fwumdata_src/fwumdata.config @@ -0,0 +1,33 @@ +# FWU Metadata Configuration File +# +# Format: <device> <offset> <metadata_size> <erase_size> +# +# This file describes where the FWU metadata is stored. You can specify +# up to two entries for redundant metadata copies. +# +# Device: MTD device (/dev/mtdX), block device (/dev/mmcblkX), or file path +# Offset: Byte offset from start of device (hex with 0x prefix) +# Metadata Size: Size of metadata structure in bytes (hex with 0x prefix) +# Erase Size: Sector/erase block size (hex with 0x prefix, defaults to +# metadata_size, required only for MTD device) +# +# Examples: +# +# MTD devices (NOR/NAND flash): +# /dev/mtd0 0x0 0x1000 0x1000 +# /dev/mtd1 0x0 0x1000 0x1000 +# +# Block device (eMMC/SD): +# /dev/mmcblk0 0x100000 0x78 +# /dev/mmcblk0 0x101000 0x78 +# +# or: +# /dev/disk/by-partlabel/metadata1 0 0x78 +# /dev/disk/by-partlabel/metadata2 0 0x78 +# +# Regular file: +# /boot/fwu-mdata.bin 0x0 0x78 +# +# Default configuration (update for your platform): +/dev/mtd0 0x0 0x78 0x1000 +/dev/mtd1 0x0 0x78 0x1000 diff --git a/tools/fwumdata_src/fwumdata.h b/tools/fwumdata_src/fwumdata.h new file mode 100644 index 00000000000..5e2c45d0fb0 --- /dev/null +++ b/tools/fwumdata_src/fwumdata.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025, Kory Maincent <[email protected]> + */ + +#ifndef _FWUMDATA_H_ +#define _FWUMDATA_H_ + +#include <linux/compiler_attributes.h> + +/* Type definitions for U-Boot compatibility */ +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +/* FWU Constants */ +#define FWU_IMAGE_ACCEPTED 0x1 +#define FWU_BANK_INVALID (uint8_t)0xFF +#define FWU_BANK_VALID (uint8_t)0xFE +#define FWU_BANK_ACCEPTED (uint8_t)0xFC +#define MAX_BANKS_V2 4 + +/* EFI GUID structure */ +struct efi_guid { + u32 time_high; + u16 time_low; + u16 reserved; + u8 family; + u8 node[7]; +} __packed; + +/* FWU Metadata structures */ +struct fwu_image_bank_info { + struct efi_guid image_guid; + u32 accepted; + u32 reserved; +} __packed; + +struct fwu_image_entry { + struct efi_guid image_type_guid; + struct efi_guid location_guid; + struct fwu_image_bank_info img_bank_info[0]; /* Variable length */ +} __packed; + +struct fwu_fw_store_desc { + u8 num_banks; + u8 reserved; + u16 num_images; + u16 img_entry_size; + u16 bank_info_entry_size; + struct fwu_image_entry img_entry[0]; /* Variable length */ +} __packed; + +struct fwu_mdata { + u32 crc32; + u32 version; + u32 active_index; + u32 previous_active_index; + /* Followed by image entries or fwu_mdata_ext */ +} __packed; + +struct fwu_mdata_ext { /* V2 only */ + u32 metadata_size; + u16 desc_offset; + u16 reserved1; + u8 bank_state[4]; + u32 reserved2; +} __packed; + +/* Metadata access helpers */ +struct fwu_image_entry *fwu_get_image_entry(struct fwu_mdata *mdata, + int version, int num_banks, + int img_id) +{ + size_t offset; + + if (version == 1) { + offset = sizeof(struct fwu_mdata) + + (sizeof(struct fwu_image_entry) + + sizeof(struct fwu_image_bank_info) * num_banks) * img_id; + } else { + /* V2: skip fwu_fw_store_desc header */ + offset = sizeof(struct fwu_mdata) + + sizeof(struct fwu_mdata_ext) + + sizeof(struct fwu_fw_store_desc) + + (sizeof(struct fwu_image_entry) + + sizeof(struct fwu_image_bank_info) * num_banks) * img_id; + } + + return (struct fwu_image_entry *)((char *)mdata + offset); +} + +struct fwu_image_bank_info *fwu_get_bank_info(struct fwu_mdata *mdata, + int version, int num_banks, + int img_id, int bank_id) +{ + size_t offset; + + if (version == 1) { + offset = sizeof(struct fwu_mdata) + + (sizeof(struct fwu_image_entry) + + sizeof(struct fwu_image_bank_info) * num_banks) * img_id + + sizeof(struct fwu_image_entry) + + sizeof(struct fwu_image_bank_info) * bank_id; + } else { + offset = sizeof(struct fwu_mdata) + + sizeof(struct fwu_mdata_ext) + + sizeof(struct fwu_fw_store_desc) + + (sizeof(struct fwu_image_entry) + + sizeof(struct fwu_image_bank_info) * num_banks) * img_id + + sizeof(struct fwu_image_entry) + + sizeof(struct fwu_image_bank_info) * bank_id; + } + + return (struct fwu_image_bank_info *)((char *)mdata + offset); +} + +struct fwu_fw_store_desc *fwu_get_fw_desc(struct fwu_mdata *mdata) +{ + size_t offset; + + offset = sizeof(struct fwu_mdata) + + sizeof(struct fwu_mdata_ext); + + return (struct fwu_fw_store_desc *)((char *)mdata + offset); +} + +struct fwu_mdata_ext *fwu_get_fw_mdata_ext(struct fwu_mdata *mdata) +{ + size_t offset; + + offset = sizeof(struct fwu_mdata); + + return (struct fwu_mdata_ext *)((char *)mdata + offset); +} + +#endif /* _FWUMDATA_H_ */ diff --git a/tools/fwumdata_src/fwumdata.mk b/tools/fwumdata_src/fwumdata.mk index 2ce618ef2ed..2199e43b372 100644 --- a/tools/fwumdata_src/fwumdata.mk +++ b/tools/fwumdata_src/fwumdata.mk @@ -5,3 +5,6 @@ mkfwumdata-objs := fwumdata_src/mkfwumdata.o generated/lib/crc32.o HOSTLDLIBS_mkfwumdata += -luuid hostprogs-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata + +fwumdata-objs := fwumdata_src/fwumdata.o generated/lib/crc32.o +hostprogs-$(CONFIG_TOOLS_FWUMDATA) += fwumdata -- 2.43.0

