From: Dinesh Maniyam <[email protected]>

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 <[email protected]>
---
 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


Reply via email to