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, ¤t_offset, ¤t_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, ¤t_offset, ¤t_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, ¤t_offset, ¤t_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

