[PATCH 5/6] tools: Add support for fwumdata tool

Kory Maincent kory.maincent at bootlin.com
Tue Dec 2 18:28:30 CET 2025


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 <kory.maincent at bootlin.com>
---
 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 <sughosh.ganu at linaro.org>
+M:	Kory Maincent <kory.maincent at bootlin.com>
 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 <kory.maincent at bootlin.com>
+.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 <kory.maincent at bootlin.com>
+ *
+ * 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 <kory.maincent at bootlin.com>
+ */
+
+#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



More information about the U-Boot mailing list