On Mon, Feb 16, 2026 at 02:35:35PM +0100, Kory Maincent wrote:
> 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]>
> ---

I am not sure if this has been discussed with Ilias earlier, and
apologies if it has been, but I do think that this patch is not
adhering to the DEN0118 specification, specifically the part mentioned
in section A3.2.1, which says that the metadata is to be maintained by
the Update Agent. I would like to hear from Jose Marinho, who is the
author of the spec, on what he thinks about this approach.

I do think that the other patches in series look fine, and can be
applied. There is just an issue of inclusion of the tool for
building. I will comment on that patch separately.

-sughosh


>  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     |   5 +-
>  9 files changed, 1270 insertions(+), 2 deletions(-)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9d954be4a9d..d680b193033 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1242,11 +1242,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 00f4ae50dbb..2199e43b372 100644
> --- a/tools/fwumdata_src/fwumdata.mk
> +++ b/tools/fwumdata_src/fwumdata.mk
> @@ -4,4 +4,7 @@
>  
>  mkfwumdata-objs := fwumdata_src/mkfwumdata.o generated/lib/crc32.o
>  HOSTLDLIBS_mkfwumdata += -luuid
> -hostprogs-always-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata
> +hostprogs-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata
> +
> +fwumdata-objs := fwumdata_src/fwumdata.o generated/lib/crc32.o
> +hostprogs-$(CONFIG_TOOLS_FWUMDATA) += fwumdata
> 
> -- 
> 2.43.0
> 

-sughosh

Reply via email to