[PATCH 2/8] arm: socfpga: Add Remote System Update (RSU) core library

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


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

Add the RSU core library for the Stratix 10 / Agilex family of SoCs.
RSU lets the SoC boot from one of several configuration images held in
SPI NOR flash and recover to a known-good factory image when an update
fails. The feature is implemented in the SDM firmware and the on-board
Cadence QSPI flash; this library drives it through the SDM mailbox (or
an SMC call when running on top of Arm Trusted Firmware) and the
SPI-NOR framework.

The library is split into the generic session/command layer (rsu.c),
the helper and SPT/CPB logic (rsu_misc.c), the QSPI low-level backend
(rsu_ll_qspi.c, rsu_s10.c) and the optional secure-monitor SMC handler
(smc_rsu_s10.c, built only with ARMV8_PSCI). The decision-firmware
version/status copy paths select the SMC client form under SPL_ATF, the
secure-RAM form when U-Boot is itself the monitor (ARMV8_PSCI), and are
a no-op otherwise.

Add the CMD_SOCFPGA_RSU and SOCFPGA_RSU_SF_CS Kconfig symbols that gate
the library and select the RSU SPT/CPB chip-select. The objects are
built for U-Boot proper only.

Signed-off-by: Dinesh Maniyam <dinesh.maniyam at altera.com>
---
 arch/arm/mach-socfpga/Kconfig       |   12 +
 arch/arm/mach-socfpga/Makefile      |    7 +
 arch/arm/mach-socfpga/rsu.c         | 1210 ++++++++++++++
 arch/arm/mach-socfpga/rsu_ll_qspi.c | 2402 +++++++++++++++++++++++++++
 arch/arm/mach-socfpga/rsu_misc.c    |  854 ++++++++++
 arch/arm/mach-socfpga/rsu_s10.c     |  459 +++++
 arch/arm/mach-socfpga/smc_rsu_s10.c |  188 +++
 cmd/Kconfig                         |   11 +
 8 files changed, 5143 insertions(+)
 create mode 100644 arch/arm/mach-socfpga/rsu.c
 create mode 100644 arch/arm/mach-socfpga/rsu_ll_qspi.c
 create mode 100644 arch/arm/mach-socfpga/rsu_misc.c
 create mode 100644 arch/arm/mach-socfpga/rsu_s10.c
 create mode 100644 arch/arm/mach-socfpga/smc_rsu_s10.c

diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig
index fb98b647442..ff961e7f13c 100644
--- a/arch/arm/mach-socfpga/Kconfig
+++ b/arch/arm/mach-socfpga/Kconfig
@@ -6,6 +6,18 @@ config ERR_PTR_OFFSET
 config NR_DRAM_BANKS
 	default 1
 
+config SOCFPGA_RSU_SF_CS
+	int "SPI flash chip-select used by the RSU flow (SPT/CPB)"
+	depends on CMD_SOCFPGA_RSU
+	depends on SPI_FLASH || DM_SPI_FLASH
+	default SF_DEFAULT_CS
+	help
+	  Chip select of the SPI flash that holds the RSU sub-partition
+	  table (SPT) and configuration pointer block (CPB) metadata.
+	  Independent from SF_DEFAULT_CS (which selects the flash SPL
+	  loads u-boot.itb from). Override only when RSU metadata lives
+	  on a different chip select than the u-boot.itb boot image.
+
 config SOCFPGA_SECURE_VAB_AUTH
 	bool "Enable boot image authentication with Secure Device Manager"
 	depends on ARCH_SOCFPGA_AGILEX || ARCH_SOCFPGA_N5X || \
diff --git a/arch/arm/mach-socfpga/Makefile b/arch/arm/mach-socfpga/Makefile
index b6f35ddacc4..109af31ceb8 100644
--- a/arch/arm/mach-socfpga/Makefile
+++ b/arch/arm/mach-socfpga/Makefile
@@ -138,6 +138,13 @@ endif
 else
 obj-$(CONFIG_SPL_ATF) += secure_reg_helper.o
 obj-$(CONFIG_SPL_ATF) += smc_api.o
+ifdef CONFIG_CMD_SOCFPGA_RSU
+obj-y	+= rsu.o
+obj-y	+= rsu_misc.o
+obj-y	+= rsu_ll_qspi.o
+obj-y	+= rsu_s10.o
+obj-$(CONFIG_ARMV8_PSCI)	+= smc_rsu_s10.o
+endif
 endif
 
 ifdef CONFIG_ARCH_SOCFPGA_GEN5
diff --git a/arch/arm/mach-socfpga/rsu.c b/arch/arm/mach-socfpga/rsu.c
new file mode 100644
index 00000000000..a8c822bf753
--- /dev/null
+++ b/arch/arm/mach-socfpga/rsu.c
@@ -0,0 +1,1210 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Intel Corporation
+ * Copyright (C) 2026 Altera Corporation <www.altera.com>
+ */
+
+#include <dm/device.h>
+#include <dm/ofnode.h>
+#include <asm/arch/rsu.h>
+#include <asm/arch/rsu_misc.h>
+#include <asm/arch/smc_api.h>
+#include <asm/arch/socfpga_rsu_dm.h>
+#include <asm/secure.h>
+#include <asm/system.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/intel-smc.h>
+
+/* RSU Notify Bitmasks */
+#define RSU_NOTIFY_IGNORE_STAGE         BIT(18)
+#define RSU_NOTIFY_CLEAR_ERROR_STATUS   BIT(17)
+#define RSU_NOTIFY_RESET_RETRY_COUNTER  BIT(16)
+
+/* Cached anchor; valid as long as the misc device stays bound. */
+static struct udevice *rsu_dm_dev;
+
+static struct rsu_ll_intf **rsu_ll_ptrp(void)
+{
+	struct socfpga_rsu_priv *priv;
+	int ret;
+	ofnode node;
+
+	if (rsu_dm_dev)
+		goto have_dev;
+
+	/* Locate by compatible to avoid hard-coding a DT path. */
+	node = ofnode_by_compatible(ofnode_null(), "altr,socfpga-rsu");
+	if (!ofnode_valid(node))
+		return NULL;
+	ret = device_get_global_by_ofnode(node, &rsu_dm_dev);
+	if (ret)
+		return NULL;
+have_dev:
+	priv = dev_get_priv(rsu_dm_dev);
+
+	return &priv->ll;
+}
+
+static struct rsu_ll_intf *rsu_ll(void)
+{
+	struct rsu_ll_intf **p = rsu_ll_ptrp();
+
+	return p ? *p : NULL;
+}
+
+/**
+ * rsu_init() - initialize flash driver, SPT and CPB data
+ * @filename: ignored; kept for ABI compatibility with librsu.
+ *
+ * If a previous session is still active, it is closed and re-opened so
+ * a stale state cannot wedge subsequent commands.
+ *
+ * Returns: 0 on success, or the errno from the low-level backend init.
+ */
+int rsu_init(char *filename)
+{
+	int ret;
+	struct rsu_ll_intf **llp = rsu_ll_ptrp();
+
+	(void)filename;
+
+	if (!llp)
+		return -ENODEV;
+
+	if (*llp) {
+		rsu_log(RSU_ERR,
+			"ll_intf already initialized; resetting\n");
+		/* Tear down the stale session and re-initialize below. */
+		rsu_exit();
+	}
+
+	ret = rsu_ll_qspi_init(llp);
+	if (ret) {
+		rsu_exit();
+		/* Preserve the backend errno for callers and tests. */
+		return ret;
+	}
+
+	/* Backend success but no session pointer: report as -ENODEV. */
+	if (!*llp) {
+		rsu_exit();
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/**
+ * rsu_exit() - free flash driver, clean SPT and CPB data
+ */
+void rsu_exit(void)
+{
+	struct rsu_ll_intf **llp = rsu_ll_ptrp();
+
+	if (!llp || !*llp)
+		return;
+	if ((*llp)->exit)
+		(*llp)->exit();
+	*llp = NULL;
+}
+
+/**
+ * rsu_spt_corrupted_info() - output warning info to user
+ */
+void rsu_spt_corrupted_info(void)
+{
+	rsu_log(RSU_ERR, "corrupted SPT --");
+	rsu_log(RSU_ERR, "run rsu restore_spt <address> first\n");
+}
+
+/**
+ * rsu_cpb_corrupted_info() - output warning info to user
+ */
+void rsu_cpb_corrupted_info(void)
+{
+	rsu_log(RSU_ERR, "corrupted CPB --");
+	rsu_log(RSU_ERR, "run rsu create_empty_cpb");
+	rsu_log(RSU_ERR, "or rsu restore_cpb <address> first\n");
+};
+
+/**
+ * rsu_slot_count() - get the number of slots defined
+ *
+ * Returns: the number of defined slots
+ */
+int rsu_slot_count(void)
+{
+	int partitions;
+	int cnt = 0;
+	int x;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	partitions = rsu_ll()->partition.count();
+
+	for (x = 0; x < partitions; x++) {
+		if (rsu_misc_is_slot(rsu_ll(), x))
+			cnt++;
+	}
+
+	return cnt;
+}
+
+/**
+ * rsu_slot_by_name() - get slot number based on name
+ * @name: name of slot
+ *
+ * Return: slot number on success, or error code
+ */
+int rsu_slot_by_name(char *name)
+{
+	int partitions;
+	int cnt = 0;
+	int x;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (!name)
+		return -EARGS;
+
+	partitions = rsu_ll()->partition.count();
+
+	for (x = 0; x < partitions; x++) {
+		if (rsu_misc_is_slot(rsu_ll(), x)) {
+			if (!strcmp(name, rsu_ll()->partition.name(x)))
+				return cnt;
+			cnt++;
+		}
+	}
+
+	return -ENAME;
+}
+
+/**
+ * rsu_slot_get_info() - get the attributes of a slot
+ * @slot: slot number
+ * @info: pointer to info structure to be filled in
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_get_info(int slot, struct rsu_slot_info *info)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (!info)
+		return -EARGS;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -EINVAL;
+
+	rsu_misc_safe_strcpy(info->name, sizeof(info->name),
+			     rsu_ll()->partition.name(part_num),
+			     sizeof(info->name));
+
+	info->offset = rsu_ll()->partition.offset(part_num);
+	info->size = rsu_ll()->partition.size(part_num);
+	info->priority = rsu_ll()->priority.get(part_num);
+
+	return 0;
+}
+
+/**
+ * rsu_slot_size() - get the size of a slot
+ * @slot: slot number
+ *
+ * Returns: the size of the slot in bytes, or error code
+ */
+int rsu_slot_size(int slot)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	return rsu_ll()->partition.size(part_num);
+}
+
+/**
+ * rsu_slot_priority() - get the Decision CMF load priority of a slot
+ * @slot: slot number
+ *
+ * Priority of zero means the slot has no priority and is disabled.
+ * The slot with priority of one has the highest priority.
+ *
+ * Returns: the priority of the slot, or error code
+ */
+int rsu_slot_priority(int slot)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	return rsu_ll()->priority.get(part_num);
+}
+
+/**
+ * rsu_slot_erase() - erase all data in a slot
+ * @slot: slot number
+ *
+ * Erase all data in a slot to prepare for programming. Remove the slot
+ * if it is in the CMF pointer block.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_erase(int slot)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	if (rsu_misc_writeprotected(slot)) {
+		rsu_log(RSU_ERR, "Trying to erase a write protected slot\n");
+		return -EWRPROT;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	if (rsu_ll()->priority.remove(part_num))
+		return -ELOWLEVEL;
+
+	if (rsu_ll()->data.erase(part_num))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * rsu_slot_program_buf() - program a slot from FPGA buffer data
+ * @slot: slot number
+ * @buf: pointer to data buffer
+ * @size: bytes to read from buffer, in hex value
+ *
+ * This function is used to program a slot using FPGA config data from
+ * a buffer and then enter the slot into CPB.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_program_buf(int slot, void *buf, int size)
+{
+	int ret;
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (rsu_cb_buf_init(buf, size)) {
+		rsu_log(RSU_ERR, "Bad buf/size arguments\n");
+		return -EARGS;
+	}
+
+	ret = rsu_cb_program_common(rsu_ll(), slot, rsu_cb_buf, 0);
+	if (ret) {
+		rsu_log(RSU_ERR, "fail to program buf data\n");
+		return ret;
+	}
+
+	rsu_cb_buf_exit();
+	return ret;
+}
+
+/**
+ * rsu_slot_program_factory_update_buf() - program a slot using factory update
+ *                                         data from a buffer
+ * @slot: slot number
+ * @buf: pointer to data buffer
+ * @size: bytes to read from buffer
+ *
+ * This function is used to program a slot using factory update data from a
+ * buffer and then enter the slot into CPB.
+ *
+ * Returns 0 on success, or error code
+ */
+int rsu_slot_program_factory_update_buf(int slot, void *buf, int size)
+{
+	return rsu_slot_program_buf(slot, buf, size);
+}
+
+/**
+ * rsu_slot_program_buf_raw() - program a slot from raw buffer data
+ * @slot: slot number
+ * @buf: pointer to data buffer
+ * @size: bytes to read from buffer, in hex value
+ *
+ * This function is used to program a slot using raw data from a buffer,
+ * the slot is not entered into CPB.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_program_buf_raw(int slot, void *buf, int size)
+{
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	if (rsu_cb_buf_init(buf, size)) {
+		rsu_log(RSU_ERR, "Bad buf/size arguments\n");
+		return -EARGS;
+	}
+
+	ret = rsu_cb_program_common(rsu_ll(), slot, rsu_cb_buf, 1);
+	if (ret) {
+		rsu_log(RSU_ERR, "fail to program raw data\n");
+		return ret;
+	}
+
+	rsu_cb_buf_exit();
+	return ret;
+}
+
+/**
+ * rsu_slot_verify_buf() - verify FPGA config data in a slot
+ * @slot: slot number
+ * @buf: pointer to data buffer
+ * @size: bytes to read from buffer, in hex value
+ *
+ * This function is used to verify FPGA configuration data in a selected
+ * slot against the provided buffer.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_verify_buf(int slot, void *buf, int size)
+{
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	if (rsu_cb_buf_init(buf, size)) {
+		rsu_log(RSU_ERR, "Bad buf/size arguments\n");
+		return -EARGS;
+	}
+
+	ret = rsu_cb_verify_common(rsu_ll(), slot, rsu_cb_buf, 0);
+	if (ret) {
+		rsu_log(RSU_ERR, "fail to verify buffer data\n");
+		return ret;
+	}
+
+	rsu_cb_buf_exit();
+
+	return ret;
+}
+
+/**
+ * rsu_slot_verify_buf_raw() - verify raw data in a slot
+ * @slot: slot number
+ * @buf: pointer to data buffer
+ * @size: bytes to read from buffer, in hex value
+ *
+ * This function is used to verify raw data in a selected slot against
+ * the provided buffer.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_verify_buf_raw(int slot, void *buf, int size)
+{
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	if (rsu_cb_buf_init(buf, size)) {
+		rsu_log(RSU_ERR, "Bad buf/size arguments\n");
+		return -EARGS;
+	}
+
+	ret = rsu_cb_verify_common(rsu_ll(), slot, rsu_cb_buf, 1);
+	if (ret) {
+		rsu_log(RSU_ERR, "fail to verify raw data\n");
+		return ret;
+	}
+
+	rsu_cb_buf_exit();
+	return ret;
+}
+
+/**
+ * rsu_slot_enable() - enable the selected slot
+ * @slot: slot number
+ *
+ * Set the selected slot as the highest priority. It will be the first
+ * slot to be tried after a power-on reset.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_enable(int slot)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	if (rsu_ll()->priority.remove(part_num))
+		return -ELOWLEVEL;
+
+	if (rsu_ll()->priority.add(part_num))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * rsu_slot_disable() - disable the selected slot
+ * @slot: slot number
+ *
+ * Remove the selected slot from the priority scheme, but don't erase the
+ * slot data so that it can be re-enabled.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_disable(int slot)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	if (rsu_ll()->priority.remove(part_num))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * rsu_slot_load() - load the selected slot
+ * @slot: slot number
+ *
+ * This function is used to request the selected slot to be loaded
+ * immediately. On success the system is rebooted after a short delay.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_load(int slot)
+{
+	int part_num;
+	u64 offset;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	offset = rsu_ll()->partition.offset(part_num);
+
+	return rsu_ll()->fw_ops.load(offset);
+}
+
+/**
+ * rsu_slot_load_factory() - load the factory image
+ *
+ * This function is used to request the factory image to be loaded
+ * immediately. On success, the system is rebooted after a short delay.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_load_factory(void)
+{
+	int part_num;
+	int partitions;
+	u64 offset;
+	char name[] = "FACTORY_IMAGE";
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	partitions = rsu_ll()->partition.count();
+	for (part_num = 0; part_num < partitions; part_num++) {
+		if (!strcmp(name, rsu_ll()->partition.name(part_num)))
+			break;
+	}
+
+	if (part_num >= partitions) {
+		rsu_log(RSU_ERR, "No FACTORY_IMAGE partition defined\n");
+		return -EFORMAT;
+	}
+
+	offset = rsu_ll()->partition.offset(part_num);
+	return rsu_ll()->fw_ops.load(offset);
+}
+
+/**
+ * rsu_slot_rename() - Rename the selected slot.
+ * @slot: slot number
+ * @name: new name for slot
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_slot_rename(int slot, char *name)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	if (!name)
+		return -EARGS;
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	if (rsu_misc_is_rsvd_name(name)) {
+		rsu_log(RSU_ERR, "Partition rename uses a reserved name\n");
+		return -ENAME;
+	}
+
+	if (rsu_ll()->partition.rename(part_num, name))
+		return -ENAME;
+
+	return 0;
+}
+
+/**
+ * rsu_slot_delete() - Delete the selected slot.
+ * @slot: slot number
+ *
+ * Returns 0 on success, or Error Code
+ */
+int rsu_slot_delete(int slot)
+{
+	int part_num;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	if (slot < 0 || slot >= rsu_slot_count()) {
+		rsu_log(RSU_ERR, "Invalid slot number\n");
+		return -ESLOTNUM;
+	}
+
+	if (rsu_misc_writeprotected(slot)) {
+		rsu_log(RSU_ERR, "Trying to delete a write protected slot\n");
+		return -EWRPROT;
+	}
+
+	part_num = rsu_misc_slot2part(rsu_ll(), slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	if (rsu_ll()->priority.remove(part_num))
+		return -ELOWLEVEL;
+
+	if (rsu_ll()->data.erase(part_num))
+		return -ELOWLEVEL;
+
+	if (rsu_ll()->partition.delete(part_num))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * rsu_slot_create() - Create a new slot.
+ * @name: slot name
+ * @address: slot start address
+ * @size: slot size
+ *
+ * Returns 0 on success, or Error Code
+ */
+int rsu_slot_create(char *name, u64 address, unsigned int size)
+{
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	if (rsu_misc_is_rsvd_name(name)) {
+		rsu_log(RSU_ERR, "Partition create uses a reserved name\n");
+		return -ENAME;
+	}
+
+	if (rsu_ll()->partition.create(name, address, size))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * rsu_status_log() - Copy firmware status log to info struct
+ * @info: pointer to info struct to fill in
+ *
+ * Return 0 on success, or error code
+ */
+int rsu_status_log(struct rsu_status_info *info)
+{
+	if (!rsu_ll())
+		return -EINTF;
+
+	return rsu_ll()->fw_ops.status(info);
+}
+
+/**
+ * rsu_notify() - report HPS software execution stage as a 16bit number
+ * @stage: software execution stage
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_notify(int stage)
+{
+	u32 arg;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	arg = stage & GENMASK(15, 0);
+	return rsu_ll()->fw_ops.notify(arg);
+}
+
+/**
+ * rsu_clear_error_status() - clear errors from the current RSU status log
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_clear_error_status(void)
+{
+	struct rsu_status_info info;
+	u32 arg;
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	ret = rsu_status_log(&info);
+	if (ret < 0)
+		return ret;
+
+	if (!RSU_VERSION_ACMF_VERSION(info.version))
+		return -ELOWLEVEL;
+
+	arg = RSU_NOTIFY_IGNORE_STAGE | RSU_NOTIFY_CLEAR_ERROR_STATUS;
+	return rsu_ll()->fw_ops.notify(arg);
+}
+
+/**
+ * rsu_reset_retry_counter() - reset the retry counter
+ *
+ * This function is used to request the retry counter to be reset, so that the
+ * currently running image may be tried again after the next watchdog timeout.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_reset_retry_counter(void)
+{
+	struct rsu_status_info info;
+	u32 arg;
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	ret = rsu_status_log(&info);
+	if (ret < 0)
+		return ret;
+
+	if (!RSU_VERSION_ACMF_VERSION(info.version) ||
+	    !RSU_VERSION_DCMF_VERSION(info.version))
+		return -ELOWLEVEL;
+
+	arg = RSU_NOTIFY_IGNORE_STAGE | RSU_NOTIFY_RESET_RETRY_COUNTER;
+	return rsu_ll()->fw_ops.notify(arg);
+}
+
+#ifdef CONFIG_ARMV8_PSCI
+extern u32 smc_rsu_dcmf_version[4];
+#endif
+
+static int copy_dcmf_version_to_smc(u32 *versions)
+{
+#if !defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_ATF)
+	u64 args[2];
+#elif defined(CONFIG_ARMV8_PSCI)
+	void *dcmf_versions;
+#endif
+
+	if (!versions)
+		return -EINVAL;
+
+#if !defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_ATF)
+	args[0] = ((u64)versions[1] << 32) | versions[0];
+	args[1] = ((u64)versions[3] << 32) | versions[2];
+
+	if (invoke_smc(INTEL_SIP_SMC_RSU_COPY_DCMF_VERSION, args,
+		       ARRAY_SIZE(args), NULL, 0))
+		return -EINVAL;
+#elif defined(CONFIG_ARMV8_PSCI)
+	/*
+	 * U-Boot is the secure monitor: stash the versions in secure RAM
+	 * so the in-U-Boot RSU SMC handler can serve them later. Convert
+	 * the address of smc_rsu_dcmf_version to its pre-relocation form.
+	 */
+	dcmf_versions = (char *)__secure_start - CONFIG_ARMV8_SECURE_BASE +
+			(u64)secure_ram_addr(smc_rsu_dcmf_version);
+
+	memcpy(dcmf_versions, versions, sizeof(*versions) * 4);
+#endif
+	return 0;
+}
+
+/**
+ * rsu_dcmf_version() - retrieve the decision firmware version
+ * @versions: pointer to where the four DCMF versions will be stored
+ *
+ * This function is used to retrieve the version of each of the four DCMF copies
+ * in flash and also report the values to the SMC handler.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_dcmf_version(u32 *versions)
+{
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (!versions)
+		return -EARGS;
+
+	ret = rsu_ll()->fw_ops.dcmf_version(versions);
+	if (ret)
+		return ret;
+
+	return copy_dcmf_version_to_smc(versions);
+}
+
+/**
+ * rsu_max_retry() - retrieve the max_retry parameter
+ * @value: pointer to where the max_retry will be stored
+ *
+ * This function is used to retrieve the max_retry parameter from the decision
+ * firmware section in flash
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_max_retry(u8 *value)
+{
+#if !defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_ATF)
+	u64 arg;
+#endif
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (!value)
+		return -EARGS;
+
+	ret = rsu_ll()->fw_ops.max_retry(value);
+	if (ret)
+		return ret;
+
+#if !defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_ATF)
+	arg = *value;
+	if (invoke_smc(INTEL_SIP_SMC_RSU_COPY_MAX_RETRY, &arg, 1, NULL, 0))
+		return -EINVAL;
+	return 0;
+#elif defined(CONFIG_ARMV8_PSCI)
+	return smc_store_max_retry(*value);
+#else
+	return 0;
+#endif
+}
+
+#ifdef CONFIG_ARMV8_PSCI
+extern u16 smc_rsu_dcmf_status[4];
+#endif
+
+static int copy_dcmf_status_to_smc(u16 *status)
+{
+#if !defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_ATF)
+	u64 arg;
+#elif defined(CONFIG_ARMV8_PSCI)
+	void *dcmf_status;
+#endif
+
+	if (!status)
+		return -EINVAL;
+
+#if !defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_ATF)
+	arg = ((u64)status[3] << 48) | ((u64)status[2] << 32) |
+	      ((u64)status[1] << 16) | status[0];
+	if (invoke_smc(INTEL_SIP_SMC_RSU_COPY_DCMF_STATUS, &arg, 1, NULL, 0))
+		return -EINVAL;
+#elif defined(CONFIG_ARMV8_PSCI)
+	/*
+	 * U-Boot is the secure monitor: stash the status in secure RAM so
+	 * the in-U-Boot RSU SMC handler can serve it later. Convert the
+	 * address of smc_rsu_dcmf_status to its pre-relocation form.
+	 */
+	dcmf_status = (char *)__secure_start - CONFIG_ARMV8_SECURE_BASE +
+			(u64)secure_ram_addr(smc_rsu_dcmf_status);
+
+	memcpy(dcmf_status, status, sizeof(*status) * 4);
+#endif
+	return 0;
+}
+
+/**
+ * rsu_dcmf_status() - retrieve the decision firmware status
+ * @status: pointer to where the statuses will be stored
+ *
+ * This function is used to determine whether decision firmware copies are
+ * corrupted in flash, with the currently used decision firmware being used as
+ * reference. The status is an array of 4 values, one for each decision
+ * firmware copy. A 0 means the copy is fine, anything else means the copy is
+ * corrupted. The status is also reported to the SMC handler.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_dcmf_status(u16 *status)
+{
+	int ret;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (!status)
+		return -EARGS;
+
+	ret = rsu_ll()->fw_ops.dcmf_status(status);
+	if (ret)
+		return ret;
+
+	return copy_dcmf_status_to_smc(status);
+}
+
+/**
+ * rsu_create_empty_cpb() - create a CPB with header field only
+ *
+ * This function is used to create a empty configuration pointer block
+ * (CPB) with the header field only.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_create_empty_cpb(void)
+{
+	if (!rsu_ll())
+		return -EINTF;
+
+	return rsu_ll()->cpb_ops.empty();
+}
+
+/**
+ * rsu_restore_cpb() - restore the cpb from an address
+ * @address: the address which cpb will be restored from.
+ *
+ * This function is used to restore a saved CPB from an address.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_restore_cpb(u64 address)
+{
+	if (!rsu_ll())
+		return -EINTF;
+
+	return rsu_ll()->cpb_ops.restore(address);
+}
+
+/**
+ * rsu_save_cpb() - save cpb to the address
+ * @address: the address which cpb will be saved to.
+ *
+ * This function is used to save CPB to an address.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_save_cpb(u64 address)
+{
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->cpb_ops.corrupted()) {
+		rsu_cpb_corrupted_info();
+		return -ECORRUPTED_CPB;
+	}
+
+	return rsu_ll()->cpb_ops.save(address);
+}
+
+/**
+ * rsu_restore_spt() - restore the spt from an address
+ * @address: the address which spt will be restored from.
+ *
+ * This function is used to restore a saved SPT from an address.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_restore_spt(u64 address)
+{
+	if (!rsu_ll())
+		return -EINTF;
+
+	return rsu_ll()->spt_ops.restore(address);
+}
+
+/**
+ * rsu_save_spt() - save spt to the address
+ * @address: the address which spt will be saved to.
+ *
+ * This function is used to save SPT to an address.
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_save_spt(u64 address)
+{
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	return rsu_ll()->spt_ops.save(address);
+}
+
+/**
+ * rsu_running_factory() - determine if current running image is factory image
+ * @factory: set to non-zero value when running factory image, zero otherwise
+ *
+ * Returns: 0 on success, or error code
+ */
+int rsu_running_factory(int *factory)
+{
+	s64 factory_offset;
+	struct rsu_status_info status;
+
+	if (!rsu_ll())
+		return -EINTF;
+
+	if (rsu_ll()->spt_ops.corrupted()) {
+		rsu_spt_corrupted_info();
+		return -ECORRUPTED_SPT;
+	}
+
+	factory_offset = rsu_ll()->partition.factory_offset();
+	if (factory_offset < 0)
+		return -ELOWLEVEL;
+
+	if (rsu_ll()->fw_ops.status(&status))
+		return -ELOWLEVEL;
+
+	*factory = (factory_offset == status.current_image);
+	return 0;
+}
diff --git a/arch/arm/mach-socfpga/rsu_ll_qspi.c b/arch/arm/mach-socfpga/rsu_ll_qspi.c
new file mode 100644
index 00000000000..423df4dd676
--- /dev/null
+++ b/arch/arm/mach-socfpga/rsu_ll_qspi.c
@@ -0,0 +1,2402 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Intel Corporation
+ * Copyright (C) 2026 Altera Corporation <www.altera.com>
+ */
+
+#include <env.h>
+#include <limits.h>
+#include <malloc.h>
+#include <spi.h>
+#include <spi_flash.h>
+#include <stdio.h>
+#include <asm/arch/mailbox_s10.h>
+#if CONFIG_IS_ENABLED(DM_SPI_FLASH)
+#include <dm/device.h>
+#endif
+#include <asm/arch/rsu_flash_if.h>
+#include <asm/arch/rsu.h>
+#include <asm/arch/rsu_misc.h>
+#include <asm/arch/smc_api.h>
+#include <linux/errno.h>
+#include <linux/intel-smc.h>
+#include <linux/kconfig.h>
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+#include <linux/string.h>
+#include <u-boot/zlib.h>
+
+#define SPT_MAGIC_NUMBER	0x57713427
+#define SPT_FLAG_RESERVED	1
+#define SPT_FLAG_READONLY	2
+#define SPT_OFFSET_MBOX		4
+
+#define CPB_MAGIC_NUMBER	0x57789609
+#define CPB_HEADER_SIZE		24
+
+#define ERASED_ENTRY		((u64)-1)
+#define SPENT_ENTRY		((u64)0)
+
+#define SPT_VERSION		0
+#define LIBRSU_VER		0
+
+#if IS_ENABLED(CONFIG_TARGET_SOCFPGA_STRATIX10)
+#define DCMF_SIZE		0x040000
+#define DCMF0_VERSION_OFFSET	0x000420
+#define DCMF1_VERSION_OFFSET	0x040420
+#define DCMF2_VERSION_OFFSET	0x080420
+#define DCMF3_VERSION_OFFSET	0x0C0420
+#define DCIO_MAX_RETRY_OFFSET   0x10018C
+#else
+#define DCMF_SIZE		0x080000
+#define DCMF0_VERSION_OFFSET	0x000420
+#define DCMF1_VERSION_OFFSET	0x080420
+#define DCMF2_VERSION_OFFSET	0x100420
+#define DCMF3_VERSION_OFFSET	0x180420
+#define DCIO_MAX_RETRY_OFFSET   0x20018C
+#endif
+
+/* maximum supported QSPI from programmer */
+#define QSPI_MAX_DEVICE		4
+#define SPT_MAX_PARTITIONS	127
+#define MIN_QSPI_ERASE_SIZE	4096
+
+#define CPB_SIZE		SZ_4K
+#define SPT_SIZE		SZ_4K
+#define CPB_IMAGE_PTR_OFFSET	32
+#define CPB_IMAGE_PTR_NSLOTS	508
+
+#define SPT_CHECKSUM_OFFSET	0x0C
+
+#define FACTORY_IMAGE_NAME	"FACTORY_IMAGE"
+
+/**
+ * struct sub_partition_table_partition - SPT partition structure
+ * @name: sub-partition name
+ * @offset: sub-partition start offset
+ * @length: sub-partition length
+ * @flags: sub-partition flags
+ */
+struct sub_partition_table_partition {
+	char name[16];
+	u64 offset;
+	u32 length;
+	u32 flags;
+};
+
+/**
+ * struct sub_partition_table - sub partition table structure
+ * @magic_number: the magic number
+ * @version: version number
+ * @partitions: number of entries
+ * @revd: reserved
+ * @sub_partition_table_partition.partition: SPT partition array
+ */
+struct sub_partition_table {
+	u32 magic_number;
+	u32 version;
+	u32 partitions;
+	u32 checksum;
+	u32 rsvd[4];
+	struct sub_partition_table_partition partition[SPT_MAX_PARTITIONS];
+};
+
+/**
+ * union cmf_pointer_block - CMF pointer block
+ * @header.magic_number: CMF pointer block magic number
+ * @header.header_size: size of CMF pointer block header
+ * @header.cpb_size: size of CMF pointer block
+ * @header.cpb_reserved: reserved
+ * @header.image_ptr_offset: offset of image pointers
+ * @header.image_ptr_slots: number of image pointer slots
+ * @data: image pointer slot array
+ */
+union cmf_pointer_block {
+	struct {
+		u32 magic_number;
+		u32 header_size;
+		u32 cpb_size;
+		u32 cpb_reserved;
+		u32 image_ptr_offset;
+		u32 image_ptr_slots;
+	} header;
+	char data[4 * 1024];
+};
+
+/* retrieve multiple qspi info from mailbox */
+struct flash_info {
+	u32 size;
+	u32 erasesize;
+};
+
+/**
+ * struct rsu_qspi_priv - per-session QSPI RSU backend state
+ *
+ * Allocated in rsu_ll_qspi_init(), released in ll_exit(); SPT/CPB cached
+ * copies are memset before free to shorten sensitive data lifetime in RAM.
+ */
+struct rsu_qspi_priv {
+	union cmf_pointer_block cpb;
+	struct sub_partition_table spt;
+	u64 *cpb_slots;
+#if CONFIG_IS_ENABLED(DM_SPI_FLASH)
+	struct udevice **flashlist;
+	struct udevice *flash;
+#else
+	struct spi_flash **flashlist;
+	struct spi_flash *flash;
+#endif
+	u32 spt0_offset;
+	u32 spt1_offset;
+	int cpb0_part;
+	int cpb1_part;
+	int num_flash;
+	bool cpb_corrupted;
+	bool cpb_fixed;
+	bool spt_corrupted;
+};
+
+static struct rsu_qspi_priv *qspi_ctx;
+
+#define P (qspi_ctx)
+
+static int load_cpb(void);
+static int check_spt(void);
+
+/**
+ * get_part_offset() - get a selected partition offset
+ * @part_num: the selected partition number
+ * @offset: the partition offset
+ *
+ * Return: 0 on success, or -ve on error
+ */
+static int get_part_offset(int part_num, u64 *offset)
+{
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return -EINVAL;
+
+	*offset = P->spt.partition[part_num].offset;
+
+	return 0;
+}
+
+/*
+ * @brief Calculates the current flash offset based on the given offset.
+ *
+ * This function calculates the current flash offset and the current flash index
+ * based on the given offset. It iterates through the flashlist array and checks
+ * if the current offset is greater than the size of each flash. If it is, it
+ * subtracts the size of the flash from the current offset and continues to the
+ * next flash. If the current offset is not greater than the size of the flash,
+ * it sets the current flash index and breaks out of the loop.
+ *
+ * @param offset The offset to calculate the current flash offset from.
+ * @param current_offset Pointer to store the calculated current offset.
+ * @param current_flash Pointer to store the index of the current flash.
+ */
+
+static int get_current_flash_offset(u64 offset, u32 *current_offset,
+				    int *current_flash)
+{
+	u64 relative_offset = offset;
+
+	if (!current_offset || !current_flash)
+		return -EINVAL;
+
+	for (int j = 0; j < P->num_flash && j < QSPI_MAX_DEVICE; j++) {
+		u32 sz = rsu_mtd_size(P->flashlist[j]);
+
+		if (!sz)
+			return -EINVAL;
+		if (relative_offset >= sz) {
+			relative_offset -= sz;
+			continue;
+		} else {
+			/* relative_offset < sz here; u32 keeps the assignment loss-free. */
+			*current_flash = j;
+			*current_offset = (u32)relative_offset;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * read_dev() - read data from flash
+ * @offset: the offset which read from flash
+ * @buf: buffer for read data
+ * @len: the size of data which read from flash
+ *
+ * Return: 0 on success, or -ve for error
+ */
+static int read_dev(u64 offset, void *buf, int len)
+{
+	int ret, current_flash;
+	u32 count, current_len, current_offset;
+
+	if (len < 0)
+		return -EINVAL;
+	/*
+	 * Preserve the long-standing no-op convention: callers (notably
+	 * read_part) may pass len == 0 at offset == flash/partition end,
+	 * which the per-flash boundary check would otherwise reject.
+	 */
+	if (len == 0)
+		return 0;
+
+	count = 0;
+
+	ret = get_current_flash_offset(offset, &current_offset, &current_flash);
+	if (ret)
+		return ret;
+
+	for (int i = current_flash; i < P->num_flash && i < QSPI_MAX_DEVICE;
+	     i++) {
+		u32 sz = rsu_mtd_size(P->flashlist[i]);
+
+		/* break if total data length is done */
+		if (count == (u32)len)
+			break;
+
+		/*
+		 * Re-validate per iteration: only the first flash was checked
+		 * by get_current_flash_offset(), and a later device may report
+		 * sz == 0.
+		 */
+		if (!sz || current_offset > sz) {
+			rsu_log(RSU_ERR,
+				"%s: flash %d invalid size %u or offset %u\n",
+				__func__, i, sz, current_offset);
+			return -EINVAL;
+		}
+
+		/* Compute in u64 so (len + current_offset) cannot wrap past 4 GiB. */
+		if ((u64)(u32)len + current_offset - count > sz)
+			current_len = sz - current_offset;
+		else
+			current_len = (u32)len - count;
+
+		ret = rsu_mtd_read(P->flashlist[i], current_offset,
+				   (int)current_len, buf);
+		if (ret) {
+			rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+			return ret;
+		}
+
+		buf = (char *)buf + current_len;
+
+		/* reset the offset to new flash */
+		current_offset = 0;
+		count += current_len;
+	}
+
+	return 0;
+}
+
+/**
+ * write_dev() - write data to flash
+ * @offset: the offset which data will written to
+ * @buf: the written data
+ * @len: the size of data which write to flash
+ *
+ * Return: 0 on success, or -ve for error
+ */
+static int write_dev(u64 offset, void *buf, int len)
+{
+	int ret, current_flash;
+	u32 count, current_len, current_offset;
+
+	if (len < 0)
+		return -EINVAL;
+	/* See read_dev() for why len == 0 is treated as a no-op. */
+	if (len == 0)
+		return 0;
+
+	count = 0;
+
+	ret = get_current_flash_offset(offset, &current_offset, &current_flash);
+	if (ret)
+		return ret;
+
+	for (int i = current_flash; i < P->num_flash && i < QSPI_MAX_DEVICE;
+	     i++) {
+		u32 sz = rsu_mtd_size(P->flashlist[i]);
+
+		/* break if total data length is done */
+		if (count == (u32)len)
+			break;
+
+		/* See read_dev() for why these guards are required. */
+		if (!sz || current_offset > sz) {
+			rsu_log(RSU_ERR,
+				"%s: flash %d invalid size %u or offset %u\n",
+				__func__, i, sz, current_offset);
+			return -EINVAL;
+		}
+
+		if ((u64)(u32)len + current_offset - count > sz)
+			current_len = sz - current_offset;
+		else
+			current_len = (u32)len - count;
+
+		ret = rsu_mtd_write(P->flashlist[i], current_offset,
+				    (int)current_len, buf);
+		if (ret) {
+			rsu_log(RSU_ERR, "write flash error=%i\n", ret);
+			return ret;
+		}
+
+		buf = (char *)buf + current_len;
+
+		/* reset the offset to new flash */
+		current_offset = 0;
+		count += current_len;
+	}
+
+	return 0;
+}
+
+/**
+ * erase_dev() - erase data at flash
+ * @offset: the offset from which data will be erased
+ * @len: the size of data to be erased
+ *
+ * Return: 0 on success, or -ve for error
+ */
+static int erase_dev(u64 offset, int len)
+{
+	int ret, current_flash;
+	u32 count, current_len, current_offset;
+
+	if (len < 0)
+		return -EINVAL;
+	/* See read_dev() for why len == 0 is treated as a no-op. */
+	if (len == 0)
+		return 0;
+
+	count = 0;
+
+	ret = get_current_flash_offset(offset, &current_offset, &current_flash);
+	if (ret)
+		return ret;
+
+	for (int i = current_flash; i < P->num_flash && i < QSPI_MAX_DEVICE;
+	     i++) {
+		u32 sz = rsu_mtd_size(P->flashlist[i]);
+
+		if (count >= (u32)len)
+			break;
+
+		/* See read_dev() for why these guards are required. */
+		if (!sz || current_offset > sz) {
+			rsu_log(RSU_ERR,
+				"%s: flash %d invalid size %u or offset %u\n",
+				__func__, i, sz, current_offset);
+			return -EINVAL;
+		}
+
+		if ((u64)(u32)len + current_offset - count > sz)
+			current_len = sz - current_offset;
+		else
+			current_len = (u32)len - count;
+
+		ret = rsu_mtd_erase(P->flashlist[i], current_offset,
+				    (int)current_len);
+		if (ret) {
+			rsu_log(RSU_ERR, "erase flash error=%i\n", ret);
+			return ret;
+		}
+
+		current_offset = 0;
+		count += current_len;
+	}
+
+	return 0;
+}
+
+/**
+ * read_part() - read a selected partition data
+ * @part_num: the selected partition number
+ * @offset: the offset from which data will be read
+ * @buf: buffer contains the read data
+ * @len: the size of data to be read
+ *
+ * Return: 0 on success, or -ve for error
+ */
+static int read_part(int part_num, u64 offset, void *buf, int len)
+{
+	u64 part_offset;
+	int ret;
+
+	ret = get_part_offset(part_num, &part_offset);
+	if (ret)
+		return ret;
+
+	if (len < 0)
+		return -EINVAL;
+
+	if (offset > P->spt.partition[part_num].length ||
+	    (u64)len > P->spt.partition[part_num].length - offset)
+		return -EINVAL;
+
+	return read_dev(part_offset + offset, buf, len);
+}
+
+/**
+ * write_part() - write a selected partition data
+ * @part_num: the selected partition number
+ * @offset: the offset to which data will be written
+ * @buf: data to be written to
+ * @len: the size of data to be written
+ *
+ * Return: 0 on success, or -ve for error
+ */
+static int write_part(int part_num, u64 offset, void *buf, int len)
+{
+	u64 part_offset;
+	int ret;
+
+	ret = get_part_offset(part_num, &part_offset);
+	if (ret)
+		return ret;
+
+	if (len < 0)
+		return -EINVAL;
+
+	if (offset > P->spt.partition[part_num].length ||
+	    (u64)len > P->spt.partition[part_num].length - offset)
+		return -EINVAL;
+
+	return write_dev(part_offset + offset, buf, len);
+}
+
+/**
+ * erase_part() - erase a selected partition data
+ * @part_num: the selected partition number
+ *
+ * Return: 0 on success, or -ve for error
+ */
+static int erase_part(int part_num)
+{
+	u64 part_offset;
+	int ret;
+
+	ret = get_part_offset(part_num, &part_offset);
+	if (ret)
+		return ret;
+
+	return erase_dev(part_offset, P->spt.partition[part_num].length);
+}
+
+/**
+ * save_spt_to_address() - save spt to the address
+ * @address: the address which spt is saved to
+ *
+ * Return: 0 for successful operation, or -ve on error
+ */
+static int save_spt_to_address(u64 address)
+{
+	int ret;
+	char *spt_data_dst = (char *)address;
+	char *spt_data_src;
+	u32 calc_crc;
+
+	if (!spt_data_dst) {
+		rsu_log(RSU_ERR, "failed due to invalid address\n");
+		return -EINVAL;
+	}
+
+	spt_data_src = (char *)malloc(SPT_SIZE);
+	if (!spt_data_src) {
+		rsu_log(RSU_ERR, "failed to allocate spt_data_src\n");
+		return -ENOMEM;
+	}
+
+	ret = read_dev(P->spt0_offset, spt_data_src, SPT_SIZE);
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to read SPT data\n");
+		free(spt_data_src);
+		return ret;
+	}
+
+	calc_crc = crc32(0, (void *)spt_data_src, SPT_SIZE);
+	rsu_log(RSU_DEBUG, "%s - calc_crc is 0x%x\n", __func__, calc_crc);
+	memcpy(spt_data_dst, spt_data_src, SPT_SIZE);
+	memcpy(spt_data_dst + SPT_SIZE, &calc_crc, sizeof(calc_crc));
+	rsu_log(RSU_INFO, "%ld bytes SPT data saved\n",
+		SPT_SIZE + sizeof(calc_crc));
+	env_set_hex("filesize", SPT_SIZE + sizeof(calc_crc));
+
+	free(spt_data_src);
+	return ret;
+}
+
+/**
+ * corrupted_spt() - check if spt is corrupted
+ *
+ * Return: 1 for the corrupted spt , or 0 for not
+ */
+static int corrupted_spt(void)
+{
+	return P->spt_corrupted;
+}
+
+/**
+ * writeback_spt() - write back SPT
+ *
+ * Return: 0 on success, or -ve on error
+ */
+static int writeback_spt(void)
+{
+	int x;
+	int updates = 0;
+	char *spt_data;
+	u32 calc_crc;
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		if (strcmp(P->spt.partition[x].name, "SPT0") &&
+		    strcmp(P->spt.partition[x].name, "SPT1"))
+			continue;
+
+		if (erase_part(x)) {
+			rsu_log(RSU_ERR, "failed to erase SPTx");
+			return -EIO;
+		}
+
+		if (P->spt.version > SPT_VERSION &&
+		    rsu_misc_spt_checksum_enabled()) {
+			rsu_log(RSU_DEBUG, "update SPT checksum...\n");
+			spt_data = (char *)malloc(SPT_SIZE);
+			if (!spt_data) {
+				rsu_log(RSU_ERR,
+					"failed to allocate spt_data\n");
+				return -ENOMEM;
+			}
+
+			P->spt.checksum = (u32)0xFFFFFFFF;
+			if (write_part(x, SPT_CHECKSUM_OFFSET,
+				       &P->spt.checksum,
+				       sizeof(P->spt.checksum))) {
+				rsu_log(RSU_ERR,
+					"failed to write SPTx table");
+				free(spt_data);
+				return -EINVAL;
+			}
+
+			memcpy(spt_data, &P->spt, SPT_SIZE);
+			memset(spt_data + SPT_CHECKSUM_OFFSET, 0,
+			       sizeof(P->spt.checksum));
+
+			swap_bits(spt_data, SPT_SIZE);
+			calc_crc = crc32(0, (void *)spt_data, SPT_SIZE);
+			P->spt.checksum = be32_to_cpu(calc_crc);
+			swap_bits(spt_data, SPT_SIZE);
+			free(spt_data);
+
+			if (write_part(x, SPT_CHECKSUM_OFFSET,
+				       &P->spt.checksum,
+				       sizeof(P->spt.checksum))) {
+				rsu_log(RSU_ERR,
+					"failed to write SPTx table");
+				return -EINVAL;
+			}
+		}
+
+		P->spt.magic_number = (u32)0xFFFFFFFF;
+		if (write_part(x, 0, &P->spt, sizeof(P->spt))) {
+			rsu_log(RSU_ERR, "failed to write SPTx table");
+			return -EIO;
+		}
+
+		P->spt.magic_number = (u32)SPT_MAGIC_NUMBER;
+		if (write_part(x, 0, &P->spt.magic_number,
+			       sizeof(P->spt.magic_number))) {
+			rsu_log(RSU_ERR, "failed to write SPTx magic #");
+			return -EIO;
+		}
+
+		updates++;
+	}
+
+	if (updates != 2) {
+		rsu_log(RSU_ERR, "didn't find two SPTs");
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/**
+ * restore_spt_from_address() - restore the spt from an address
+ * @address: the address which spt is restored from
+ *
+ * Return: 0 for successful operation, or -ve on error
+ */
+static int restore_spt_from_address(u64 address)
+{
+	int ret;
+	u32 calc_crc;
+	u32 crc_from_saved;
+	u32 magic_number;
+	char *spt_data = (char *)address;
+
+	if (!spt_data) {
+		rsu_log(RSU_ERR, "failed due to invalid address\n");
+		return -EINVAL;
+	}
+
+	calc_crc = crc32(0, (void *)spt_data, SPT_SIZE);
+	rsu_log(RSU_DEBUG, "%s - calc_crc is 0x%x\n", __func__, calc_crc);
+	memcpy(&crc_from_saved, spt_data + SPT_SIZE, sizeof(crc_from_saved));
+	rsu_log(RSU_DEBUG, "%s - crc_from_saved is 0x%x\n", __func__,
+		crc_from_saved);
+
+	if (calc_crc != crc_from_saved) {
+		rsu_log(RSU_ERR, "saved data is corrupted\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * check the magic number to prevent user from accidentally
+	 * restoring CPB
+	 */
+	memcpy(&magic_number, spt_data, sizeof(magic_number));
+	if (magic_number != SPT_MAGIC_NUMBER) {
+		rsu_log(RSU_ERR, "failure due to mismatch magic number\n");
+		return -EINVAL;
+	}
+
+	memcpy(&P->spt, spt_data, SPT_SIZE);
+
+	/*
+	 * CRC+magic only prove self-consistency of the supplied image; a
+	 * crafted SPT could still carry an out-of-range partition count or
+	 * overlapping regions. Re-run the full validator before committing.
+	 */
+	ret = check_spt();
+	if (ret) {
+		rsu_log(RSU_ERR, "restored SPT failed validation\n");
+		P->spt_corrupted = true;
+		return ret;
+	}
+
+	ret = writeback_spt();
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to write back spt\n");
+		return ret;
+	}
+
+	P->spt_corrupted = false;
+
+	/* try to reload CPB, as we have a new SPT */
+	P->cpb_corrupted = false;
+	if (load_cpb() && !P->cpb_corrupted)
+		rsu_log(RSU_ERR, "Bad CPB\n");
+
+	return 0;
+}
+
+/**
+ * check_spt() - check if SPT is valid
+ *
+ * Return: 0 for valid SPT, or -ve on error
+ */
+static int check_spt(void)
+{
+	int x;
+	int y;
+	int max_len = sizeof(P->spt.partition[0].name);
+	int spt0_found = 0;
+	int spt1_found = 0;
+	int cpb0_found = 0;
+	int cpb1_found = 0;
+
+	u32 calc_crc;
+	char *spt_data;
+
+	/*
+	 * Make sure the SPT names are '\0' terminated. Truncate last byte
+	 * if the name uses all available bytes.  Perform validity check on
+	 * entries.
+	 */
+
+	rsu_log(RSU_DEBUG,
+		"MAX length of a name = %i bytes\n", max_len - 1);
+
+	if (P->spt.version > SPT_VERSION &&
+	    rsu_misc_spt_checksum_enabled()) {
+		rsu_log(RSU_DEBUG, "check SPT checksum...\n");
+
+		spt_data = (char *)malloc(SPT_SIZE);
+		if (!spt_data) {
+			rsu_log(RSU_ERR, "failed to allocate spt_data\n");
+			return -ENOMEM;
+		}
+
+		memcpy(spt_data, &P->spt, SPT_SIZE);
+		memset(spt_data + SPT_CHECKSUM_OFFSET, 0,
+		       sizeof(P->spt.checksum));
+
+		swap_bits(spt_data, SPT_SIZE);
+		calc_crc = crc32(0, (void *)spt_data, SPT_SIZE);
+		if (be32_to_cpu(P->spt.checksum) != calc_crc) {
+			rsu_log(RSU_ERR, "Error, bad SPT checksum\n");
+			free(spt_data);
+			return -EINVAL;
+		}
+		swap_bits(spt_data, SPT_SIZE);
+		free(spt_data);
+	}
+
+	if (P->spt.partitions > SPT_MAX_PARTITIONS) {
+		rsu_log(RSU_ERR, "bigger than max partition\n");
+		return -EINVAL;
+	}
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		u64 s_start;
+		u64 s_len;
+		u64 s_end;
+
+		if (strnlen(P->spt.partition[x].name, max_len) >= max_len)
+			P->spt.partition[x].name[max_len - 1] = '\0';
+
+		s_start = P->spt.partition[x].offset;
+		s_len = P->spt.partition[x].length;
+
+		/* Zero length would underflow the inclusive end below. */
+		if (s_len == 0) {
+			rsu_log(RSU_ERR,
+				"SPT entry %d (%s): zero-length partition\n",
+				x, P->spt.partition[x].name);
+			return -EINVAL;
+		}
+
+		/* Reject offset+length wraparound; otherwise overlap test passes spuriously. */
+		if (s_len > U64_MAX - s_start) {
+			rsu_log(RSU_ERR,
+				"SPT entry %d: offset+length overflows u64\n",
+				x);
+			return -EINVAL;
+		}
+		s_end = s_start + s_len;
+
+		rsu_log(RSU_DEBUG, "RSU %-16s %016llX - %016llX (%X)\n",
+			P->spt.partition[x].name, s_start, s_end - 1,
+			P->spt.partition[x].flags);
+
+		for (y = 0; y < P->spt.partitions; y++) {
+			if (x == y)
+				continue;
+
+			/*
+			 * don't allow the same partition name to appear
+			 * more than once
+			 */
+			if (!(strcmp(P->spt.partition[x].name,
+				     P->spt.partition[y].name))) {
+				rsu_log(RSU_ERR, "partition name ");
+				rsu_log(RSU_ERR, "appears more than once\n");
+				return -EINVAL;
+			}
+
+			u64 d_start = P->spt.partition[y].offset;
+			u64 d_len = P->spt.partition[y].length;
+			u64 d_end;
+
+			if (d_len > U64_MAX - d_start) {
+				rsu_log(RSU_ERR,
+					"SPT entry %d: offset+length overflows u64\n",
+					y);
+				return -EINVAL;
+			}
+			d_end = d_start + d_len;
+
+			if (s_start < d_end && s_end > d_start) {
+				rsu_log(RSU_ERR, "partition overlap\n");
+				return -EINVAL;
+			}
+		}
+
+		if (strcmp(P->spt.partition[x].name, "SPT0") == 0)
+			spt0_found = 1;
+		else if (strcmp(P->spt.partition[x].name, "SPT1") == 0)
+			spt1_found = 1;
+		else if (strcmp(P->spt.partition[x].name, "CPB0") == 0)
+			cpb0_found = 1;
+		else if (strcmp(P->spt.partition[x].name, "CPB1") == 0)
+			cpb1_found = 1;
+	}
+
+	if (!spt0_found || !spt1_found || !cpb0_found || !cpb1_found) {
+		rsu_log(RSU_ERR, "Missing a critical entry in the SPT\n");
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/**
+ * check_both_spt() - check if both SPTs are same
+ *
+ * Return: 0 for the identical SPTs, or -ve on error
+ */
+static int check_both_spt(void)
+{
+	int ret;
+	char *spt0_data;
+	char *spt1_data;
+
+	spt0_data = (char *)malloc(SPT_SIZE);
+	if (!spt0_data) {
+		rsu_log(RSU_ERR, "failed to allocate spt0_data\n");
+		return -ENOMEM;
+	}
+
+	spt1_data = (char *)malloc(SPT_SIZE);
+	if (!spt1_data) {
+		rsu_log(RSU_ERR, "failed to allocate spt1_data\n");
+		free(spt0_data);
+		return -ENOMEM;
+	}
+
+	ret = read_dev(P->spt0_offset, spt0_data, SPT_SIZE);
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to read spt0_data\n");
+		goto ops_error;
+	}
+
+	ret = read_dev(P->spt1_offset, spt1_data, SPT_SIZE);
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to read spt1_data\n");
+		goto ops_error;
+	}
+
+	ret = memcmp(spt0_data, spt1_data, SPT_SIZE);
+
+ops_error:
+	free(spt1_data);
+	free(spt0_data);
+	return ret;
+}
+
+/**
+ * load_spt() - retrieve SPT from flash
+ *
+ * Return: 0 on success, or -ve on error
+ */
+static int load_spt(void)
+{
+	int spt0_good = 0;
+	int spt1_good = 0;
+	int spt_size = P->spt1_offset - P->spt0_offset;
+
+	rsu_log(RSU_DEBUG, "reading SPT1\n");
+	if (read_dev(P->spt1_offset, &P->spt, sizeof(P->spt)) == 0 &&
+	    P->spt.magic_number == SPT_MAGIC_NUMBER) {
+		if (check_spt() == 0)
+			spt1_good = 1;
+		else
+			rsu_log(RSU_ERR, "SPT1 validity check failed\n");
+	} else {
+		rsu_log(RSU_ERR, "Bad SPT1 magic number 0x%08X\n",
+			P->spt.magic_number);
+	}
+
+	rsu_log(RSU_DEBUG, "reading SPT0\n");
+	if (read_dev(P->spt0_offset, &P->spt, sizeof(P->spt)) == 0 &&
+	    P->spt.magic_number == SPT_MAGIC_NUMBER) {
+		if (check_spt() == 0)
+			spt0_good = 1;
+		else
+			rsu_log(RSU_ERR, "SPT0 validity check failed\n");
+	} else {
+		rsu_log(RSU_ERR, "Bad SPT0 magic number 0x%08X\n",
+			P->spt.magic_number);
+	}
+
+	if (spt0_good && spt1_good) {
+		if (check_both_spt()) {
+			rsu_log(RSU_ERR, "unmatched SPT0/1 data");
+			P->spt_corrupted = true;
+			return -EINVAL;
+		}
+		rsu_log(RSU_INFO, "SPTs are GOOD!!!\n");
+		return 0;
+	}
+
+	if (spt0_good) {
+		rsu_log(RSU_WARNING, "warning: Restoring SPT1\n");
+		if (erase_dev(P->spt1_offset, spt_size)) {
+			rsu_log(RSU_ERR, "Erase SPT1 region failed\n");
+			return -EIO;
+		}
+
+		P->spt.magic_number = (u32)0xFFFFFFFF;
+		if (write_dev(P->spt1_offset, &P->spt, sizeof(P->spt))) {
+			rsu_log(RSU_ERR, "Unable to write SPT1 table\n");
+			return -EIO;
+		}
+
+		P->spt.magic_number = (u32)SPT_MAGIC_NUMBER;
+		if (write_dev(P->spt1_offset, &P->spt.magic_number,
+			      sizeof(P->spt.magic_number))) {
+			rsu_log(RSU_ERR, "Unable to wr SPT1 magic #\n");
+			return -EIO;
+		}
+
+		return 0;
+	}
+
+	if (spt1_good) {
+		if (read_dev(P->spt1_offset, &P->spt, sizeof(P->spt)) ||
+		    P->spt.magic_number != SPT_MAGIC_NUMBER || check_spt()) {
+			rsu_log(RSU_ERR, "Failed to load SPT1\n");
+			return -EUCLEAN;
+		}
+
+		rsu_log(RSU_WARNING, "Restoring SPT0");
+
+		if (erase_dev(P->spt0_offset, spt_size)) {
+			rsu_log(RSU_ERR, "Erase SPT0 region failed\n");
+			return -EIO;
+		}
+
+		P->spt.magic_number = (u32)0xFFFFFFFF;
+		if (write_dev(P->spt0_offset, &P->spt, sizeof(P->spt))) {
+			rsu_log(RSU_ERR, "Unable to write SPT0 table\n");
+			return -EIO;
+		}
+
+		P->spt.magic_number = (u32)SPT_MAGIC_NUMBER;
+		if (write_dev(P->spt0_offset, &P->spt.magic_number,
+			      sizeof(P->spt.magic_number))) {
+			rsu_log(RSU_ERR, "Unable to wr SPT0 magic #\n");
+			return -EIO;
+		}
+
+		return 0;
+	}
+
+	P->spt_corrupted = true;
+	rsu_log(RSU_ERR, "no valid SPT0 and SPT1 found\n");
+	return -EUCLEAN;
+}
+
+/**
+ * cpb_header_access_ok() - validate CPB header fields used to index
+ * cpb_slots[].
+ *
+ * Return: 0 if the header is safe to use, -EINVAL otherwise.
+ */
+static int cpb_header_access_ok(void)
+{
+	u32 ip_off = P->cpb.header.image_ptr_offset;
+	u32 ip_slots = P->cpb.header.image_ptr_slots;
+	u32 max_by_buf;
+
+	if (P->cpb.header.header_size > CPB_HEADER_SIZE) {
+		rsu_log(RSU_WARNING,
+			"CPB header is larger than expected\n");
+		return -EINVAL;
+	}
+	if (ip_off >= CPB_SIZE) {
+		rsu_log(RSU_ERR, "CPB image_ptr_offset out of range\n");
+		return -EINVAL;
+	}
+	/* cpb_slots[] is dereferenced as u64; reject misaligned image_ptr_offset. */
+	if (ip_off % sizeof(u64)) {
+		rsu_log(RSU_ERR, "CPB image_ptr_offset not 8-byte aligned\n");
+		return -EINVAL;
+	}
+	max_by_buf = (CPB_SIZE - ip_off) / sizeof(u64);
+	/* Reject ip_slots == 0 too: zero-iteration loops would silently succeed. */
+	if (!ip_slots || !max_by_buf || ip_slots > max_by_buf ||
+	    ip_slots > CPB_IMAGE_PTR_NSLOTS) {
+		rsu_log(RSU_ERR, "CPB image_ptr_slots out of range\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * check_cpb() - check if CPB is valid
+ *
+ * Return: 0 for the valid CPB, or -ve on error
+ */
+static int check_cpb(void)
+{
+	int x, y;
+	int ret;
+
+	ret = cpb_header_access_ok();
+	if (ret)
+		return ret;
+
+	for (x = 0; x < P->cpb.header.image_ptr_slots; x++) {
+		if (P->cpb_slots[x] == ERASED_ENTRY ||
+		    P->cpb_slots[x] == SPENT_ENTRY)
+			continue;
+
+		for (y = 0; y < P->spt.partitions; y++) {
+			if (P->cpb_slots[x] == P->spt.partition[y].offset) {
+				rsu_log(RSU_DEBUG, "cpb_slots[%i] = %s\n",
+					x, P->spt.partition[y].name);
+				break;
+			}
+		}
+
+		if (y >= P->spt.partitions) {
+			rsu_log(RSU_ERR, "CPB is not included in SPT\n");
+			rsu_log(RSU_DEBUG, "cpb_slots[%i] = %016llX ???",
+				x, P->cpb_slots[x]);
+			return -EINVAL;
+		}
+
+		if (P->spt.partition[y].flags & SPT_FLAG_RESERVED) {
+			rsu_log(RSU_ERR, "CPB is included in SPT ");
+			rsu_log(RSU_ERR, "but it is reserved\n");
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * check_both_cpb() - check if both CPBs are same
+ *
+ * Return: 0 for the identical CPBs, or -ve on error
+ */
+static int check_both_cpb(void)
+{
+	int ret;
+	char *cpb0_data;
+	char *cpb1_data;
+
+	cpb0_data = (char *)malloc(CPB_SIZE);
+	if (!cpb0_data) {
+		rsu_log(RSU_ERR, "failed to allocate cpb0_data\n");
+		return -ENOMEM;
+	}
+
+	cpb1_data = (char *)malloc(CPB_SIZE);
+	if (!cpb1_data) {
+		rsu_log(RSU_ERR, "failed to allocate cpb1_data\n");
+		free(cpb0_data);
+		return -ENOMEM;
+	}
+
+	ret = read_part(P->cpb0_part, 0, cpb0_data, CPB_SIZE);
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to read cpb0_data\n");
+		goto ops_error;
+	}
+
+	ret = read_part(P->cpb1_part, 0, cpb1_data, CPB_SIZE);
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to read cpb1_data\n");
+		goto ops_error;
+	}
+
+	ret = memcmp(cpb0_data, cpb1_data, CPB_SIZE);
+
+ops_error:
+	free(cpb1_data);
+	free(cpb0_data);
+	return ret;
+}
+
+/**
+ * save_cpb_to_address() - save cpb to the address
+ * @address: the address which cpb is saved to
+ *
+ * Return: 0 for successful operation, or -ve on error
+ */
+static int save_cpb_to_address(u64 address)
+{
+	int ret;
+	char *cpb_data_dst = (char *)address;
+	char *cpb_data_src;
+	u32 calc_crc;
+
+	if (!cpb_data_dst) {
+		rsu_log(RSU_ERR, "failed due to invalid address");
+		return -EINVAL;
+	}
+
+	cpb_data_src = (char *)malloc(CPB_SIZE);
+	if (!cpb_data_src) {
+		rsu_log(RSU_ERR, "failed to allocate cpb_data_src\n");
+		return -ENOMEM;
+	}
+
+	ret = read_part(P->cpb0_part, 0, cpb_data_src, CPB_SIZE);
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to read CPB data\n");
+		free(cpb_data_src);
+		return ret;
+	}
+
+	calc_crc = crc32(0, (void *)cpb_data_src, CPB_SIZE);
+	rsu_log(RSU_DEBUG, "%s - calc_crc is 0x%x\n", __func__, calc_crc);
+	memcpy(cpb_data_dst, cpb_data_src, CPB_SIZE);
+	memcpy(cpb_data_dst + CPB_SIZE, &calc_crc, sizeof(calc_crc));
+	rsu_log(RSU_INFO, "%ld bytes CPB data saved\n",
+		CPB_SIZE + sizeof(calc_crc));
+	env_set_hex("filesize", CPB_SIZE + sizeof(calc_crc));
+
+	free(cpb_data_src);
+	return ret;
+}
+
+/**
+ * corrupted_cpb() - check if cpb is corrupted
+ *
+ * Return: 1 for the corrupted cpb , or 0 for not
+ */
+static int corrupted_cpb(void)
+{
+	return P->cpb_corrupted;
+}
+
+/**
+ * load_cpb() - retrieve CPB from flash
+ *
+ * Return: 0 on success, or -ve on error
+ */
+static int load_cpb(void)
+{
+	int x;
+	int cpb0_good = 0;
+	int cpb1_good = 0;
+	struct rsu_status_info status_info;
+	int cpb0_corrupted = 0;
+
+	if (mbox_rsu_status((u32 *)&status_info,
+			    sizeof(status_info) / 4)) {
+		rsu_log(RSU_ERR, "FW doesn't support RSU\n");
+		return -EINVAL;
+	}
+
+	if (!P->cpb_fixed && status_info.state == STATE_CPB0_CPB1_CORRUPTED) {
+		rsu_log(RSU_ERR, "FW detects both CPBs corrupted\n");
+		P->cpb_corrupted = true;
+		return -EINVAL;
+	}
+
+	if (!P->cpb_fixed && status_info.state == STATE_CPB0_CORRUPTED) {
+		rsu_log(RSU_ERR,
+			"FW detects corrupted CPB0 but CPB1 is fine\n");
+		cpb0_corrupted = 1;
+	}
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		if (strcmp(P->spt.partition[x].name, "CPB0") == 0)
+			P->cpb0_part = x;
+		else if (strcmp(P->spt.partition[x].name, "CPB1") == 0)
+			P->cpb1_part = x;
+
+		if (P->cpb0_part >= 0 && P->cpb1_part >= 0)
+			break;
+	}
+
+	if (P->cpb0_part < 0 || P->cpb1_part < 0) {
+		rsu_log(RSU_ERR, "Missing CPB0/1 partition\n");
+		return -ENOENT;
+	}
+
+	rsu_log(RSU_DEBUG, "Reading CPB1\n");
+	if (read_part(P->cpb1_part, 0, &P->cpb, sizeof(P->cpb)) == 0 &&
+	    P->cpb.header.magic_number == CPB_MAGIC_NUMBER &&
+	    cpb_header_access_ok() == 0) {
+		P->cpb_slots = (u64 *)
+			     &P->cpb.data[P->cpb.header.image_ptr_offset];
+		if (check_cpb() == 0)
+			cpb1_good = 1;
+	} else {
+		rsu_log(RSU_ERR, "Bad CPB1 is bad\n");
+	}
+
+	if (!cpb0_corrupted) {
+		rsu_log(RSU_DEBUG, "Reading CPB0\n");
+		if (read_part(P->cpb0_part, 0, &P->cpb, sizeof(P->cpb)) == 0 &&
+		    P->cpb.header.magic_number == CPB_MAGIC_NUMBER &&
+		    cpb_header_access_ok() == 0) {
+			P->cpb_slots = (u64 *)
+				     &P->cpb.data[P->cpb.header.image_ptr_offset];
+			if (check_cpb() == 0)
+				cpb0_good = 1;
+		} else {
+			rsu_log(RSU_ERR, "Bad CPB0 is bad\n");
+		}
+	}
+
+	if (cpb0_good && cpb1_good) {
+		if (check_both_cpb()) {
+			rsu_log(RSU_ERR, "unmatched CPB0/1 data");
+			P->cpb_corrupted = true;
+			return -EINVAL;
+		}
+		rsu_log(RSU_INFO, "CPBs are GOOD!!!\n");
+		P->cpb_slots = (u64 *)
+			     &P->cpb.data[P->cpb.header.image_ptr_offset];
+		return 0;
+	}
+
+	if (cpb0_good) {
+		rsu_log(RSU_WARNING, "Restoring CPB1\n");
+		if (erase_part(P->cpb1_part)) {
+			rsu_log(RSU_ERR, "Failed erase CPB1\n");
+			return -EIO;
+		}
+
+		P->cpb.header.magic_number = (u32)0xFFFFFFFF;
+		if (write_part(P->cpb1_part, 0, &P->cpb, sizeof(P->cpb))) {
+			rsu_log(RSU_ERR, "Unable to write CPB1 table\n");
+			return -EIO;
+		}
+
+		P->cpb.header.magic_number = (u32)CPB_MAGIC_NUMBER;
+		if (write_part(P->cpb1_part, 0, &P->cpb.header.magic_number,
+			       sizeof(P->cpb.header.magic_number))) {
+			rsu_log(RSU_ERR, "Unable to write CPB1 magic number\n");
+			return -EIO;
+		}
+
+		P->cpb_slots = (u64 *)&P->cpb.data[P->cpb.header.image_ptr_offset];
+		return 0;
+	}
+
+	if (cpb1_good) {
+		if (read_part(P->cpb1_part, 0, &P->cpb, sizeof(P->cpb)) ||
+		    P->cpb.header.magic_number != CPB_MAGIC_NUMBER) {
+			rsu_log(RSU_ERR, "Unable to load CPB1\n");
+			return -EUCLEAN;
+		}
+
+		rsu_log(RSU_WARNING, "Restoring CPB0\n");
+		if (erase_part(P->cpb0_part)) {
+			rsu_log(RSU_ERR, "Failed erase CPB0\n");
+			return -EIO;
+		}
+
+		P->cpb.header.magic_number = (u32)0xFFFFFFFF;
+		if (write_part(P->cpb0_part, 0, &P->cpb, sizeof(P->cpb))) {
+			rsu_log(RSU_ERR, "Unable to write CPB0 table\n");
+			return -EIO;
+		}
+
+		P->cpb.header.magic_number = (u32)CPB_MAGIC_NUMBER;
+		if (write_part(P->cpb0_part, 0, &P->cpb.header.magic_number,
+			       sizeof(P->cpb.header.magic_number))) {
+			rsu_log(RSU_ERR, "Unable to write CPB0 magic number\n");
+			return -EIO;
+		}
+
+		P->cpb_slots = (u64 *)&P->cpb.data[P->cpb.header.image_ptr_offset];
+		return 0;
+	}
+
+	P->cpb_corrupted = true;
+	rsu_log(RSU_ERR, "No valid CPB0 or CPB1 found\n");
+	return -EUCLEAN;
+}
+
+/**
+ * update_cpb() - update a CPB slot in flash (best-effort).
+ * @slot: index into P->cpb_slots
+ * @ptr:  new pointer value (NAND-style 1->0 transitions only)
+ *
+ * Writes CPB0 then CPB1; on a mid-write failure (one updated, the other
+ * stale) returns -ve with no rollback. Callers MUST then call load_cpb(),
+ * which reconciles the two flash copies; do not roll back P->cpb locally.
+ *
+ * Return: 0 on success, or -ve on error (caller MUST load_cpb()).
+ */
+static int update_cpb(int slot, u64 ptr)
+{
+	int x;
+	int updates = 0;
+
+	if (slot < 0 || slot >= P->cpb.header.image_ptr_slots)
+		return -EINVAL;
+
+	if ((P->cpb_slots[slot] & ptr) != ptr)
+		return -EINVAL;
+
+	P->cpb_slots[slot] = ptr;
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		if (strcmp(P->spt.partition[x].name, "CPB0") &&
+		    strcmp(P->spt.partition[x].name, "CPB1"))
+			continue;
+
+		if (write_part(x, 0, &P->cpb, sizeof(P->cpb)))
+			return -EIO;
+
+		updates++;
+	}
+
+	if (updates != 2) {
+		rsu_log(RSU_ERR, "Did not find two CPBs\n");
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/**
+ * writeback_cpb() - write CPB back to flash
+ *
+ * Return: 0 on success, or -ve on error
+ */
+static int writeback_cpb(void)
+{
+	int x;
+	int updates = 0;
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		if (strcmp(P->spt.partition[x].name, "CPB0") &&
+		    strcmp(P->spt.partition[x].name, "CPB1"))
+			continue;
+
+		if (erase_part(x)) {
+			rsu_log(RSU_ERR, "Unable to ease CPBx\n");
+			return -EIO;
+		}
+
+		P->cpb.header.magic_number = (u32)0xFFFFFFFF;
+		if (write_part(x, 0, &P->cpb, sizeof(P->cpb))) {
+			rsu_log(RSU_ERR, "Unable to write CPBx table\n");
+			return -EIO;
+		}
+
+		P->cpb.header.magic_number = (u32)CPB_MAGIC_NUMBER;
+		if (write_part(x, 0, &P->cpb.header.magic_number,
+			       sizeof(P->cpb.header.magic_number))) {
+			rsu_log(RSU_ERR,
+				"Unable to write CPBx magic number\n");
+			return -EIO;
+		}
+
+		updates++;
+	}
+
+	if (updates != 2) {
+		rsu_log(RSU_ERR, "Did not find two CPBs\n");
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/**
+ * empty_cpb() - create a cpb with header field only
+ *
+ * Return: 0 for successful operation, or -ve on error
+ */
+static int empty_cpb(void)
+{
+	int ret;
+	struct cpb_header {
+		u32 magic_number;
+		u32 header_size;
+		u32 cpb_size;
+		u32 cpb_reserved;
+		u32 image_ptr_offset;
+		u32 image_ptr_slots;
+	} *c_header;
+
+	if (P->spt_corrupted) {
+		rsu_log(RSU_ERR, "corrupted SPT ---");
+		rsu_log(RSU_ERR, "run rsu restore_spt <address> first\n");
+		return -EINVAL;
+	}
+
+	c_header = (struct cpb_header *)malloc(sizeof(struct cpb_header));
+	if (!c_header) {
+		rsu_log(RSU_ERR, "failed to allocate cpb_header\n");
+		return -ENOMEM;
+	}
+
+	c_header->magic_number = CPB_MAGIC_NUMBER;
+	c_header->header_size = CPB_HEADER_SIZE;
+	c_header->cpb_size = CPB_SIZE;
+	c_header->cpb_reserved = 0;
+	c_header->image_ptr_offset = CPB_IMAGE_PTR_OFFSET;
+	c_header->image_ptr_slots = CPB_IMAGE_PTR_NSLOTS;
+
+	memset(&P->cpb, -1, CPB_SIZE);
+	memcpy(&P->cpb, c_header, sizeof(*c_header));
+
+	ret = writeback_cpb();
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to write back cpb\n");
+		goto ops_error;
+	}
+
+	P->cpb_slots = (u64 *)&P->cpb.data[P->cpb.header.image_ptr_offset];
+	P->cpb_corrupted = false;
+	P->cpb_fixed = true;
+
+ops_error:
+	free(c_header);
+	return ret;
+}
+
+/**
+ * restore_cpb_from_address() - restore the cpb from an address
+ * @address: the address which cpb is restored from
+ *
+ * Return: 0 for successful operation, or -ve on error
+ */
+static int restore_cpb_from_address(u64 address)
+{
+	int ret;
+	u32 calc_crc;
+	u32 crc_from_saved;
+	u32 magic_number;
+	char *cpb_data = (char *)address;
+
+	if (P->spt_corrupted) {
+		rsu_log(RSU_ERR, "corrupted SPT --");
+		rsu_log(RSU_ERR, "run rsu restore_spt <address> first\n");
+		return -EINVAL;
+	}
+
+	if (!cpb_data) {
+		rsu_log(RSU_ERR, "failed due to invalid address\n");
+		return -EINVAL;
+	}
+
+	calc_crc = crc32(0, (void *)cpb_data, CPB_SIZE);
+	rsu_log(RSU_DEBUG, "%s - calc_crc is 0x%x\n", __func__, calc_crc);
+	memcpy(&crc_from_saved, cpb_data + CPB_SIZE, sizeof(crc_from_saved));
+	rsu_log(RSU_DEBUG, "%s - crc_from_saved is 0x%x\n", __func__,
+		crc_from_saved);
+
+	if (calc_crc != crc_from_saved) {
+		rsu_log(RSU_ERR, "saved data is corrupted\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * check the magic number to prevent user from accidentally
+	 * restoring SPB
+	 */
+	memcpy(&magic_number, cpb_data, sizeof(magic_number));
+	if (magic_number != CPB_MAGIC_NUMBER) {
+		rsu_log(RSU_ERR, "failure due to mismatch magic number\n");
+		return -EINVAL;
+	}
+
+	memcpy(&P->cpb, cpb_data, CPB_SIZE);
+
+	/*
+	 * CRC+magic only prove self-consistency; a crafted header with an
+	 * out-of-range or misaligned image_ptr_offset would otherwise flow
+	 * into cpb_slots[] accesses. Validate before writing back to flash.
+	 */
+	ret = cpb_header_access_ok();
+	if (ret) {
+		rsu_log(RSU_ERR, "restored CPB has invalid header\n");
+		P->cpb_slots = NULL;
+		P->cpb_corrupted = true;
+		return ret;
+	}
+
+	P->cpb_slots = (u64 *)&P->cpb.data[P->cpb.header.image_ptr_offset];
+	ret = check_cpb();
+	if (ret) {
+		rsu_log(RSU_ERR, "restored CPB failed validation\n");
+		P->cpb_slots = NULL;
+		P->cpb_corrupted = true;
+		return ret;
+	}
+
+	ret = writeback_cpb();
+	if (ret) {
+		rsu_log(RSU_ERR, "failed to write back cpb\n");
+		return ret;
+	}
+
+	P->cpb_corrupted = false;
+	P->cpb_fixed = true;
+	return 0;
+}
+
+/**
+ * partition_count() - get the partition count
+ *
+ * Return: the number of partition at flash
+ */
+static int partition_count(void)
+{
+	return P->spt.partitions;
+}
+
+/**
+ * partition_name() - get a selected partition name
+ * @part_num: the selected partition number
+ *
+ * Return: partition name on success, or "BAD" on error
+ */
+static char *partition_name(int part_num)
+{
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return "BAD";
+
+	return P->spt.partition[part_num].name;
+}
+
+/**
+ * partition_offset() - get a selected partition offset
+ * @part_num: the selected partition number
+ *
+ * Return: offset on success, or -1 on error
+ */
+static u64 partition_offset(int part_num)
+{
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return -1;
+
+	return P->spt.partition[part_num].offset;
+}
+
+/**
+ * factory_offset() - get the offset of the factory image
+ *
+ * Return: offset on success, or -ENOENT if factory image not found
+ */
+static s64 factory_offset(void)
+{
+	int x;
+
+	for (x = 0; x < P->spt.partitions; x++)
+		if (strncmp(P->spt.partition[x].name, FACTORY_IMAGE_NAME,
+			    sizeof(P->spt.partition[0].name) - 1) == 0)
+			return P->spt.partition[x].offset;
+
+	return -ENOENT;
+}
+
+/**
+ * partition_size() - get a selected partition size
+ * @part_num: the selected partition number
+ *
+ * Return: the partition size for success, or -1 for error
+ */
+static u32 partition_size(int part_num)
+{
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return -1;
+
+	return P->spt.partition[part_num].length;
+}
+
+/**
+ * partition_reserved() - check if a selected partition is reserved
+ * @part_num: the selected partition number
+ *
+ * Return: 1 for reserved partition, or 0 for not
+ */
+static int partition_reserved(int part_num)
+{
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return 0;
+
+	return (P->spt.partition[part_num].flags & SPT_FLAG_RESERVED) ? 1 : 0;
+}
+
+/**
+ * partition_readonly() - check if a selected partition is read only
+ * @part_num: the selected partition number
+ *
+ * Return: 1 for read only partition, or 0 for not
+ */
+static int partition_readonly(int part_num)
+{
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return 0;
+
+	return (P->spt.partition[part_num].flags & SPT_FLAG_READONLY) ? 1 : 0;
+}
+
+/**
+ * partition_rename() - rename the selected partition name
+ * @part_num: the selected partition
+ * @name: the new name
+ *
+ * Return: 0 for success, or -ve on error
+ */
+static int partition_rename(int part_num, char *name)
+{
+	int x;
+	int ret;
+
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return -EINVAL;
+
+	if (strnlen(name, sizeof(P->spt.partition[0].name)) >=
+	    sizeof(P->spt.partition[0].name)) {
+		rsu_log(RSU_ERR,
+			"Partition name is too long - limited to %li",
+			sizeof(P->spt.partition[0].name) - 1);
+		return -EINVAL;
+	}
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		if (strncmp(P->spt.partition[x].name, name,
+			    sizeof(P->spt.partition[0].name) - 1) == 0) {
+			rsu_log(RSU_ERR,
+				"Partition rename already in use\n");
+			return -EEXIST;
+		}
+	}
+
+	rsu_misc_safe_strcpy(P->spt.partition[part_num].name,
+			     sizeof(P->spt.partition[0].name),
+			     name, sizeof(P->spt.partition[0].name));
+
+	ret = writeback_spt();
+	if (ret)
+		return ret;
+
+	return load_spt();
+}
+
+/**
+ * partition_delete() - delete a partition
+ * @part_num: the selected partition
+ *
+ * Return: 0 for success, or -ve on error
+ */
+static int partition_delete(int part_num)
+{
+	int x;
+	int ret;
+
+	if (part_num < 0 || part_num >= P->spt.partitions) {
+		rsu_log(RSU_ERR, "Invalid partition number\n");
+		return -EINVAL;
+	}
+
+	for (x = part_num; x < P->spt.partitions - 1; x++)
+		P->spt.partition[x] = P->spt.partition[x + 1];
+
+	P->spt.partitions--;
+
+	ret = writeback_spt();
+	if (ret)
+		return ret;
+
+	return load_spt();
+}
+
+/**
+ * partition_create() - create a partition
+ * @name: partition name
+ * @start: partition start address
+ * @size: partition size
+ *
+ * Return: 0 for success, or -ve on error
+ */
+static int partition_create(char *name, u64 start, unsigned int size)
+{
+	int x;
+	int ret;
+	u64 end;
+
+	/* Reject overflow before computing end; a wrapped end defeats overlap checks. */
+	if ((u64)size > U64_MAX - start) {
+		rsu_log(RSU_ERR, "Partition end overflows u64\n");
+		return -EINVAL;
+	}
+	end = start + size;
+
+	if (size % MIN_QSPI_ERASE_SIZE) {
+		rsu_log(RSU_ERR, "Invalid partition size\n");
+		return -EINVAL;
+	}
+
+	if (start % MIN_QSPI_ERASE_SIZE) {
+		rsu_log(RSU_ERR, "Invalid partition address\n");
+		return -EINVAL;
+	}
+
+	if (strnlen(name, sizeof(P->spt.partition[0].name)) >=
+	    sizeof(P->spt.partition[0].name)) {
+		rsu_log(RSU_ERR, "Partition name is too long - limited to %i\n",
+			sizeof(P->spt.partition[0].name) - 1);
+		return -EINVAL;
+	}
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		if (strncmp(P->spt.partition[x].name, name,
+			    sizeof(P->spt.partition[0].name) - 1) == 0) {
+			rsu_log(RSU_ERR, "Partition name already in use\n");
+			return -EEXIST;
+		}
+	}
+
+	if (P->spt.partitions == SPT_MAX_PARTITIONS) {
+		rsu_log(RSU_ERR, "Partition table is full\n");
+		return -ENOSPC;
+	}
+
+	for (x = 0; x < P->spt.partitions; x++) {
+		u64 pstart = P->spt.partition[x].offset;
+		u64 plen = P->spt.partition[x].length;
+		u64 pend;
+
+		/* Skip a corrupt entry rather than wrap; check_spt() validates on load. */
+		if (plen > U64_MAX - pstart)
+			continue;
+		pend = pstart + plen;
+
+		if (start < pend && end > pstart) {
+			rsu_log(RSU_ERR, "Partition overlap\n");
+			return -EEXIST;
+		}
+	}
+
+	rsu_misc_safe_strcpy(P->spt.partition[P->spt.partitions].name,
+			     sizeof(P->spt.partition[0].name), name,
+			     sizeof(P->spt.partition[0].name));
+	P->spt.partition[P->spt.partitions].offset = start;
+	P->spt.partition[P->spt.partitions].length = size;
+	P->spt.partition[P->spt.partitions].flags = 0;
+
+	P->spt.partitions++;
+
+	ret = writeback_spt();
+	if (ret)
+		return ret;
+
+	return load_spt();
+}
+
+/* Reject corrupt CPB header before indexing cpb_slots[]. */
+static int cpb_ptr_slots_access_ok(void)
+{
+	if (!P->cpb_slots)
+		return -EUCLEAN;
+	return cpb_header_access_ok();
+}
+
+/**
+ * priority_get() - get the selected partition's priority
+ * @part_num: the selected partition number
+ *
+ * Return: 0 for success, or -ve on error
+ */
+static int priority_get(int part_num)
+{
+	int x;
+	int priority = 0;
+	int ret;
+
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return -EINVAL;
+	ret = cpb_ptr_slots_access_ok();
+	if (ret)
+		return ret;
+
+	for (x = P->cpb.header.image_ptr_slots; x > 0; x--) {
+		if (P->cpb_slots[x - 1] != ERASED_ENTRY &&
+		    P->cpb_slots[x - 1] != SPENT_ENTRY) {
+			priority++;
+			if (P->cpb_slots[x - 1] ==
+			    P->spt.partition[part_num].offset)
+				return priority;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * priority_add() - enable the selected partition's priority
+ * @part_num: the selected partition number
+ *
+ * Return: 0 for success, or -ve on error
+ */
+static int priority_add(int part_num)
+{
+	int x;
+	int y;
+	int ret;
+
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return -EINVAL;
+	ret = cpb_ptr_slots_access_ok();
+	if (ret)
+		return ret;
+
+	for (x = 0; x < P->cpb.header.image_ptr_slots; x++) {
+		if (P->cpb_slots[x] == ERASED_ENTRY) {
+			if (update_cpb(x,
+				       P->spt.partition[part_num].offset)) {
+				/*
+				 * update_cpb() may have written one CPB but
+				 * not the other; force a reload so load_cpb()
+				 * resyncs the two flash copies before we
+				 * return failure.
+				 */
+				load_cpb();
+				return -EIO;
+			}
+			return load_cpb();
+		}
+	}
+
+	rsu_log(RSU_DEBUG, "Compressing CPB\n");
+
+	for (x = 0, y = 0; x < P->cpb.header.image_ptr_slots; x++) {
+		if (P->cpb_slots[x] != ERASED_ENTRY &&
+		    P->cpb_slots[x] != SPENT_ENTRY) {
+			P->cpb_slots[y++] = P->cpb_slots[x];
+		}
+	}
+
+	if (y < P->cpb.header.image_ptr_slots)
+		P->cpb_slots[y++] = P->spt.partition[part_num].offset;
+	else
+		return -ENOSPC;
+
+	while (y < P->cpb.header.image_ptr_slots)
+		P->cpb_slots[y++] = ERASED_ENTRY;
+
+	ret = writeback_cpb();
+	if (ret)
+		return ret;
+
+	return load_cpb();
+}
+
+/**
+ * priority_remove() - remove the selected partition's priority
+ * @part_num: the selected partition number
+ *
+ * Return: 0 for success, or -ve on error
+ */
+static int priority_remove(int part_num)
+{
+	int x;
+	int ret;
+
+	if (part_num < 0 || part_num >= P->spt.partitions)
+		return -EINVAL;
+	ret = cpb_ptr_slots_access_ok();
+	if (ret)
+		return ret;
+
+	for (x = 0; x < P->cpb.header.image_ptr_slots; x++) {
+		if (P->cpb_slots[x] == P->spt.partition[part_num].offset)
+			if (update_cpb(x, SPENT_ENTRY)) {
+				/* See priority_add(): same recovery contract. */
+				load_cpb();
+				return -EIO;
+			}
+	}
+
+	return load_cpb();
+}
+
+/**
+ * data_read() - read data from flash
+ * @part_num: partition number
+ * @offset: offset which data will be read from
+ * @bytes: data size in byte which will be read
+ * @buf: pointer to buffer contains to be read data
+ *
+ * Return: 0 for success, or error code
+ */
+static int data_read(int part_num, int offset, int bytes, void *buf)
+{
+	return read_part(part_num, offset, buf, bytes);
+}
+
+/**
+ * data_write() - write data to flash
+ * @part_num: partition number
+ * @part_num: offset which data will be written to
+ * @bytes: data size in bytes which will be written
+ * @buf: pointer to buffer contains to be written data
+ *
+ * Return: 0 for success, or error code
+ */
+static int data_write(int part_num, int offset, int bytes, void *buf)
+{
+	return write_part(part_num, offset, buf, bytes);
+}
+
+/**
+ * data_erase() - erase flash data
+ * @part_num: partition number
+ *
+ * Return: 0 for success, or error code
+ */
+static int data_erase(int part_num)
+{
+	return erase_part(part_num);
+}
+
+/**
+ * image_load() - load production or factory image
+ * @offset: the image offset
+ *
+ * Return: 0 for success, or error code
+ */
+static int image_load(u64 offset)
+{
+	u32 flash_offset[2];
+
+	flash_offset[0] = lower_32_bits(offset);
+	flash_offset[1] = upper_32_bits(offset);
+
+	rsu_log(RSU_DEBUG, "RSU_DEBUG: RSU updated to 0x%08x%08x\n",
+		flash_offset[1], flash_offset[0]);
+
+	if (mbox_rsu_update(flash_offset))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * status_log() - get firmware status info
+ * @info: pointer to rsu_status_info
+ *
+ * Return: 0 for success, or error code
+ */
+static int status_log(struct rsu_status_info *info)
+{
+	if (mbox_rsu_status((u32 *)info,
+			    sizeof(struct rsu_status_info) / 4)) {
+		rsu_log(RSU_ERR,
+			"RSU: Firmware or flash content not supporting RSU\n");
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+/**
+ * notify_fw() - call the firmware notify command
+ * @value: the notification value
+ *
+ * Return: 0 for success, or error code
+ */
+static int notify_fw(u32 value)
+{
+	rsu_log(RSU_DEBUG, "RSU_DEBUG: notified with 0x%08x.\n", value);
+
+	if (mbox_hps_stage_notify(value))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * dcmf_version() - retrieve the decision firmware version
+ * @versions: pointer to where the four DCMF versions will be stored
+ *
+ * This function is used to retrieve the version of each of the four DCMF copies
+ * in flash.
+ *
+ * Returns: 0 on success, or error code
+ */
+static int dcmf_version(__u32 *versions)
+{
+	int ret;
+
+	if (!versions)
+		return -EINVAL;
+
+	/* get the first flash since DCMF always located at first flash */
+	P->flash = P->flashlist[0];
+
+	ret = rsu_mtd_read(P->flash, DCMF0_VERSION_OFFSET, 4, &versions[0]);
+	if (ret) {
+		rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+		return ret;
+	}
+
+	ret = rsu_mtd_read(P->flash, DCMF1_VERSION_OFFSET, 4, &versions[1]);
+	if (ret) {
+		rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+		return ret;
+	}
+
+	ret = rsu_mtd_read(P->flash, DCMF2_VERSION_OFFSET, 4, &versions[2]);
+	if (ret) {
+		rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+		return ret;
+	}
+
+	ret = rsu_mtd_read(P->flash, DCMF3_VERSION_OFFSET, 4, &versions[3]);
+	if (ret) {
+		rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * dcmf_status() - determine if decision dcmfs are corrupted
+ * @status: pointer to where the status will be stored
+ *
+ * This function is used to determine whether decision firmware copies are
+ * corrupted in flash, with the currently used decision firmware being used as
+ * reference. The status is an array of 4 values, one for each decision
+ * firmware copy. A 0 means the copy is fine, anything else means the copy is
+ * corrupted.
+ *
+ * Returns: 0 on success, or error code
+ */
+static int dcmf_status(u16 *status)
+{
+	int ret;
+	struct rsu_status_info rsu_status;
+	char *buffa = NULL;
+	char *buffb = NULL;
+	int crt_dcmf;
+	int idx;
+
+	/* get the first flash since DCMF always located at first flash */
+	P->flash = P->flashlist[0];
+
+	ret = status_log(&rsu_status);
+	if (ret) {
+		rsu_log(RSU_ERR, "status_log error");
+		return ret;
+	}
+	crt_dcmf = RSU_VERSION_CRT_DCMF_IDX(rsu_status.version);
+
+	buffa = (char *)malloc(DCMF_SIZE);
+	if (!buffa) {
+		rsu_log(RSU_ERR, "malloc error");
+		return -ENOMEM;
+	}
+
+	buffb = (char *)malloc(DCMF_SIZE);
+	if (!buffb) {
+		rsu_log(RSU_ERR, "malloc error");
+		ret = -ENOMEM;
+		goto ret_val;
+	}
+
+	ret = rsu_mtd_read(P->flash, crt_dcmf * DCMF_SIZE, DCMF_SIZE, buffa);
+	if (ret) {
+		rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+		goto ret_val;
+	}
+
+	for (idx = 0; idx < 4; idx++) {
+		int i;
+
+		status[idx] = 0;
+
+		if (idx == crt_dcmf)
+			continue;
+
+		ret = rsu_mtd_read(P->flash, idx * DCMF_SIZE, DCMF_SIZE, buffb);
+		if (ret) {
+			rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+			goto ret_val;
+		}
+
+		for (i = 0; i < DCMF_SIZE; i++)
+			if (buffa[i] != buffb[i]) {
+				status[idx] = 1;
+				break;
+			}
+	}
+
+ret_val:
+	if (buffa)
+		free(buffa);
+	if (buffb)
+		free(buffb);
+	return ret;
+}
+
+/**
+ * max_retry() - retrieve the max_retry parameter
+ * @value: pointer to where the max_retry will be stored
+ *
+ * This function is used to retrieve the max_retry parameter from the decision
+ * firmware data section.
+ *
+ * Returns: 0 on success, or error code
+ */
+static int max_retry(__u8 *value)
+{
+	int ret;
+	__u8 tmp;
+
+	if (!value)
+		return -EINVAL;
+
+	/* get the first flash since DCMF always located at first flash */
+	P->flash = P->flashlist[0];
+
+	ret = rsu_mtd_read(P->flash, DCIO_MAX_RETRY_OFFSET, 1, &tmp);
+	if (ret) {
+		rsu_log(RSU_ERR, "read flash error=%i\n", ret);
+		return ret;
+	}
+
+	/* Add one, to make value consistent with Quartus view */
+	*value = tmp + 1;
+
+	return ret;
+}
+
+/* Forward decl so qspi_ll_intf can reference .exit before the body. */
+static void ll_exit(void);
+
+static struct rsu_ll_intf qspi_ll_intf = {
+	.exit = ll_exit,
+	.priv = NULL,
+
+	.partition.count = partition_count,
+	.partition.name = partition_name,
+	.partition.offset = partition_offset,
+	.partition.factory_offset = factory_offset,
+	.partition.size = partition_size,
+	.partition.reserved = partition_reserved,
+	.partition.readonly = partition_readonly,
+	.partition.rename = partition_rename,
+	.partition.delete = partition_delete,
+	.partition.create = partition_create,
+
+	.priority.get = priority_get,
+	.priority.add = priority_add,
+	.priority.remove = priority_remove,
+
+	.data.read = data_read,
+	.data.write = data_write,
+	.data.erase = data_erase,
+
+	.fw_ops.load = image_load,
+	.fw_ops.status = status_log,
+	.fw_ops.notify = notify_fw,
+	.fw_ops.dcmf_version = dcmf_version,
+	.fw_ops.dcmf_status = dcmf_status,
+	.fw_ops.max_retry = max_retry,
+
+	.spt_ops.restore = restore_spt_from_address,
+	.spt_ops.save = save_spt_to_address,
+	.spt_ops.corrupted = corrupted_spt,
+
+	.cpb_ops.empty = empty_cpb,
+	.cpb_ops.restore = restore_cpb_from_address,
+	.cpb_ops.save = save_cpb_to_address,
+	.cpb_ops.corrupted = corrupted_cpb
+};
+
+static void ll_exit(void)
+{
+	struct rsu_qspi_priv *ctx = qspi_ctx;
+
+	if (!ctx)
+		return;
+
+	ctx->cpb0_part = -1;
+	ctx->cpb1_part = -1;
+	ctx->cpb_corrupted = false;
+	ctx->cpb_fixed = false;
+	ctx->spt_corrupted = false;
+
+	for (int i = 0; i < QSPI_MAX_DEVICE; i++) {
+		if (ctx->flashlist && ctx->flashlist[i]) {
+			rsu_mtd_unclaim(ctx->flashlist[i]);
+			ctx->flashlist[i] = NULL;
+		}
+	}
+
+	free(ctx->flashlist);
+	ctx->flashlist = NULL;
+	ctx->flash = NULL;
+	ctx->cpb_slots = NULL;
+
+	memset(ctx, 0, sizeof(*ctx));
+	free(ctx);
+	qspi_ctx = NULL;
+
+	/* Clear so a stale .priv cannot dereference freed memory. */
+	qspi_ll_intf.priv = NULL;
+}
+
+#if CONFIG_IS_ENABLED(SOCFPGA_RSU_MULTIFLASH)
+int get_num_flash(u32 *flash_enabled)
+{
+	int flash_count = 0;
+	struct flash_info mbox_flash_info[QSPI_MAX_DEVICE];
+
+	/* retrieve qspi info from mailbox */
+	if (mbox_qspi_get_device_info((u32 *)mbox_flash_info, 8)) {
+		rsu_log(RSU_ERR,
+			"%s: RSU:Firmware or flash content not supporting RSU\n", __func__);
+		return -EOPNOTSUPP;
+	}
+
+	for (int i = 0; i < QSPI_MAX_DEVICE; i++) {
+		debug("QSPI Device INFO\nflash_info[%d]: 0x%08x\nlen: 0x%08x\n",
+		      i, mbox_flash_info[i].size, QSPI_GET_DEVICE_INFO_RESP_LEN);
+	}
+
+	for (int i = 0; i < QSPI_MAX_DEVICE; i++) {
+		flash_enabled[i] = 0;
+
+		/* Calculate the size return by mailbox; */
+		if (mbox_flash_info[i].size > SZ_2G) {
+			/* >2Gb, power the bit 0-30 */
+			debug("RSU: QSPI %d larger than 2Gbits capacity.", i);
+			flash_enabled[i] = pow(2, mbox_flash_info[i].size & GENMASK(30, 0));
+			flash_enabled[i] = flash_enabled[i] / SZ_8;
+		} else {
+			/* <= 2Gb, total the bit 0-30 */
+			debug("RSU: QSPI %d lower than 2Gbits capacity.", i);
+			flash_enabled[i] = mbox_flash_info[i].size & GENMASK(30, 0);
+			flash_enabled[i] = flash_enabled[i] / SZ_8;
+		}
+
+		debug("Calculated flash size[%d]: %d\n", i, flash_enabled[i]);
+		if (flash_enabled[i] > 0)
+			flash_count++;
+	}
+
+	debug("RSU: Total flash #: %d\n", flash_count);
+
+	return flash_count;
+}
+#endif
+
+int rsu_ll_qspi_init(struct rsu_ll_intf **intf)
+{
+	u32 spt_offset[SPT_OFFSET_MBOX];
+
+	qspi_ctx = malloc(sizeof(*qspi_ctx));
+	if (!qspi_ctx)
+		return -ENOMEM;
+	memset(qspi_ctx, 0, sizeof(*qspi_ctx));
+	qspi_ctx->cpb0_part = -1;
+	qspi_ctx->cpb1_part = -1;
+	qspi_ctx->num_flash = -1;
+	/* Set for future per-session use; current backend reads qspi_ctx directly. */
+	qspi_ll_intf.priv = qspi_ctx;
+
+	/* get the offset from firmware */
+	if (mbox_rsu_get_spt_offset(spt_offset, 4)) {
+		rsu_log(RSU_ERR,
+			"RSU: Firmware or flash content not supporting RSU\n");
+		ll_exit();
+		return -ECOMM;
+	}
+
+#if CONFIG_IS_ENABLED(SOCFPGA_RSU_MULTIFLASH)
+	/* Zero-init so a partial fill cannot leak into the probe loop. */
+	u32 flash_enabled[QSPI_MAX_DEVICE] = {0};
+
+	/* retrieve qspi info from mailbox */
+	P->num_flash = get_num_flash(flash_enabled);
+	printf("%s: MULTIFLASH_ENABLED: num_flash #%d\n", __func__, P->num_flash);
+	if (P->num_flash < 0) {
+		rsu_log(RSU_ERR, "get_num_flash failed, err=%d\n",
+			P->num_flash);
+		ll_exit();
+		return P->num_flash;
+	}
+#else
+	P->num_flash = 1;
+	printf("%s: MULTIFLASH_DISABLED: num_flash #%d\n", __func__, P->num_flash);
+#endif
+
+	P->flashlist = calloc(QSPI_MAX_DEVICE, sizeof(*P->flashlist));
+	if (!P->flashlist) {
+		rsu_log(RSU_ERR,
+			"RSU: Failed to allocate memory for flash list. Exiting.\n");
+		ll_exit();
+		return -ENOMEM;
+	}
+
+#if CONFIG_IS_ENABLED(SOCFPGA_RSU_MULTIFLASH)
+	{
+		int found = 0;
+
+		/*
+		 * Firmware may enable CSes sparsely; compact the probed
+		 * handles into a contiguous prefix because consumers iterate
+		 * 0..num_flash-1 unconditionally.
+		 */
+		for (int i = 0; i < QSPI_MAX_DEVICE; i++) {
+			debug("%s: probe flash #%d\n", __func__, i);
+			if (flash_enabled[i] > 0) {
+				int err;
+
+				err = rsu_mtd_probe(CONFIG_SF_DEFAULT_BUS, i,
+						    &P->flash);
+				if (err) {
+					rsu_log(RSU_ERR, "SPI probe failed.\n");
+					ll_exit();
+					return err;
+				}
+
+				P->flashlist[found++] = P->flash;
+			}
+		}
+
+		if (!found) {
+			rsu_log(RSU_ERR,
+				"RSU: no QSPI flash enabled by firmware\n");
+			ll_exit();
+			return -ENODEV;
+		}
+
+		/* Resync num_flash with the compacted flashlist[]. */
+		P->num_flash = found;
+	}
+#else
+	{
+		int err;
+
+		err = rsu_mtd_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SOCFPGA_RSU_SF_CS,
+				    &P->flash);
+		if (err) {
+			ll_exit();
+			rsu_log(RSU_ERR, "SPI probe failed.\n");
+			return err;
+		}
+
+		P->flashlist[0] = P->flash;
+	}
+#endif
+	P->spt0_offset = spt_offset[1];
+	P->spt1_offset = spt_offset[3];
+	rsu_log(RSU_DEBUG, "SPT0 offset 0x%08x\n", P->spt0_offset);
+	rsu_log(RSU_DEBUG, "SPT1 offset 0x%08x\n", P->spt1_offset);
+
+	if (load_spt() && !P->spt_corrupted) {
+		ll_exit();
+		rsu_log(RSU_ERR, "Bad SPT\n");
+		return -EUCLEAN;
+	}
+
+	if (P->spt_corrupted) {
+		P->cpb_corrupted = true;
+	} else if (load_cpb() && !P->cpb_corrupted) {
+		ll_exit();
+		rsu_log(RSU_ERR, "Bad CPB\n");
+		return -EUCLEAN;
+	}
+
+	*intf = &qspi_ll_intf;
+
+	return 0;
+}
diff --git a/arch/arm/mach-socfpga/rsu_misc.c b/arch/arm/mach-socfpga/rsu_misc.c
new file mode 100644
index 00000000000..50a744d162e
--- /dev/null
+++ b/arch/arm/mach-socfpga/rsu_misc.c
@@ -0,0 +1,854 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Intel Corporation
+ * Copyright (C) 2026 Altera Corporation <www.altera.com>
+ */
+
+#include <linux/compat.h>
+#include <linux/compiler.h>
+#include <linux/errno.h>
+#include <asm/arch/rsu.h>
+#include <asm/arch/rsu_ll.h>
+#include <asm/arch/rsu_misc.h>
+#include <asm/types.h>
+#include <u-boot/zlib.h>
+#include <errno.h>
+#include <exports.h>
+
+#define LOG_BUF_SIZE	1024
+
+static char *cb_buffer;
+static int cb_buffer_togo;
+
+static char *reserved_names[] = {
+	"BOOT_INFO",
+	"FACTORY_IMAGE",
+	"SPT",
+	"SPT0",
+	"SPT1",
+	"CPB",
+	"CPB0",
+	"CPB1",
+	""
+};
+
+struct pointer_block {
+	u32 num_ptrs;
+	u32 RSVD0;
+	u64 ptrs[4];
+	u8 RSVD1[0xd4];
+	u32 crc;
+};
+
+/**
+ * enum rsu_block_type - enumeration for image block types
+ * @SECTION_BLOCK: section block
+ * @SIGNATURE_BLOCK: signature block
+ * @REGULAR_BLOCK: all other block types
+ */
+enum rsu_block_type {
+	SECTION_BLOCK = 0,
+	SIGNATURE_BLOCK,
+	REGULAR_BLOCK
+};
+
+/* maximum number of sections supported for an image */
+#define MAX_SECTIONS 64
+
+/**
+ * struct rsu_image_state - structure for stated of image processing
+ * @offset: current block offset in bytes
+ * @block_type: current block type
+ * @sections: identified section offsets
+ * @no_sections: number of identified sections
+ * @absolute: current image is an absolute image
+ *
+ * This structure is used to maintain the state of image parsing, both for
+ * relocating images to final destination in flash, and also for verifying
+ * images already stored in flash.
+ */
+struct rsu_image_state {
+	int offset;
+	enum rsu_block_type block_type;
+	u64 sections[MAX_SECTIONS];
+	int no_sections;
+	int absolute;
+};
+
+/**
+ * find_section() - search section in the current list of identified sections
+ * @state: current state machine state
+ * @section: section to be searched
+ *
+ * Return: 1 if section is found, 0 if section is not found
+ */
+static int find_section(struct rsu_image_state *state, u64 section)
+{
+	int x;
+
+	for (x = 0; x < state->no_sections; x++)
+		if (section == state->sections[x])
+			return 1;
+
+	return 0;
+}
+
+/**
+ * add_section() - add section to the current list of identified sections
+ * @state: current state machine state
+ * @section: section to be added
+ *
+ * Return: 0 on success, or -ve on error
+ */
+static int add_section(struct rsu_image_state *state, u64 section)
+{
+	if (find_section(state, section))
+		return 0;
+
+	if (state->no_sections >= MAX_SECTIONS)
+		return -ENOSPC;
+
+	state->sections[state->no_sections++] = section;
+
+	return 0;
+}
+
+/**
+ * swap_bits() - swap bits
+ * @data: pointer point to data
+ * @len: data length
+ */
+void swap_bits(char *data, int len)
+{
+	int x, y;
+	char tmp;
+
+	for (x = 0; x < len; x++) {
+		tmp = 0;
+		for (y = 0; y < 8; y++) {
+			tmp <<= 1;
+			if (data[x] & 1)
+				tmp |= 1;
+			data[x] >>= 1;
+		}
+		data[x] = tmp;
+	}
+}
+
+int pow(u32 x, u32 y)
+{
+	if (y == 0)
+		return 1;
+	else if ((y % 2) == 0)
+		return pow(x, y / 2) * pow(x, y / 2);
+	else
+		return x * pow(x, y / 2) * pow(x, y / 2);
+}
+
+/**
+ * rsu_misc_is_rsvd_name() - check if a reserved name
+ *
+ * @name: name to check
+ *
+ * Returns 1 if a reserved name, or 0 for not
+ */
+int rsu_misc_is_rsvd_name(char *name)
+{
+	int x;
+
+	for (x = 0; reserved_names[x][0] != '\0'; x++)
+		if (strcmp(name, reserved_names[x]) == 0)
+			return 1;
+
+	return 0;
+}
+
+/**
+ * rsu_misc_is_slot() - check if a read only or reserved partition
+ * @ll_intf: pointer to ll_intf
+ * @part_num: partition number
+ *
+ * Return 1 if not read only or reserved, or 0 for is
+ */
+int rsu_misc_is_slot(struct rsu_ll_intf *ll_intf, int part_num)
+{
+	if (ll_intf->partition.readonly(part_num) ||
+	    ll_intf->partition.reserved(part_num))
+		return 0;
+
+	if (rsu_misc_is_rsvd_name(ll_intf->partition.name(part_num)))
+		return 0;
+
+	return 1;
+}
+
+/**
+ * rsu_misc_slot2part() - get partition number from the slot
+ * @ll_intf: pointer to ll_intf
+ * @slot: slot number
+ *
+ * Return: partition number on success, or -ve on error
+ */
+int rsu_misc_slot2part(struct rsu_ll_intf *ll_intf, int slot)
+{
+	int partitions;
+	int cnt = 0;
+
+	partitions = ll_intf->partition.count();
+
+	for (int x = 0; x < partitions; x++) {
+		if (rsu_misc_is_slot(ll_intf, x)) {
+			if (slot == cnt)
+				return x;
+			cnt++;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * rsu_misc_writeprotected() - check if a slot is protected
+ * @slot: the number of slot to be checked
+ *
+ * Return 1 if a slot is protected, 0 for not
+ */
+int rsu_misc_writeprotected(int slot)
+{
+	char *protected;
+	int protected_slot_numb;
+
+	/* protect works only for slot 0-31 */
+	if (slot > 31)
+		return 0;
+
+	protected = env_get("rsu_protected_slot");
+	if (!protected)
+		return 0;
+
+	protected_slot_numb = (int)simple_strtol(protected, NULL, 0);
+	if (protected_slot_numb < 0 || protected_slot_numb > 31) {
+		rsu_log(RSU_WARNING,
+			"protected slot works only on the first 32 slots\n");
+		return 0;
+	}
+
+	if (protected_slot_numb == slot)
+		return 1;
+	else
+		return 0;
+}
+
+/**
+ * rsu_misc_spt_checksum_enabled() - check if the SPT checksum is enabled
+ *
+ * Return 1 if SPT checksum mechanism is enabled, 0 for disabled
+ */
+int rsu_misc_spt_checksum_enabled(void)
+{
+	char *c_enabled;
+	int checksum_enabled;
+
+	c_enabled = env_get("rsu_spt_checksum");
+	if (!c_enabled)
+		return 0;
+
+	checksum_enabled = (int)simple_strtol(c_enabled, NULL, 0);
+	if (checksum_enabled)
+		return 1;
+
+	return 0;
+}
+
+/**
+ * rsu_misc_safe_strcpy() - buffer copy
+ * @dst: pointer to dst
+ * @dsz: dst buffer size
+ * @src: pointer to src
+ * @ssz: src buffer size
+ */
+void rsu_misc_safe_strcpy(char *dst, int dsz, char *src, int ssz)
+{
+	int len;
+
+	if (!dst || dsz <= 0)
+		return;
+
+	if (!src || ssz <= 0) {
+		dst[0] = '\0';
+		return;
+	}
+
+	len = strnlen(src, ssz);
+	if (len >= dsz)
+		len = dsz - 1;
+
+	memcpy(dst, src, len);
+	dst[len] = '\0';
+}
+
+/**
+ * rsu_cb_buf_init() - initialize buffer parameters
+ * @buf: pointer to buf
+ * @size: size of buffer
+ *
+ * Return: 0 on success, or -ve on error
+ */
+int rsu_cb_buf_init(void *buf, int size)
+{
+	if (!buf || size <= 0)
+		return -EINVAL;
+
+	cb_buffer = (char *)buf;
+	cb_buffer_togo = size;
+
+	return 0;
+}
+
+/**
+ * rsu_cb_buf_exit() - reset buffer parameters
+ */
+void rsu_cb_buf_exit(void)
+{
+	cb_buffer = NULL;
+	cb_buffer_togo = -1;
+}
+
+/**
+ * rsu_cb_buf() - copy data to buffer
+ * @buf: pointer to data buffer
+ * @len: size of data buffer
+ *
+ * Return the buffer data size
+ */
+int rsu_cb_buf(void *buf, int len)
+{
+	int read_len;
+
+	if (!cb_buffer_togo)
+		return 0;
+
+	if (!cb_buffer || cb_buffer_togo < 0 || !buf || len < 0)
+		return -EINVAL;
+
+	if (cb_buffer_togo < len)
+		read_len = cb_buffer_togo;
+	else
+		read_len = len;
+
+	memcpy(buf, cb_buffer, read_len);
+
+	cb_buffer += read_len;
+	cb_buffer_togo -= read_len;
+
+	if (!cb_buffer_togo)
+		cb_buffer = NULL;
+
+	return read_len;
+}
+
+/**
+ * sig_block_process() - process signature block
+ * @state: current state machine state
+ * @block: signature block
+ * @info: slot where the data will be written
+ *
+ * Determine if the signature block is part of an absolute image, and add its
+ * section pointers to the list of identified sections.
+ *
+ * Return: zero value for success, or negative value on error
+ */
+static int sig_block_process(struct rsu_image_state *state,	void *block,
+			     struct rsu_slot_info *info)
+{
+	char *data = (char *)block;
+	struct pointer_block *ptr_blk = (struct pointer_block *)(data
+					+ SIG_BLOCK_PTR_OFFS);
+	int x;
+
+	/* Determine if absolute image - only done for 2nd block in an image
+	 * which is always a signature block
+	 */
+	if (state->offset == IMAGE_BLOCK_SZ)
+		for (x = 0; x < 4; x++)
+			if (ptr_blk->ptrs[x] > (__u64)info->size) {
+				state->absolute = 1;
+				rsu_log(RSU_DEBUG, "Found absolute image\n");
+				break;
+			}
+
+	/* Add pointers to list of identified sections */
+	for (x = 0; x < 4; x++)
+		if (ptr_blk->ptrs[x]) {
+			if (state->absolute)
+				add_section(state, ptr_blk->ptrs[x] -
+					    info->offset);
+			else
+				add_section(state, ptr_blk->ptrs[x]);
+		}
+
+	return 0;
+}
+
+/**
+ * sig_block_adjust() - adjust signature block pointers before writing to flash
+ * @state: current state machine state
+ * @block: signature block
+ * @info: slot where the data will be written
+ *
+ * This function checks that the section pointers are consistent, and for non-
+ * absolute images it updates them to match the destination slot, also re-
+ * computing the CRC.
+ *
+ * Return: zero value for success, -1 on error
+ */
+static int sig_block_adjust(struct rsu_image_state *state, void *block,
+			    struct rsu_slot_info *info)
+{
+	u32 calc_crc;
+	int x;
+	char *data = (char *)block;
+	struct pointer_block *ptr_blk = (struct pointer_block *)(data
+					+ SIG_BLOCK_PTR_OFFS);
+
+	/*
+	 * Check CRC on 4kB block before proceeding.  All bytes must be
+	 * bit-swapped before they can used in zlib CRC32 library function.
+	 * The CRC value is stored in big endian in the bitstream.
+	 */
+	swap_bits(block, IMAGE_BLOCK_SZ);
+	calc_crc = crc32(0, (uchar *)block, SIG_BLOCK_CRC_OFFS);
+	if (be32_to_cpu(ptr_blk->crc) != calc_crc) {
+		rsu_log(RSU_ERR,
+			"Error: Bad CRC32. Calc = %08X / From Block = %08x\n",
+			calc_crc, be32_to_cpu(ptr_blk->crc));
+		return -EBADMSG;
+	}
+	swap_bits(block, IMAGE_BLOCK_SZ);
+
+	/* Check pointers */
+	for (x = 0; x < 4; x++) {
+		u64 ptr = ptr_blk->ptrs[x];
+
+		if (!ptr)
+			continue;
+
+		if (state->absolute)
+			ptr -= info->offset;
+
+		if (ptr > info->size) {
+			rsu_log(RSU_ERR,
+				"Error: A pointer not within the slot\n");
+			return -EINVAL;
+		}
+	}
+
+	/* Absolute images do not require pointer updates */
+	if (state->absolute)
+		return 0;
+
+	/* Update pointers */
+	for (x = 0; x < 4; x++) {
+		if (ptr_blk->ptrs[x]) {
+			u64 old =  ptr_blk->ptrs[x];
+
+			ptr_blk->ptrs[x] += info->offset;
+			rsu_log(RSU_DEBUG,
+				"Adjusting pointer 0x%llx -> 0x%llx\n",
+				old, ptr_blk->ptrs[x]);
+		}
+	}
+
+	/* Update CRC in block */
+	swap_bits(block, IMAGE_BLOCK_SZ);
+	calc_crc = crc32(0, (uchar *)block, SIG_BLOCK_CRC_OFFS);
+	ptr_blk->crc = be32_to_cpu(calc_crc);
+	swap_bits(block, IMAGE_BLOCK_SZ);
+
+	return 0;
+}
+
+/**
+ * block_compare() - compare two image blocks
+ * @state: current state machine state
+ * @block: input data provided by user
+ * @vblock: verification data read from flash
+ *
+ * Return: non-negative value for successful comparisor, or negative value on
+ * failure or comparison difference found.
+ */
+static int block_compare(struct rsu_image_state *state, void *block,
+			 void *vblock)
+{
+	char *buf = (char *)block;
+	char *vbuf = (char *)vblock;
+	int x;
+
+	for (x = 0; x < IMAGE_BLOCK_SZ; x++)
+		if (vbuf[x] != buf[x]) {
+			rsu_log(RSU_ERR, "Expect %02X, got %02X @0x%08X\n",
+				buf[x], vbuf[x], state->offset + x);
+			return -ECMP;
+		}
+
+	return 0;
+}
+
+/**
+ * sig_block_compare() - compare two signature blocks
+ * @state: current state machine state
+ * @ublock: input data provided by user
+ * @vblock: verification data read from flash
+ * @info: slot where the verification data was read from
+ *
+ * Absolute images are compared directly, while for non-absolute images the
+ * pointers and associated CRC are re-computed to see if they match.
+ *
+ * Return: zero for success, or negative value on erorr or finding differences.
+ */
+static int sig_block_compare(struct rsu_image_state *state, void *ublock,
+			     void *vblock, struct rsu_slot_info *info)
+{
+	u32 calc_crc;
+	int x;
+	char block[IMAGE_BLOCK_SZ];
+	struct pointer_block *ptr_blk = (struct pointer_block *)(block +
+		SIG_BLOCK_PTR_OFFS);
+
+	rsu_log(RSU_DEBUG, "Compare signature block @0x%08x\n", state->offset);
+
+	/* Make a copy of the data provided by the user */
+	memcpy(block, ublock, IMAGE_BLOCK_SZ);
+
+	/* Update signature block to match what we expect in flash */
+	if (!state->absolute) {
+		/* Update pointers */
+		for (x = 0; x < 4; x++)
+			if (ptr_blk->ptrs[x])
+				ptr_blk->ptrs[x] += info->offset;
+
+		/* Update CRC in block */
+		swap_bits(block, IMAGE_BLOCK_SZ);
+		calc_crc = crc32(0, (uchar *)block, SIG_BLOCK_CRC_OFFS);
+		ptr_blk->crc = be32_to_cpu(calc_crc);
+		swap_bits(block, IMAGE_BLOCK_SZ);
+	}
+
+	return block_compare(state, block, vblock);
+}
+
+/**
+ * rsu_misc_image_block_init() - initialize state machine for processing blocks
+ * @state: current state machine state
+ *
+ * Function is called before processing images either for writing to flash or
+ * for comparison with verification data.
+ *
+ * Returns 0 on success, or -1 on error
+ */
+static int rsu_misc_image_block_init(struct rsu_image_state *state)
+{
+	rsu_log(RSU_DEBUG, "Resetting image block state machine\n");
+
+	state->no_sections = 1;
+	add_section(state, 0);
+	state->block_type = REGULAR_BLOCK;
+	state->absolute = 0;
+	state->offset = -IMAGE_BLOCK_SZ;
+
+	return 0;
+}
+
+/**
+ * rsu_misc_image_block_process() - process image blocks
+ *
+ * @state: current state machine state
+ * @block: pointer to current 4KB image block
+ * @vblock: pointer to current 4KB image verification block
+ * @info: rsu_slot_info structure for target slot
+ *
+ * Image blocks are processed either for updating before writing to flash
+ * (when vblock==NULL) or for comparison with verification data
+ * (when vblock!=NULL)
+ *
+ * Returns: 0 on success, or -ve on error
+ */
+static int rsu_misc_image_block_process(struct rsu_image_state *state,
+					void *block, void *vblock,
+					struct rsu_slot_info *info)
+{
+	u32 magic;
+	int ret;
+
+	state->offset += IMAGE_BLOCK_SZ;
+
+	if (find_section(state, state->offset))
+		state->block_type = SECTION_BLOCK;
+
+	switch (state->block_type) {
+	case SECTION_BLOCK:
+		magic = *(__u32 *)block;
+		if (magic == CMF_MAGIC) {
+			rsu_log(RSU_DEBUG, "Found CMF sect @0x%08x\n",
+				state->offset);
+			state->block_type = SIGNATURE_BLOCK;
+		} else {
+			state->block_type = REGULAR_BLOCK;
+		}
+
+		if (vblock)
+			return block_compare(state, block, vblock);
+		break;
+
+	case SIGNATURE_BLOCK:
+		rsu_log(RSU_DEBUG, "Found signature block @0x%08x\n",
+			state->offset);
+
+		ret = sig_block_process(state, block, info);
+		if (ret)
+			return ret;
+
+		state->block_type = REGULAR_BLOCK;
+
+		if (vblock)
+			return sig_block_compare(state, block, vblock, info);
+
+		ret = sig_block_adjust(state, block, info);
+		if (ret)
+			return ret;
+
+		break;
+
+	case REGULAR_BLOCK:
+		break;
+	}
+
+	if (vblock)
+		return block_compare(state, block, vblock);
+
+	return 0;
+}
+
+/**
+ * rsu_cb_program_common - callback to program flash
+ * @ll_intf: pointer to ll_intf
+ * @slot: slot number
+ * @callback: callback function pointer
+ * @rawdata: flag (raw data or not)
+ *
+ * Return 0 if success, or error code
+ */
+int rsu_cb_program_common(struct rsu_ll_intf *ll_intf, int slot,
+			  rsu_data_callback callback, int rawdata)
+{
+	int part_num;
+	int offset;
+	unsigned char buf[IMAGE_BLOCK_SZ];
+	unsigned char vbuf[IMAGE_BLOCK_SZ];
+	int cnt, c, done;
+	int x;
+	struct rsu_slot_info info;
+	struct rsu_image_state state;
+
+	if (!ll_intf)
+		return -EINTF;
+
+	if (slot < 0)
+		return -ESLOTNUM;
+
+	if (rsu_misc_writeprotected(slot)) {
+		rsu_log(RSU_ERR,
+			"Trying to program a write protected slot\n");
+		return -EWRPROT;
+	}
+
+	if (rsu_slot_get_info(slot, &info)) {
+		rsu_log(RSU_ERR, "Unable to read slot info\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(ll_intf, slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	if (ll_intf->priority.get(part_num) > 0) {
+		rsu_log(RSU_ERR,
+			"Trying to program a slot already in use\n");
+		return -EPROGRAM;
+	}
+
+	if (!callback)
+		return -EARGS;
+
+	offset = 0;
+	done = 0;
+
+	if (rsu_misc_image_block_init(&state))
+		return -EPROGRAM;
+
+	while (!done) {
+		cnt = 0;
+		while (cnt < IMAGE_BLOCK_SZ) {
+			c = callback(buf + cnt, IMAGE_BLOCK_SZ - cnt);
+			if (c == 0) {
+				done = 1;
+				break;
+			} else if (c < 0) {
+				return -ECALLBACK;
+			}
+			cnt += c;
+		}
+
+		if (cnt == 0)
+			break;
+
+		if (!rawdata)
+			if (rsu_misc_image_block_process(&state, buf, NULL,
+							 &info))
+				return -EPROGRAM;
+
+		if ((offset + cnt) > ll_intf->partition.size(part_num)) {
+			rsu_log(RSU_ERR,
+				"Trying to program too much data into slot\n");
+			return -ESIZE;
+		}
+
+		if (ll_intf->data.write(part_num, offset, cnt, buf))
+			return -ELOWLEVEL;
+
+		if (ll_intf->data.read(part_num, offset, cnt, vbuf))
+			return -ELOWLEVEL;
+
+		for (x = 0; x < cnt; x++)
+			if (vbuf[x] != buf[x]) {
+				rsu_log(RSU_DEBUG,
+					"Expect %02X, got %02X @ 0x%08X\n",
+					buf[x], vbuf[x], offset + x);
+				return -ECMP;
+			}
+
+		offset += cnt;
+	}
+
+	if (!rawdata && ll_intf->priority.add(part_num))
+		return -ELOWLEVEL;
+
+	return 0;
+}
+
+/**
+ * rsu_cb_verify_common() - callback for data verification
+ * @ll_intf: pointer to ll_intf
+ * @slot: slot number
+ * @callback: callback function pointer
+ * @rawdata: flag (raw data or not)
+ *
+ * Return 0 if success, or error code
+ */
+int rsu_cb_verify_common(struct rsu_ll_intf *ll_intf, int slot,
+			 rsu_data_callback callback, int rawdata)
+{
+	int part_num;
+	int offset;
+	unsigned char buf[IMAGE_BLOCK_SZ];
+	unsigned char vbuf[IMAGE_BLOCK_SZ];
+	int cnt, c, done;
+	int x;
+	struct rsu_slot_info info;
+	struct rsu_image_state state;
+
+	if (!ll_intf)
+		return -EINTF;
+
+	if (rsu_slot_get_info(slot, &info)) {
+		rsu_log(RSU_ERR, "Unable to read slot info\n");
+		return -ESLOTNUM;
+	}
+
+	part_num = rsu_misc_slot2part(ll_intf, slot);
+	if (part_num < 0)
+		return -ESLOTNUM;
+
+	if (!rawdata && ll_intf->priority.get(part_num) <= 0) {
+		rsu_log(RSU_ERR, "Trying to verify a slot not in use\n");
+		return -EERASE;
+	}
+
+	if (!callback)
+		return -EARGS;
+
+	offset = 0;
+	done = 0;
+
+	if (rsu_misc_image_block_init(&state))
+		return -ECMP;
+
+	while (!done) {
+		cnt = 0;
+		while (cnt < IMAGE_BLOCK_SZ) {
+			c = callback(buf + cnt, IMAGE_BLOCK_SZ - cnt);
+			if (c == 0) {
+				done = 1;
+				break;
+			} else if (c < 0) {
+				return -ECALLBACK;
+			}
+
+			cnt += c;
+		}
+
+		if (cnt == 0)
+			break;
+
+		if (ll_intf->data.read(part_num, offset, cnt, vbuf))
+			return -ELOWLEVEL;
+
+		if (!rawdata) {
+			if (rsu_misc_image_block_process(&state, buf, vbuf,
+							 &info))
+				return -ECMP;
+
+			offset += cnt;
+			continue;
+		}
+
+		for (x = 0; x < cnt; x++)
+			if (vbuf[x] != buf[x]) {
+				rsu_log(RSU_ERR,
+					"Expect %02X, got %02X @ 0x%08X\n",
+					buf[x], vbuf[x], offset + x);
+				return -ECMP;
+			}
+
+		offset += cnt;
+	}
+
+	return 0;
+}
+
+/*
+ * rsu_log() - display rsu log message
+ * @level: log level
+ * @format: log message format
+ */
+void rsu_log(const enum rsu_log_level level, const char *format, ...)
+{
+	va_list args;
+	char *log_level_env;
+	int log_level;
+	char printbuffer[LOG_BUF_SIZE];
+
+	log_level_env = env_get("rsu_log_level");
+
+	if (log_level_env) {
+		log_level = (int)simple_strtol(log_level_env, NULL, 0);
+
+		if (level >= log_level)
+			return;
+
+		va_start(args, format);
+		vscnprintf(printbuffer, sizeof(printbuffer), format, args);
+		va_end(args);
+		puts(printbuffer);
+	}
+}
diff --git a/arch/arm/mach-socfpga/rsu_s10.c b/arch/arm/mach-socfpga/rsu_s10.c
new file mode 100644
index 00000000000..fa3f2448031
--- /dev/null
+++ b/arch/arm/mach-socfpga/rsu_s10.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Copyright (C) 2018-2023 Intel Corporation
+ *
+ */
+
+#include <limits.h>
+#include <linux/compiler.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#ifdef CONFIG_DM_SPI_FLASH
+#include <dm/device.h>
+#endif
+#include <asm/arch/mailbox_s10.h>
+#include <asm/arch/rsu.h>
+#include <asm/arch/rsu_s10.h>
+#include <command.h>
+#include <rsu_console.h>
+#include <vsprintf.h>
+#include <spi.h>
+#include <spi_flash.h>
+#include <env.h>
+#include <fdt_support.h>
+#include <asm/arch/rsu_flash_if.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define RSU_S10_SPT_SLOT_MAX 127
+
+/* Linux DTB label identifying the RSU-managed boot partition. */
+#define RSU_BOOT_PARTITION_LABEL "Boot and fpga data"
+
+static unsigned int rsu_s10_spt_entry_count(const struct socfpga_rsu_s10_spt *spt)
+{
+	if (spt->magic_number != RSU_S10_SPT_MAGIC_NUMBER)
+		return 0;
+	if (spt->entries > RSU_S10_SPT_SLOT_MAX)
+		return RSU_S10_SPT_SLOT_MAX;
+	return spt->entries;
+}
+
+static int rsu_print_status(void)
+{
+	struct rsu_status_info status_info;
+
+	if (mbox_rsu_status((u32 *)&status_info, sizeof(status_info) / 4)) {
+		puts("RSU: Firmware or flash content not supporting RSU\n");
+		return -EOPNOTSUPP;
+	}
+	puts("RSU: Remote System Update Status\n");
+	printf("Current Image\t: 0x%08llx\n", status_info.current_image);
+	printf("Last Fail Image\t: 0x%08llx\n", status_info.fail_image);
+	printf("State\t\t: 0x%08x\n", status_info.state);
+	printf("Version\t\t: 0x%08x\n", status_info.version);
+	printf("Error location\t: 0x%08x\n", status_info.error_location);
+	printf("Error details\t: 0x%08x\n", status_info.error_details);
+	if (RSU_VERSION_ACMF_VERSION(status_info.version) &&
+	    RSU_VERSION_DCMF_VERSION(status_info.version))
+		printf("Retry counter\t: 0x%08x\n", status_info.retry_counter);
+
+	return 0;
+}
+
+static void rsu_print_spt_slot(const struct socfpga_rsu_s10_spt *spt,
+			       unsigned int nentries)
+{
+	unsigned int i;
+
+	puts("RSU: Sub-partition table content\n");
+	for (i = 0; i < nentries; i++) {
+		printf("%16.16s\tOffset: 0x%08x%08x\tLength: 0x%08x\tFlag : 0x%08x\n",
+		       spt->spt_slot[i].name,
+		       spt->spt_slot[i].offset[1],
+		       spt->spt_slot[i].offset[0],
+		       spt->spt_slot[i].length,
+		       spt->spt_slot[i].flag);
+	}
+}
+
+static void rsu_print_cpb_slot(const struct socfpga_rsu_s10_cpb *cpb)
+{
+	int i, j = 1;
+	unsigned int nslots = cpb->nslots;
+
+	if (nslots > ARRAY_SIZE(cpb->pointer_slot))
+		nslots = ARRAY_SIZE(cpb->pointer_slot);
+
+	puts("RSU: CMF pointer block's image pointer list\n");
+	if (!nslots)
+		return;
+	for (i = (int)nslots - 1; i >= 0; i--) {
+		if (cpb->pointer_slot[i] != ~0ULL &&
+		    cpb->pointer_slot[i] != 0) {
+			printf("Priority %d Offset: 0x%016llx nslot: %d\n",
+			       j, cpb->pointer_slot[i], i);
+			j++;
+		}
+	}
+}
+
+static u32 rsu_spt_slot_find_cpb(const struct socfpga_rsu_s10_spt *spt,
+				 unsigned int nentries)
+{
+	unsigned int i;
+
+	for (i = 0; i < nentries; i++) {
+		if (strstr(spt->spt_slot[i].name, "CPB0"))
+			return spt->spt_slot[i].offset[0];
+	}
+	puts("RSU: Cannot find CPB0 entry in sub-partition table\n");
+	return 0;
+}
+
+static void rsu_s10_sanitize_spt_names(struct socfpga_rsu_s10_spt *spt,
+				       unsigned int nentries)
+{
+	unsigned int i;
+
+	for (i = 0; i < nentries; i++)
+		spt->spt_slot[i].name[MAX_PART_NAME_LENGTH - 1] = '\0';
+}
+
+/**
+ * rsu_spt_cpb_list_inner() - read mailbox SPT offsets, flash SPT/CPB, print
+ * @spt0_out: if non-NULL, set after successful mailbox read (for rsu dtb)
+ * @spt1_out: if non-NULL, set after successful mailbox read
+ */
+static int rsu_spt_cpb_list_inner(int argc, char * const argv[],
+				  u32 *spt0_out, u32 *spt1_out)
+{
+	u32 spt_offset[4];
+	u32 cpb_offset;
+	u32 spt0_off, spt1_off;
+	int err;
+#ifdef CONFIG_DM_SPI_FLASH
+	struct udevice *flash;
+#else
+	struct spi_flash *flash;
+#endif
+	struct socfpga_rsu_s10_spt spt = { 0 };
+	struct socfpga_rsu_s10_cpb cpb = { 0 };
+	unsigned int nentries;
+
+	if (argc != 1)
+		return CMD_RET_USAGE;
+
+	err = rsu_print_status();
+	if (err)
+		return err;
+
+	if (mbox_rsu_get_spt_offset(spt_offset, 4)) {
+		puts("RSU: Error from mbox_rsu_get_spt_offset\n");
+		return -ECOMM;
+	}
+	spt0_off = spt_offset[SPT0_INDEX];
+	spt1_off = spt_offset[SPT1_INDEX];
+
+	if (spt0_out)
+		*spt0_out = spt0_off;
+	if (spt1_out)
+		*spt1_out = spt1_off;
+
+	env_set_hex("rsu_sbt0", spt0_off);
+	env_set_hex("rsu_sbt1", spt1_off);
+	printf("RSU: Sub-partition table 0 offset 0x%08x\n", spt0_off);
+	printf("RSU: Sub-partition table 1 offset 0x%08x\n", spt1_off);
+
+	err = rsu_mtd_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SOCFPGA_RSU_SF_CS, &flash);
+	if (err) {
+		puts("RSU: SPI probe failed.\n");
+		return -ENODEV;
+	}
+
+	if (rsu_mtd_read(flash, spt0_off, sizeof(spt), &spt)) {
+		puts("RSU: rsu_mtd_read failed\n");
+		err = -EIO;
+		goto out;
+	}
+
+	if (spt.magic_number != RSU_S10_SPT_MAGIC_NUMBER) {
+		printf("RSU: Sub-partition table magic number not match 0x%08x\n",
+		       spt.magic_number);
+		err = -EFAULT;
+		goto out;
+	}
+
+	nentries = rsu_s10_spt_entry_count(&spt);
+	rsu_s10_sanitize_spt_names(&spt, nentries);
+	rsu_print_spt_slot(&spt, nentries);
+
+	cpb_offset = rsu_spt_slot_find_cpb(&spt, nentries);
+	if (!cpb_offset) {
+		err = -ENXIO;
+		goto out;
+	}
+	printf("RSU: CMF pointer block offset 0x%08x\n", cpb_offset);
+
+	if (rsu_mtd_read(flash, cpb_offset, sizeof(cpb), &cpb)) {
+		puts("RSU: rsu_mtd_read failed\n");
+		err = -EIO;
+		goto out;
+	}
+
+	if (cpb.magic_number != RSU_S10_CPB_MAGIC_NUMBER) {
+		printf("RSU: CMF pointer block magic number not match 0x%08x\n",
+		       cpb.magic_number);
+		err = -EFAULT;
+		goto out;
+	}
+
+	rsu_print_cpb_slot(&cpb);
+	err = 0;
+out:
+	/* Release the probed SPI flash; no-op under DM_SPI_FLASH. */
+	rsu_mtd_unclaim(flash);
+	return err;
+}
+
+int rsu_spt_cpb_list(int argc, char * const argv[])
+{
+	return rsu_spt_cpb_list_inner(argc, argv, NULL, NULL);
+}
+
+/*
+ * Strictly parse a hex u64 from argv[].
+ *
+ * U-Boot's simple_strtoull() silently accepts partial input ("12xyz")
+ * and wraps on overflow; both would let an unintended flash offset
+ * reach the SDM. Parse digit-by-digit so we can reject either case.
+ *
+ * Allows a single trailing '\n' for pasted-command compatibility.
+ */
+static int rsu_parse_hex_u64(const char *s, u64 *out)
+{
+	const char *start;
+	u64 result = 0;
+
+	if (!s || !*s || !out)
+		return -EINVAL;
+
+	if (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 (result > (ULLONG_MAX - digit) / 16)
+			return -ERANGE;
+
+		result = result * 16 + digit;
+		s++;
+	}
+
+	if (s == start)
+		return -EINVAL;
+	if (*s != '\0' && !(*s == '\n' && s[1] == '\0'))
+		return -EINVAL;
+
+	*out = result;
+	return 0;
+}
+
+int rsu_update(int argc, char * const argv[])
+{
+	u32 flash_offset[2];
+	u64 addr;
+	int ret;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	if (rsu_parse_hex_u64(argv[1], &addr))
+		return CMD_RET_USAGE;
+
+	flash_offset[0] = lower_32_bits(addr);
+	flash_offset[1] = upper_32_bits(addr);
+
+	printf("RSU: RSU update to 0x%08x%08x\n",
+	       flash_offset[1], flash_offset[0]);
+	ret = mbox_rsu_update(flash_offset);
+	if (ret) {
+		printf("RSU: mbox_rsu_update failed (%d)\n", ret);
+		return CMD_RET_FAILURE;
+	}
+	return CMD_RET_SUCCESS;
+}
+
+int rsu_dtb(int argc, char * const argv[])
+{
+	char flash0_string[100];
+	int nodeoffset, parentoffset, fdt_flash0_offset, len;
+	int child, fp_off;
+	u32 end;
+	const fdt32_t *val;
+	u32 reg[2];
+	u32 spt0_off = 0;
+	u32 spt1_off __always_unused = 0;
+	int err;
+	bool found = false;
+
+	/* Extracting RSU info from bitstream */
+	err = rsu_spt_cpb_list_inner(argc, argv, &spt0_off, &spt1_off);
+	/*
+	 * The shared inner helper returns CMD_RET_USAGE (positive) when
+	 * argv has extra tokens. Surface that to the command framework
+	 * directly instead of treating it as SPT/CPB corruption and
+	 * stomping on the live DTB.
+	 */
+	if (err == CMD_RET_USAGE)
+		return CMD_RET_USAGE;
+	if (err == -EOPNOTSUPP) {
+		return 0;
+	} else if ((err == -ECOMM) || (err == -ENODEV) || (err == -EIO)) {
+		return err;
+	} else if (err) {
+		/*
+		 * There was corruption occurred in SPT or CPB, doesn't
+		 * return error & let load process continue. So that Linux
+		 * can recovery the corrupted SPT or CPB.
+		 */
+		puts("Corrupted SPT or CPB, Linux will recovery them\n");
+	}
+
+	/* Retrieve the soc partition node from Linux DTB as start offset */
+	parentoffset = fdt_path_offset(working_fdt, "/soc");
+	if (parentoffset < 0) {
+		printf("DTB: /soc node not found. Check the dtb and fdt addr.\n");
+		return -ENODEV;
+	}
+
+	/*
+	 * A board may carry more than one fixed-partitions node (e.g.
+	 * one for QSPI and one for NAND). Scan every fixed-partitions
+	 * node to find the RSU-managed boot partition. Primary pass
+	 * matches by label; fallback pass honours the legacy rsu-handle
+	 * phandle on the fixed-partitions parent (older Linux DTBs).
+	 */
+	fp_off = parentoffset;
+	while ((fp_off = fdt_node_offset_by_compatible(working_fdt, fp_off,
+						       "fixed-partitions")) >= 0) {
+		fdt_for_each_subnode(child, working_fdt, fp_off) {
+			const char *lbl;
+
+			lbl = fdt_getprop(working_fdt, child, "label", NULL);
+			if (lbl && !strcmp(lbl, RSU_BOOT_PARTITION_LABEL)) {
+				nodeoffset = child;
+				found = true;
+				break;
+			}
+		}
+		if (found)
+			break;
+	}
+
+	if (!found) {
+		fp_off = parentoffset;
+		while ((fp_off = fdt_node_offset_by_compatible(working_fdt,
+							       fp_off,
+							       "fixed-partitions")) >= 0) {
+			const __be32 *rsu_handle;
+			u32 alt_phandle = 0;
+
+			rsu_handle = fdt_getprop(working_fdt, fp_off,
+						 "rsu-handle", NULL);
+			if (rsu_handle)
+				alt_phandle = be32_to_cpup(rsu_handle);
+			if (!alt_phandle)
+				continue;
+
+			nodeoffset = fdt_node_offset_by_phandle(working_fdt,
+								alt_phandle);
+			if (nodeoffset < 0)
+				continue;
+
+			printf("DTB: boot partition found via rsu-handle (legacy DTB).\n");
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
+		printf("DTB: boot partition not found by label \"%s\" or rsu-handle.\n",
+		       RSU_BOOT_PARTITION_LABEL);
+		return -ENODEV;
+	}
+
+	/* Extract the flash0's reg from Linux DTB */
+	fdt_flash0_offset = fdt_get_path(working_fdt, nodeoffset, flash0_string,
+					 sizeof(flash0_string));
+	if (fdt_flash0_offset < 0) {
+		puts("DTB: qspi_boot alias node not found. Check your dts\n");
+		return -ENODEV;
+	}
+	printf("DTB: qspi_boot node at %s\n", flash0_string);
+
+	/* locate the boot partition */
+	nodeoffset = fdt_path_offset(working_fdt, flash0_string);
+	if (nodeoffset < 0) {
+		printf("DTB: %s node not found\n", flash0_string);
+		return -ENODEV;
+	}
+
+	/* determine initial end address of boot partition */
+	val = fdt_getprop(working_fdt, nodeoffset, "reg", &len);
+	if (!val) {
+		printf("DTB: %s.reg was not found\n", flash0_string);
+		return -ENODEV;
+	}
+	if (len != 2 * sizeof(fdt32_t)) {
+		printf("DTB: %s.reg has incorrect length\n", flash0_string);
+		return -ENODEV;
+	}
+	reg[0] = fdt32_to_cpu(val[0]);
+	reg[1] = fdt32_to_cpu(val[1]);
+	/*
+	 * Reject a DTB-supplied reg window whose start+size wraps u32.
+	 * Without this check, `end` underflows into a tiny value that
+	 * silently passes the spt0_off > end guard below, and the
+	 * subsequent (end - spt0_off) length poisons the boot partition.
+	 */
+	if (reg[0] > U32_MAX - reg[1]) {
+		printf("DTB: %s.reg start+size overflows u32 (0x%x + 0x%x)\n",
+		       flash0_string, reg[0], reg[1]);
+		return -ERANGE;
+	}
+	end = reg[0] + reg[1];
+
+	/* align to 64Kb flash sector size */
+	end = roundup(end, 64 * 1024);
+
+	/*
+	 * Guard reg[1]: spt0_off must lie within the boot partition, else
+	 * the u32 subtract below underflows into a multi-GiB length and
+	 * corrupts the DTB.
+	 */
+	if (spt0_off > end) {
+		printf("DTB: SPT0 offset 0x%x exceeds boot partition end 0x%x\n",
+		       spt0_off, end);
+		return -EINVAL;
+	}
+
+	/* assemble new reg value for boot partition */
+	reg[0] = cpu_to_fdt32(spt0_off);
+	reg[1] = cpu_to_fdt32(end  - spt0_off);
+
+	/* update back to Linux DTB */
+	return fdt_setprop(working_fdt, nodeoffset, "reg", reg, sizeof(reg));
+}
diff --git a/arch/arm/mach-socfpga/smc_rsu_s10.c b/arch/arm/mach-socfpga/smc_rsu_s10.c
new file mode 100644
index 00000000000..01bd18da560
--- /dev/null
+++ b/arch/arm/mach-socfpga/smc_rsu_s10.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Intel Corporation. All rights reserved
+ * Copyright (C) 2026 Altera Corporation <www.altera.com>
+ */
+
+#include <errno.h>
+#include <asm/io.h>
+#include <asm/arch/mailbox_s10.h>
+#include <asm/arch/smc_s10.h>
+#include <asm/system.h>
+#include <linux/intel-smc.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define DCMF_STATUS_INVALID 0xFFFF
+
+u32 smc_rsu_update_address __secure_data;
+u32 smc_rsu_dcmf_version[4] __secure_data = {0, 0, 0, 0};
+u16 smc_rsu_dcmf_status[4] __secure_data = {DCMF_STATUS_INVALID,
+					    DCMF_STATUS_INVALID,
+					    DCMF_STATUS_INVALID,
+					    DCMF_STATUS_INVALID};
+static u32 smc_rsu_max_retry __secure_data;
+
+int smc_store_max_retry(u32 value)
+{
+	void *max_retry;
+
+	/*
+	 * Convert the address of smc_rsu_max_retry
+	 * to pre-relocation address.
+	 */
+	max_retry = (char *)__secure_start - CONFIG_ARMV8_SECURE_BASE +
+			(u64)secure_ram_addr(&smc_rsu_max_retry);
+
+	memcpy(max_retry, &value, sizeof(u32));
+
+	return 0;
+}
+
+static void __secure smc_socfpga_rsu_status_psci(unsigned long function_id)
+{
+	SMC_ALLOC_REG_MEM(r);
+	u64 rsu_status[5];
+
+	SMC_INIT_REG_MEM(r);
+
+	if (mbox_rsu_status_psci((u32 *)rsu_status, 9)) {
+		SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_RSU_ERROR);
+		SMC_RET_REG_MEM(r);
+		return;
+	}
+
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG0, rsu_status[0]);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG1, rsu_status[1]);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG2, rsu_status[2]);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG3, rsu_status[3]);
+
+	SMC_RET_REG_MEM(r);
+}
+
+static void __secure smc_socfpga_rsu_update_psci(unsigned long function_id,
+						 unsigned long update_address)
+{
+	SMC_ALLOC_REG_MEM(r);
+
+	SMC_INIT_REG_MEM(r);
+
+	smc_rsu_update_address = update_address;
+
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_STATUS_OK);
+
+	SMC_RET_REG_MEM(r);
+}
+
+static void __secure smc_socfpga_rsu_notify_psci(unsigned long function_id,
+						 unsigned long execution_stage)
+{
+	SMC_ALLOC_REG_MEM(r);
+	SMC_INIT_REG_MEM(r);
+
+	if (mbox_hps_stage_notify_psci(execution_stage))
+		SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_RSU_ERROR);
+	else
+		SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_STATUS_OK);
+
+	SMC_RET_REG_MEM(r);
+}
+
+static void __secure smc_socfpga_rsu_retry_counter_psci(unsigned long function_id)
+{
+	SMC_ALLOC_REG_MEM(r);
+	u32 rsu_status[9];
+
+	SMC_INIT_REG_MEM(r);
+
+	if (mbox_rsu_status_psci((u32 *)rsu_status, sizeof(rsu_status) / 4)) {
+		SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_RSU_ERROR);
+		SMC_RET_REG_MEM(r);
+		return;
+	}
+
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_STATUS_OK);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG1, rsu_status[8]);
+
+	SMC_RET_REG_MEM(r);
+}
+
+static void __secure smc_socfpga_rsu_dcmf_version_psci(unsigned long
+						       function_id)
+{
+	SMC_ALLOC_REG_MEM(r);
+	u64 resp0;
+	u64 resp1;
+
+	SMC_INIT_REG_MEM(r);
+
+	resp0 = smc_rsu_dcmf_version[1];
+	resp0 = (resp0 << 32) | (u64)smc_rsu_dcmf_version[0];
+
+	resp1 = smc_rsu_dcmf_version[3];
+	resp1 = (resp1 << 32) | (u64)smc_rsu_dcmf_version[2];
+
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_STATUS_OK);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG1, resp0);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG2, resp1);
+
+	SMC_RET_REG_MEM(r);
+}
+
+static void __secure smc_socfpga_rsu_max_retry_psci(unsigned long
+						       function_id)
+{
+	SMC_ALLOC_REG_MEM(r);
+	u64 resp0;
+
+	SMC_INIT_REG_MEM(r);
+
+	resp0 = smc_rsu_max_retry;
+
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_STATUS_OK);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG1, resp0);
+
+	SMC_RET_REG_MEM(r);
+}
+
+static void __secure smc_socfpga_rsu_dcmf_status_psci(unsigned long function_id)
+{
+	SMC_ALLOC_REG_MEM(r);
+	u64 resp0;
+
+	SMC_INIT_REG_MEM(r);
+
+	if (smc_rsu_dcmf_status[0] == DCMF_STATUS_INVALID ||
+	    smc_rsu_dcmf_status[1] == DCMF_STATUS_INVALID ||
+	    smc_rsu_dcmf_status[2] == DCMF_STATUS_INVALID ||
+	    smc_rsu_dcmf_status[3] == DCMF_STATUS_INVALID) {
+		SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_RSU_ERROR);
+		SMC_RET_REG_MEM(r);
+		return;
+	}
+
+	resp0 = smc_rsu_dcmf_status[3];
+	resp0 = (resp0 << 16) | (u64)smc_rsu_dcmf_status[2];
+	resp0 = (resp0 << 16) | (u64)smc_rsu_dcmf_status[1];
+	resp0 = (resp0 << 16) | (u64)smc_rsu_dcmf_status[0];
+
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG0, INTEL_SIP_SMC_STATUS_OK);
+	SMC_ASSIGN_REG_MEM(r, SMC_ARG1, resp0);
+
+	SMC_RET_REG_MEM(r);
+}
+
+DECLARE_SECURE_SVC(rsu_status_psci, INTEL_SIP_SMC_RSU_STATUS,
+		   smc_socfpga_rsu_status_psci);
+DECLARE_SECURE_SVC(rsu_update_psci, INTEL_SIP_SMC_RSU_UPDATE,
+		   smc_socfpga_rsu_update_psci);
+DECLARE_SECURE_SVC(rsu_notify_psci, INTEL_SIP_SMC_RSU_NOTIFY,
+		   smc_socfpga_rsu_notify_psci);
+DECLARE_SECURE_SVC(rsu_retry_counter_psci, INTEL_SIP_SMC_RSU_RETRY_COUNTER,
+		   smc_socfpga_rsu_retry_counter_psci);
+DECLARE_SECURE_SVC(rsu_dcmf_version_psci, INTEL_SIP_SMC_RSU_DCMF_VERSION,
+		   smc_socfpga_rsu_dcmf_version_psci);
+DECLARE_SECURE_SVC(rsu_max_retry_psci, INTEL_SIP_SMC_RSU_MAX_RETRY,
+		   smc_socfpga_rsu_max_retry_psci);
+DECLARE_SECURE_SVC(rsu_dcmf_status_psci, INTEL_SIP_SMC_RSU_DCMF_STATUS,
+		   smc_socfpga_rsu_dcmf_status_psci);
diff --git a/cmd/Kconfig b/cmd/Kconfig
index c71c6824a19..67b035729f2 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1275,6 +1275,17 @@ config CMD_FPGAD
 	  fpga_get_reg() function. This functions similarly to the 'md'
 	  command.
 
+config CMD_SOCFPGA_RSU
+	bool "rsu - Altera SoC FPGA Remote System Update"
+	depends on ARCH_SOCFPGA_SOC64
+	depends on CADENCE_QSPI
+	select MISC
+	help
+	  Remote System Update (RSU) for Altera Stratix 10 / Agilex family
+	  SoCs: inspect flash images and the sub-partition table (SPT) /
+	  configuration pointer block (CPB), manage configuration slots,
+	  trigger firmware updates, and adjust the Linux DTB QSPI layout.
+
 config CMD_FUSE
 	bool "fuse - support for the fuse subssystem"
 	depends on !COMPILE_TEST
-- 
2.43.7



More information about the U-Boot mailing list