[PATCH 4/8] cmd: socfpga: Add rsu command for Remote System Update

dinesh.maniyam at altera.com dinesh.maniyam at altera.com
Tue Jun 30 08:55:20 CEST 2026


From: Dinesh Maniyam <dinesh.maniyam at altera.com>

Add the "rsu" command exposing the RSU core library to the user: list
and inspect flash slots, manage and program configuration slots, trigger
firmware updates, query DCMF version/status and max_retry, and maintain
the sub-partition table (SPT) and configuration pointer block (CPB).

Add a sandbox unit test covering the usage and argument-parsing paths,
command documentation under doc/usage/cmd/rsu.rst, and a MAINTAINERS
entry for the RSU files not already covered by the SoCFPGA globs. Enable
CMD_SOCFPGA_RSU in the agilex5, agilex, n5x and stratix10 defconfigs.

Signed-off-by: Dinesh Maniyam <dinesh.maniyam at altera.com>
---
 MAINTAINERS                         |    6 +
 cmd/Makefile                        |    1 +
 cmd/socfpga_rsu.c                   | 1048 +++++++++++++++++++++++++++
 configs/socfpga_agilex5_defconfig   |    2 +-
 configs/socfpga_agilex_defconfig    |    2 +-
 configs/socfpga_n5x_defconfig       |    2 +
 configs/socfpga_stratix10_defconfig |    2 +
 doc/usage/cmd/rsu.rst               |  208 ++++++
 test/cmd/Makefile                   |    1 +
 test/cmd/socfpga_rsu.c              |   59 ++
 10 files changed, 1329 insertions(+), 2 deletions(-)
 create mode 100644 cmd/socfpga_rsu.c
 create mode 100644 doc/usage/cmd/rsu.rst
 create mode 100644 test/cmd/socfpga_rsu.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 571af196465..ce248c58b4d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -169,10 +169,16 @@ T:	git https://source.denx.de/u-boot/custodians/u-boot-socfpga.git
 F:	arch/arm/dts/socfpga_*
 F:	arch/arm/mach-socfpga/
 F:	board/intel/agilex-socdk/
+F:	cmd/socfpga_rsu.c
 F:	configs/socfpga_*
+F:	doc/device-tree-bindings/misc/altr,socfpga-rsu.yaml
+F:	doc/usage/cmd/rsu.rst
 F:	drivers/ddr/altera/
+F:	drivers/misc/socfpga_rsu.c
 F:	drivers/power/domain/altr-pmgr-agilex5.c
 F:	drivers/sysreset/sysreset_socfpga*
+F:	include/rsu_console.h
+F:	test/cmd/socfpga_rsu.c
 
 ARM AMLOGIC SOC SUPPORT
 M:	Neil Armstrong <neil.armstrong at linaro.org>
diff --git a/cmd/Makefile b/cmd/Makefile
index bbbdfcc4ded..647f83a6263 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -90,6 +90,7 @@ obj-$(CONFIG_CMD_FPGA) += fpga.o
 obj-$(CONFIG_CMD_FPGAD) += fpgad.o
 obj-$(CONFIG_CMD_FS_GENERIC) += fs.o
 obj-$(CONFIG_CMD_FUSE) += fuse.o
+obj-$(CONFIG_CMD_SOCFPGA_RSU) += socfpga_rsu.o
 obj-$(CONFIG_CMD_FWU_METADATA) += fwu_mdata.o
 obj-$(CONFIG_CMD_GETTIME) += gettime.o
 obj-$(CONFIG_CMD_GPIO) += gpio.o
diff --git a/cmd/socfpga_rsu.c b/cmd/socfpga_rsu.c
new file mode 100644
index 00000000000..47d28bc7d08
--- /dev/null
+++ b/cmd/socfpga_rsu.c
@@ -0,0 +1,1048 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 Altera Corporation <www.altera.com>
+ *
+ * SoC FPGA RSU console command (Stratix 10 / Agilex family).
+ */
+
+#include <command.h>
+#include <limits.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <rsu_console.h>
+#include <vsprintf.h>
+#include <asm/arch/rsu.h>
+
+/*
+ * Strictly parse a numeric argv[] value.
+ *
+ * U-Boot's dectoul()/simple_strtoul*() silently return partial values
+ * for "12xyz" and wrap on u64 overflow without indication; either could
+ * feed a malformed argv into slot tables or pointers. Parse digit-by-
+ * digit so we can reject both, and allow a single trailing '\n'.
+ */
+static int rsu_parse_num(const char *s, unsigned int base, u64 *out)
+{
+	const char *start;
+	u64 result = 0;
+
+	if (!s || !*s || !out)
+		return -EINVAL;
+	if (base != 10 && base != 16)
+		return -EINVAL;
+
+	/* Accept an optional "0x" prefix for base 16 (matches U-Boot usage). */
+	if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
+		s += 2;
+
+	start = s;
+	while (*s) {
+		unsigned int digit;
+		char c = *s;
+
+		if (c >= '0' && c <= '9')
+			digit = c - '0';
+		else if (c >= 'a' && c <= 'f')
+			digit = c - 'a' + 10;
+		else if (c >= 'A' && c <= 'F')
+			digit = c - 'A' + 10;
+		else
+			break;
+
+		if (digit >= base)
+			break;
+
+		/* Pre-division overflow guard: result*base + digit must fit a u64. */
+		if (result > (ULLONG_MAX - digit) / base)
+			return -ERANGE;
+
+		result = result * base + digit;
+		s++;
+	}
+
+	if (s == start)
+		return -EINVAL;
+	if (*s != '\0' && !(*s == '\n' && s[1] == '\0'))
+		return -EINVAL;
+
+	*out = result;
+	return 0;
+}
+
+/* Parse a decimal slot index into an int, rejecting values > INT_MAX. */
+static int rsu_parse_slot(const char *s, int *out)
+{
+	u64 v;
+
+	if (rsu_parse_num(s, 10, &v) || v > INT_MAX)
+		return -EINVAL;
+	*out = (int)v;
+	return 0;
+}
+
+/*
+ * Parse a hex size into int (rejects > INT_MAX); a negative value would
+ * be treated as a huge unsigned length downstream.
+ */
+static int rsu_parse_hex_int(const char *s, int *out)
+{
+	u64 v;
+
+	if (rsu_parse_num(s, 16, &v) || v > INT_MAX)
+		return -EINVAL;
+	*out = (int)v;
+	return 0;
+}
+
+/* Parse a hex size argument into unsigned int, rejecting values > UINT_MAX. */
+static int rsu_parse_hex_uint(const char *s, unsigned int *out)
+{
+	u64 v;
+
+	if (rsu_parse_num(s, 16, &v) || v > UINT_MAX)
+		return -EINVAL;
+	*out = (unsigned int)v;
+	return 0;
+}
+
+/* Parse a hex u32, rejecting values > U32_MAX. */
+static int rsu_parse_hex_u32(const char *s, u32 *out)
+{
+	u64 v;
+
+	if (rsu_parse_num(s, 16, &v) || v > U32_MAX)
+		return -EINVAL;
+	*out = (u32)v;
+	return 0;
+}
+
+static int slot_count(int argc, char * const argv[])
+{
+	int count;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	count = rsu_slot_count();
+	rsu_exit();
+
+	if (count < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Number of slots = %d.\n", count);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_by_name(int argc, char * const argv[])
+{
+	char *name;
+	int slot;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	name = argv[1];
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	slot = rsu_slot_by_name(name);
+	rsu_exit();
+
+	if (slot < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot name '%s' is %d.\n", name, slot);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_get_info(int argc, char * const argv[])
+{
+	int slot;
+	struct rsu_slot_info info;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_get_info(slot, &info);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	printf("NAME: %s\n", info.name);
+	printf("OFFSET: 0x%016llX\n", info.offset);
+	printf("SIZE: 0x%08X\n", info.size);
+	if (info.priority)
+		printf("PRIORITY: %i\n", info.priority);
+	else
+		printf("PRIORITY: [disabled]\n");
+
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_size(int argc, char * const argv[])
+{
+	int slot;
+	int size;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	size = rsu_slot_size(slot);
+	rsu_exit();
+
+	if (size < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d size = %d.\n", slot, size);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_priority(int argc, char * const argv[])
+{
+	int slot;
+	int priority;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	priority = rsu_slot_priority(slot);
+	rsu_exit();
+
+	if (priority < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d priority = %d.\n", slot, priority);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_erase(int argc, char * const argv[])
+{
+	int slot;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_erase(slot);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d erased.\n", slot);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_program_buf(int argc, char * const argv[])
+{
+	int slot;
+	u64 address;
+	int size;
+	int ret;
+	u32 addr_lo;
+	u32 addr_hi;
+
+	if (argc != 4)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot) ||
+	    rsu_parse_num(argv[2], 16, &address) ||
+	    rsu_parse_hex_int(argv[3], &size))
+		return CMD_RET_USAGE;
+
+	/*
+	 * Reject u64 addresses that don't round-trip through ulong so the
+	 * cast to (void *) below cannot silently truncate.
+	 */
+	if (address > (u64)ULONG_MAX)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_program_buf(slot, (void *)(ulong)address, size);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	addr_hi = upper_32_bits(address);
+	addr_lo = lower_32_bits(address);
+	printf("Slot %d was programmed with buffer=0x%08x%08x size=%d.\n",
+	       slot, addr_hi, addr_lo, size);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_program_factory_update_buf(int argc, char * const argv[])
+{
+	int slot;
+	u64 address;
+	int size;
+	int ret;
+	u32 addr_lo;
+	u32 addr_hi;
+
+	if (argc != 4)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot) ||
+	    rsu_parse_num(argv[2], 16, &address) ||
+	    rsu_parse_hex_int(argv[3], &size))
+		return CMD_RET_USAGE;
+
+	/*
+	 * Reject u64 addresses that don't round-trip through ulong so the
+	 * cast to (void *) below cannot silently truncate.
+	 */
+	if (address > (u64)ULONG_MAX)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_program_factory_update_buf(slot, (void *)(ulong)address,
+						  size);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	addr_hi = upper_32_bits(address);
+	addr_lo = lower_32_bits(address);
+	printf("Slot %d was programmed with buffer=0x%08x%08x size=%d.\n",
+	       slot, addr_hi, addr_lo, size);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_program_buf_raw(int argc, char * const argv[])
+{
+	int slot;
+	u64 address;
+	int size;
+	int ret;
+	u32 addr_lo;
+	u32 addr_hi;
+
+	if (argc != 4)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot) ||
+	    rsu_parse_num(argv[2], 16, &address) ||
+	    rsu_parse_hex_int(argv[3], &size))
+		return CMD_RET_USAGE;
+
+	/*
+	 * Reject u64 addresses that don't round-trip through ulong so the
+	 * cast to (void *) below cannot silently truncate.
+	 */
+	if (address > (u64)ULONG_MAX)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_program_buf_raw(slot, (void *)(ulong)address, size);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	addr_hi = upper_32_bits(address);
+	addr_lo = lower_32_bits(address);
+	printf("Slot %d was programmed with raw buffer=0x%08x%08x size=%d.\n",
+	       slot, addr_hi, addr_lo, size);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_verify_buf(int argc, char * const argv[])
+{
+	int slot;
+	u64 address;
+	int size;
+	int ret;
+	u32 addr_lo;
+	u32 addr_hi;
+
+	if (argc != 4)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot) ||
+	    rsu_parse_num(argv[2], 16, &address) ||
+	    rsu_parse_hex_int(argv[3], &size))
+		return CMD_RET_USAGE;
+
+	/*
+	 * Reject u64 addresses that don't round-trip through ulong so the
+	 * cast to (void *) below cannot silently truncate.
+	 */
+	if (address > (u64)ULONG_MAX)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_verify_buf(slot, (void *)(ulong)address, size);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	addr_hi = upper_32_bits(address);
+	addr_lo = lower_32_bits(address);
+	printf("Slot %d was verified with buffer=0x%08x%08x size=%d.\n",
+	       slot, addr_hi, addr_lo, size);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_verify_buf_raw(int argc, char * const argv[])
+{
+	int slot;
+	u64 address;
+	int size;
+	int ret;
+	u32 addr_lo;
+	u32 addr_hi;
+
+	if (argc != 4)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot) ||
+	    rsu_parse_num(argv[2], 16, &address) ||
+	    rsu_parse_hex_int(argv[3], &size))
+		return CMD_RET_USAGE;
+
+	/*
+	 * Reject u64 addresses that don't round-trip through ulong so the
+	 * cast to (void *) below cannot silently truncate.
+	 */
+	if (address > (u64)ULONG_MAX)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_verify_buf_raw(slot, (void *)(ulong)address, size);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	addr_hi = upper_32_bits(address);
+	addr_lo = lower_32_bits(address);
+	printf("Slot %d was verified with raw buffer=0x%08x%08x size=%d.\n",
+	       slot, addr_hi, addr_lo, size);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_enable(int argc, char * const argv[])
+{
+	int slot;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_enable(slot);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d enabled.\n", slot);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_disable(int argc, char * const argv[])
+{
+	int slot;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_disable(slot);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d disabled.\n", slot);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_load(int argc, char * const argv[])
+{
+	int slot;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_load(slot);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d loading.\n", slot);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_load_factory(int argc, char * const argv[])
+{
+	int ret;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_load_factory();
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Factory loading.\n");
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_rename(int argc, char * const argv[])
+{
+	int slot;
+	char *name;
+	int ret;
+
+	if (argc != 3)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+	name = argv[2];
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_rename(slot, name);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d renamed to %s.\n", slot, name);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_delete(int argc, char * const argv[])
+{
+	int slot;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_slot(argv[1], &slot))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_delete(slot);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %d deleted.\n", slot);
+	return CMD_RET_SUCCESS;
+}
+
+static int slot_create(int argc, char * const argv[])
+{
+	char *name;
+	u64 address;
+	unsigned int size;
+	int ret;
+
+	if (argc != 4)
+		return CMD_RET_USAGE;
+
+	name = argv[1];
+	if (rsu_parse_num(argv[2], 16, &address) ||
+	    rsu_parse_hex_uint(argv[3], &size))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_slot_create(name, address, size);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Slot %s created at 0x%016llx with size = 0x%08x bytes.\n", name,
+	       address, size);
+	return CMD_RET_SUCCESS;
+}
+
+static int status_log(int argc, char * const argv[])
+{
+	struct rsu_status_info info;
+	int ret;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_status_log(&info);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	printf("Current Image\t: 0x%016llx\n", info.current_image);
+	printf("Last Fail Image\t: 0x%016llx\n", info.fail_image);
+	printf("State\t\t: 0x%08x\n", info.state);
+	printf("Version\t\t: 0x%08x\n", info.version);
+	printf("Error location\t: 0x%08x\n", info.error_location);
+	printf("Error details\t: 0x%08x\n", info.error_details);
+	if (info.version)
+		printf("Retry counter\t: 0x%08x\n", info.retry_counter);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int notify(int argc, char * const argv[])
+{
+	u32 stage;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_hex_u32(argv[1], &stage))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_notify(stage);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int clear_error_status(int argc, char * const argv[])
+{
+	int ret;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_clear_error_status();
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int reset_retry_counter(int argc, char * const argv[])
+{
+	int ret;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_reset_retry_counter();
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int display_dcmf_version(int argc, char * const argv[])
+{
+	int i, ret;
+	u32 versions[4];
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_dcmf_version(versions);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	for (i = 0; i < 4; i++)
+		printf("DCMF%d version = %d.%d.%d\n", i,
+		       (int)DCMF_VERSION_MAJOR(versions[i]),
+		       (int)DCMF_VERSION_MINOR(versions[i]),
+		       (int)DCMF_VERSION_UPDATE(versions[i]));
+
+	return CMD_RET_SUCCESS;
+}
+
+static int display_dcmf_status(int argc, char * const argv[])
+{
+	int i, ret;
+	u16 status[4];
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_dcmf_status(status);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	for (i = 0; i < 4; i++)
+		printf("DCMF%d: %s\n", i, status[i] ? "Corrupted" : "OK");
+
+	return CMD_RET_SUCCESS;
+}
+
+static int display_max_retry(int argc, char * const argv[])
+{
+	int ret;
+	u8 value;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_max_retry(&value);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	printf("max_retry = %d\n", (int)value);
+
+	return CMD_RET_SUCCESS;
+}
+
+static int create_empty_cpb(int argc, char * const argv[])
+{
+	int ret;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_create_empty_cpb();
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int restore_cpb(int argc, char * const argv[])
+{
+	int ret;
+	u64 addr;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_num(argv[1], 16, &addr))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_restore_cpb(addr);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int save_cpb(int argc, char * const argv[])
+{
+	int ret;
+	u64 addr;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_num(argv[1], 16, &addr))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_save_cpb(addr);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int restore_spt(int argc, char * const argv[])
+{
+	int ret;
+	u64 addr;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_num(argv[1], 16, &addr))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_restore_spt(addr);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int save_spt(int argc, char * const argv[])
+{
+	int ret;
+	u64 addr;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_num(argv[1], 16, &addr))
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_save_spt(addr);
+	rsu_exit();
+
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+static int check_running_factory(int argc, char * const argv[])
+{
+	int ret;
+	int factory;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	if (rsu_init(NULL))
+		return CMD_RET_FAILURE;
+
+	ret = rsu_running_factory(&factory);
+	rsu_exit();
+
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	printf("Running factory image: %s\n", factory ? "yes" : "no");
+	return CMD_RET_SUCCESS;
+}
+
+struct func_t {
+	const char *cmd_string;
+	int (*func_ptr)(int cmd_argc, char * const cmd_argv[]);
+};
+
+static const struct func_t rsu_func_t[] = {
+	{"dtb", rsu_dtb},
+	{"list", rsu_spt_cpb_list},
+	{"slot_by_name", slot_by_name},
+	{"slot_count", slot_count},
+	{"slot_disable", slot_disable},
+	{"slot_enable", slot_enable},
+	{"slot_erase", slot_erase},
+	{"slot_get_info", slot_get_info},
+	{"slot_load", slot_load},
+	{"slot_load_factory", slot_load_factory},
+	{"slot_priority", slot_priority},
+	{"slot_program_buf", slot_program_buf},
+	{"slot_program_buf_raw", slot_program_buf_raw},
+	{"slot_program_factory_update_buf", slot_program_factory_update_buf},
+	{"slot_rename", slot_rename},
+	{"slot_delete", slot_delete},
+	{"slot_create", slot_create},
+	{"slot_size", slot_size},
+	{"slot_verify_buf", slot_verify_buf},
+	{"slot_verify_buf_raw", slot_verify_buf_raw},
+	{"status_log", status_log},
+	{"update", rsu_update},
+	{"notify", notify},
+	{"clear_error_status", clear_error_status},
+	{"reset_retry_counter", reset_retry_counter},
+	{"display_dcmf_version", display_dcmf_version},
+	{"display_dcmf_status", display_dcmf_status},
+	{"display_max_retry", display_max_retry},
+	{"save_spt", save_spt},
+	{"restore_spt", restore_spt},
+	{"create_empty_cpb", create_empty_cpb},
+	{"restore_cpb", restore_cpb},
+	{"save_cpb", save_cpb},
+	{"check_running_factory", check_running_factory}
+};
+
+int do_rsu(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
+{
+	const char *cmd;
+	int i;
+	int ret;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	cmd = argv[1];
+	--argc;
+	++argv;
+
+	for (i = 0; i < ARRAY_SIZE(rsu_func_t); i++) {
+		if (!strcmp(cmd, rsu_func_t[i].cmd_string)) {
+			ret = rsu_func_t[i].func_ptr(argc, argv);
+			/*
+			 * Some helpers return negative errno rather than
+			 * CMD_RET_*; normalise so a failure cannot kill an
+			 * interactive shell session.
+			 */
+			if (ret == CMD_RET_SUCCESS ||
+			    ret == CMD_RET_FAILURE ||
+			    ret == CMD_RET_USAGE)
+				return ret;
+			return CMD_RET_FAILURE;
+		}
+	}
+
+	return CMD_RET_USAGE;
+}
+
+U_BOOT_CMD(rsu, 5, 1, do_rsu,
+	   "Altera SoC FPGA Remote System Update",
+	   "dtb   - Update Linux DTB qspi-boot partition offset with spt0 value\n"
+	   "list  - List down the available bitstreams in flash\n"
+	   "slot_by_name <name> - find slot by name and display the slot number\n"
+	   "slot_count - display the slot count\n"
+	   "slot_disable <slot> - remove slot from CPB\n"
+	   "slot_enable <slot> - make slot the highest priority\n"
+	   "slot_erase <slot> - erase slot\n"
+	   "slot_get_info <slot> - display slot information\n"
+	   "slot_load <slot> - load slot immediately\n"
+	   "slot_load_factory - load factory immediately\n"
+	   "slot_priority <slot> - display slot priority\n"
+	   "slot_program_buf <slot> <buffer> <size> - program buffer into slot, and make it highest priority\n"
+	   "slot_program_buf_raw <slot> <buffer> <size> - program raw buffer into slot\n"
+	   "slot_program_factory_update_buf <slot> <buffer> <size> - program factory update buffer into slot, and make it highest priority\n"
+	   "slot_rename <slot> <name> - rename slot\n"
+	   "slot_delete <slot> - delete slot\n"
+	   "slot_create <name> <address> <size> - create slot\n"
+	   "slot_size <slot> - display slot size\n"
+	   "slot_verify_buf <slot> <buffer> <size> - verify slot contents against buffer\n"
+	   "slot_verify_buf_raw <slot> <buffer> <size> - verify slot contents against raw buffer\n"
+	   "status_log - display RSU status\n"
+	   "update <flash_offset> - Initiate firmware to load bitstream as specified by flash_offset\n"
+	   "notify <value> - Let SDM know the current state of HPS software\n"
+	   "clear_error_status - clear the RSU error status\n"
+	   "reset_retry_counter - reset the RSU retry counter\n"
+	   "display_dcmf_version - display DCMF versions and store them for SMC handler usage\n"
+	   "display_dcmf_status - display DCMF status and store it for SMC handler usage\n"
+	   "display_max_retry - display max_retry parameter, and store it for SMC handler usage\n"
+	   "restore_spt <address> - restore SPT from an address\n"
+	   "save_spt <address> - save SPT to an address\n"
+	   "create_empty_cpb - create an empty CPB\n"
+	   "restore_cpb <address> - restore CPB from an address\n"
+	   "save_cpb <address> - save CPB to an address\n"
+	   "check_running_factory - check if currently running the factory image"
+);
diff --git a/configs/socfpga_agilex5_defconfig b/configs/socfpga_agilex5_defconfig
index 530eb229f3e..9e7f8407b7b 100644
--- a/configs/socfpga_agilex5_defconfig
+++ b/configs/socfpga_agilex5_defconfig
@@ -57,6 +57,7 @@ CONFIG_SYS_PROMPT="SOCFPGA_AGILEX5 # "
 CONFIG_SYS_MAXARGS=32
 CONFIG_CMD_NVEDIT_SELECT=y
 CONFIG_CMD_MEMTEST=y
+CONFIG_CMD_SOCFPGA_RSU=y
 CONFIG_CMD_GPIO=y
 CONFIG_CMD_I2C=y
 CONFIG_CMD_I3C=y
@@ -84,7 +85,6 @@ CONFIG_DM_I2C=y
 CONFIG_SYS_I2C_DW=y
 CONFIG_I3C=y
 CONFIG_DW_I3C_MASTER=y
-CONFIG_MISC=y
 CONFIG_MMC_IO_VOLTAGE=y
 CONFIG_SPL_MMC_IO_VOLTAGE=y
 CONFIG_MMC_UHS_SUPPORT=y
diff --git a/configs/socfpga_agilex_defconfig b/configs/socfpga_agilex_defconfig
index ba8dc821add..6e705ff0312 100644
--- a/configs/socfpga_agilex_defconfig
+++ b/configs/socfpga_agilex_defconfig
@@ -48,6 +48,7 @@ CONFIG_SPL_ATF_NO_PLATFORM_PARAM=y
 CONFIG_SYS_PROMPT="SOCFPGA_AGILEX # "
 CONFIG_CMD_NVEDIT_SELECT=y
 CONFIG_CMD_MEMTEST=y
+CONFIG_CMD_SOCFPGA_RSU=y
 CONFIG_CMD_GPIO=y
 CONFIG_CMD_I2C=y
 CONFIG_CMD_MMC=y
@@ -70,7 +71,6 @@ CONFIG_SPL_ALTERA_SDRAM=y
 CONFIG_DWAPB_GPIO=y
 CONFIG_DM_I2C=y
 CONFIG_SYS_I2C_DW=y
-CONFIG_MISC=y
 CONFIG_SYS_MMC_MAX_BLK_COUNT=256
 CONFIG_MMC_DW=y
 CONFIG_DM_MTD=y
diff --git a/configs/socfpga_n5x_defconfig b/configs/socfpga_n5x_defconfig
index 3b3916e676d..7ad3b53cb6d 100644
--- a/configs/socfpga_n5x_defconfig
+++ b/configs/socfpga_n5x_defconfig
@@ -43,6 +43,7 @@ CONFIG_SPL_TARGET="spl/u-boot-spl-dtb.hex"
 CONFIG_HUSH_PARSER=y
 CONFIG_SYS_PROMPT="SOCFPGA_N5X # "
 CONFIG_CMD_MEMTEST=y
+CONFIG_CMD_SOCFPGA_RSU=y
 CONFIG_CMD_GPIO=y
 CONFIG_CMD_I2C=y
 CONFIG_CMD_MMC=y
@@ -65,6 +66,7 @@ CONFIG_SPL_DM_SEQ_ALIAS=y
 CONFIG_DWAPB_GPIO=y
 CONFIG_DM_I2C=y
 CONFIG_SYS_I2C_DW=y
+# CONFIG_SPL_MISC is not set
 CONFIG_SYS_MMC_MAX_BLK_COUNT=256
 CONFIG_MMC_DW=y
 CONFIG_SPI_FLASH_SPANSION=y
diff --git a/configs/socfpga_stratix10_defconfig b/configs/socfpga_stratix10_defconfig
index 7b04859dec7..7f7958627e8 100644
--- a/configs/socfpga_stratix10_defconfig
+++ b/configs/socfpga_stratix10_defconfig
@@ -47,6 +47,7 @@ CONFIG_SPL_TARGET="spl/u-boot-spl-dtb.hex"
 CONFIG_HUSH_PARSER=y
 CONFIG_SYS_PROMPT="SOCFPGA_STRATIX10 # "
 CONFIG_CMD_MEMTEST=y
+CONFIG_CMD_SOCFPGA_RSU=y
 CONFIG_CMD_GPIO=y
 CONFIG_CMD_I2C=y
 CONFIG_CMD_MMC=y
@@ -70,6 +71,7 @@ CONFIG_SPL_ALTERA_SDRAM=y
 CONFIG_DWAPB_GPIO=y
 CONFIG_DM_I2C=y
 CONFIG_SYS_I2C_DW=y
+# CONFIG_SPL_MISC is not set
 CONFIG_SYS_MMC_MAX_BLK_COUNT=256
 CONFIG_MMC_DW=y
 CONFIG_SPI_FLASH_SPANSION=y
diff --git a/doc/usage/cmd/rsu.rst b/doc/usage/cmd/rsu.rst
new file mode 100644
index 00000000000..b15b6e86184
--- /dev/null
+++ b/doc/usage/cmd/rsu.rst
@@ -0,0 +1,208 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+.. index::
+   single: rsu (command)
+
+rsu command
+===========
+
+Synopsis
+--------
+
+::
+
+    rsu list
+    rsu slot_count
+    rsu slot_by_name <name>
+    rsu slot_get_info <slot>
+    rsu slot_size <slot>
+    rsu slot_priority <slot>
+    rsu slot_enable <slot>
+    rsu slot_disable <slot>
+    rsu slot_load <slot>
+    rsu slot_load_factory
+    rsu slot_rename <slot> <name>
+    rsu slot_delete <slot>
+    rsu slot_create <name> <address> <size>
+    rsu slot_erase <slot>
+    rsu slot_program_buf <slot> <buffer> <size>
+    rsu slot_program_buf_raw <slot> <buffer> <size>
+    rsu slot_program_factory_update_buf <slot> <buffer> <size>
+    rsu slot_verify_buf <slot> <buffer> <size>
+    rsu slot_verify_buf_raw <slot> <buffer> <size>
+    rsu update <flash_offset>
+    rsu notify <value>
+    rsu clear_error_status
+    rsu reset_retry_counter
+    rsu display_dcmf_version
+    rsu display_dcmf_status
+    rsu display_max_retry
+    rsu restore_spt <address>
+    rsu save_spt <address>
+    rsu create_empty_cpb
+    rsu restore_cpb <address>
+    rsu save_cpb <address>
+    rsu check_running_factory
+    rsu dtb
+
+Description
+-----------
+
+The *rsu* command provides access to the Remote System Update (RSU) feature
+of the Altera Stratix 10 and Agilex family of SoCFPGA devices. RSU lets an
+application boot from one of several configuration images stored in SPI NOR
+flash and recover to a known-good factory image when an update fails.
+
+RSU is implemented in the Secure Device Manager (SDM) firmware and in the
+on-board Cadence QSPI flash. U-Boot talks to the SDM through the mailbox
+(or, when running on top of Arm Trusted Firmware, through an SMC call) and
+accesses the flash through the SPI-NOR framework. The flash layout is
+described by a Sub-Partition Table (SPT) and a Configuration Pointer Block
+(CPB).
+
+The command is bound to a driver-model anchor device with compatible string
+``altr,socfpga-rsu``. The node carries no MMIO; it only holds the active RSU
+low-level session while a command runs.
+
+Slot inspection
+~~~~~~~~~~~~~~~
+
+list
+    List the bitstreams (slots) available in flash.
+
+slot_count
+    Display the number of slots defined in the CPB.
+
+slot_by_name <name>
+    Find a slot by name and display its slot number.
+
+slot_get_info <slot>
+    Display the offset, size, priority and name of *slot*.
+
+slot_size <slot>
+    Display the size of *slot* in bytes.
+
+slot_priority <slot>
+    Display the load priority of *slot*.
+
+Slot management
+~~~~~~~~~~~~~~~
+
+slot_enable <slot>
+    Make *slot* the highest-priority slot.
+
+slot_disable <slot>
+    Remove *slot* from the CPB so it is no longer a load candidate.
+
+slot_load <slot>
+    Request the SDM to load *slot* immediately (triggers a reconfiguration).
+
+slot_load_factory
+    Request the SDM to load the factory image immediately.
+
+slot_rename <slot> <name>
+    Rename *slot* to *name*.
+
+slot_delete <slot>
+    Delete *slot*.
+
+slot_create <name> <address> <size>
+    Create a new slot *name* at flash *address* with *size* bytes.
+
+slot_erase <slot>
+    Erase the flash content of *slot*.
+
+Programming and verification
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+slot_program_buf <slot> <buffer> <size>
+    Program *size* bytes from memory *buffer* into *slot* and make the slot
+    the highest priority.
+
+slot_program_buf_raw <slot> <buffer> <size>
+    Program a raw (non-decompressed) buffer into *slot*.
+
+slot_program_factory_update_buf <slot> <buffer> <size>
+    Program a factory-update buffer into *slot* and make it highest priority.
+
+slot_verify_buf <slot> <buffer> <size>
+    Verify the contents of *slot* against memory *buffer*.
+
+slot_verify_buf_raw <slot> <buffer> <size>
+    Verify the contents of *slot* against a raw memory *buffer*.
+
+Firmware operations
+~~~~~~~~~~~~~~~~~~~
+
+update <flash_offset>
+    Tell the SDM to load the bitstream located at *flash_offset* on the next
+    reboot.
+
+notify <value>
+    Notify the SDM of the current state of the HPS software.
+
+clear_error_status
+    Clear the RSU error status.
+
+reset_retry_counter
+    Reset the RSU retry counter.
+
+display_dcmf_version
+    Display the Decision Configuration Management Firmware (DCMF) versions and
+    store them for later retrieval by the SMC handler.
+
+display_dcmf_status
+    Display the DCMF status and store it for the SMC handler.
+
+display_max_retry
+    Display the ``max_retry`` parameter and store it for the SMC handler.
+
+SPT/CPB maintenance
+~~~~~~~~~~~~~~~~~~~
+
+restore_spt <address>
+    Restore the SPT from the image located at memory *address*.
+
+save_spt <address>
+    Save the current SPT to memory *address*.
+
+create_empty_cpb
+    Create an empty CPB.
+
+restore_cpb <address>
+    Restore the CPB from the image located at memory *address*.
+
+save_cpb <address>
+    Save the current CPB to memory *address*.
+
+check_running_factory
+    Report whether the factory image is currently running.
+
+dtb
+    Update the Linux DTB ``qspi-boot`` partition offset with the spt0 value.
+
+Configuration
+-------------
+
+The command is available when ``CONFIG_CMD_SOCFPGA_RSU`` is enabled. It depends
+on ``CONFIG_ARCH_SOCFPGA_SOC64`` and ``CONFIG_CADENCE_QSPI``. The driver-model
+anchor device is provided by ``CONFIG_SOCFPGA_RSU_DM``.
+
+Example
+-------
+
+List the available slots and inspect the first one::
+
+    => rsu list
+    => rsu slot_count
+    => rsu slot_get_info 0
+
+Program a new bitstream that has been loaded to memory at ``0x2000000``
+(size ``0x100000``) into slot 1 and make it the highest priority::
+
+    => rsu slot_program_buf 1 0x2000000 0x100000
+
+Return value
+------------
+
+The return value ``$?`` is 0 (true) on success and 1 (false) on failure.
diff --git a/test/cmd/Makefile b/test/cmd/Makefile
index 8d6932f1176..63e1302af00 100644
--- a/test/cmd/Makefile
+++ b/test/cmd/Makefile
@@ -48,4 +48,5 @@ endif
 obj-$(CONFIG_CMD_SPAWN) += spawn.o
 ifdef CONFIG_CMD_ZIP
 obj-$(CONFIG_CMD_UNZIP) += unzip.o
+obj-$(CONFIG_CMD_SOCFPGA_RSU) += socfpga_rsu.o
 endif
diff --git a/test/cmd/socfpga_rsu.c b/test/cmd/socfpga_rsu.c
new file mode 100644
index 00000000000..af9f16567c9
--- /dev/null
+++ b/test/cmd/socfpga_rsu.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for Altera SoC FPGA rsu command (usage path).
+ *
+ * Copyright (C) 2026 Altera Corporation <www.altera.com>
+ */
+
+#include <command.h>
+#include <test/cmd.h>
+#include <test/ut.h>
+
+static int cmd_ut_socfpga_rsu_usage(struct unit_test_state *uts)
+{
+	ut_asserteq(CMD_RET_USAGE, run_command("rsu", 0));
+
+	return 0;
+}
+
+CMD_TEST(cmd_ut_socfpga_rsu_usage, 0);
+
+/*
+ * Malformed numeric arguments (non-digit characters, overflow, trailing junk)
+ * must be rejected by the subcommand handlers with CMD_RET_USAGE *before* any
+ * RSU state is touched. This exercises the rsu_parse_num() / rsu_parse_slot()
+ * /rsu_parse_hex_* helpers without requiring a working RSU backend, because
+ * parsing is performed up-front and the rsu_init() call is never reached on
+ * the error path.
+ */
+static int cmd_ut_socfpga_rsu_bad_slot(struct unit_test_state *uts)
+{
+	ut_asserteq(CMD_RET_USAGE,
+		    run_command("rsu slot_get_info foo", 0));
+	ut_asserteq(CMD_RET_USAGE,
+		    run_command("rsu slot_get_info 12xyz", 0));
+	ut_asserteq(CMD_RET_USAGE,
+		    run_command("rsu slot_get_info 99999999999", 0));
+
+	return 0;
+}
+
+CMD_TEST(cmd_ut_socfpga_rsu_bad_slot, 0);
+
+static int cmd_ut_socfpga_rsu_bad_size(struct unit_test_state *uts)
+{
+	/*
+	 * slot_program_buf takes <slot> <buffer> <size>; an INT_MAX+1 size
+	 * must be rejected with CMD_RET_USAGE (would otherwise be silently
+	 * cast to a negative int, which is how buffer overflow bugs start).
+	 */
+	ut_asserteq(CMD_RET_USAGE,
+		    run_command("rsu slot_program_buf 0 0x1000 80000000", 0));
+	/* Garbage in the size field. */
+	ut_asserteq(CMD_RET_USAGE,
+		    run_command("rsu slot_program_buf 0 0x1000 qqq", 0));
+
+	return 0;
+}
+
+CMD_TEST(cmd_ut_socfpga_rsu_bad_size, 0);
-- 
2.43.7



More information about the U-Boot mailing list