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

