From 97d616b927a101fae5b6957d1a749bee43f113c7 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 21 Jul 2025 15:54:44 +0200 Subject: [PATCH] soc/amd/common/block/spi: Add helper functions When a mainboard has a secondary SPI flash, also referred to as DUAL SPIROM or backup SPI flash, and a board specific recovery mechanism for failed flash updates it might need to access the secondary SPI flash. A use case would be syncing the MRC cache (APOB NV on AMD), RPMC and fTPM regions to the secondary flash. Add generic code to access the "backup" SPI flash. It assumed that both SPI flash have the same size and same type. The backup SPI flash chip select line is determined at runtime so that it is the opposite of boot_device_spi_cs(). Thus when booting from CS2, CS0 will become the backup flash. TEST=Can access and use backup flash on AMD Glinda SoC. Signed-off-by: Maximilian Brune Change-Id: Ied683408d36850416fc1bbfaef0c415703ff183e Reviewed-on: https://review.coreboot.org/c/coreboot/+/90780 Tested-by: build bot (Jenkins) Reviewed-by: Felix Held --- .../include/amdblocks/backup_boot_device.h | 19 ++ src/soc/amd/common/block/spi/Kconfig | 20 ++ src/soc/amd/common/block/spi/Makefile.mk | 5 + .../block/spi/backup_boot_device_rw_nommap.c | 192 ++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 src/soc/amd/common/block/include/amdblocks/backup_boot_device.h create mode 100644 src/soc/amd/common/block/spi/backup_boot_device_rw_nommap.c diff --git a/src/soc/amd/common/block/include/amdblocks/backup_boot_device.h b/src/soc/amd/common/block/include/amdblocks/backup_boot_device.h new file mode 100644 index 0000000000..6eb4b2b6be --- /dev/null +++ b/src/soc/amd/common/block/include/amdblocks/backup_boot_device.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef AMD_BLOCK_BACKUP_BOOT_DEVICE_H +#define AMD_BLOCK_BACKUP_BOOT_DEVICE_H + +#include +#include + +/* Retrieve the SPI CS index of the backup boot device. */ +int backup_boot_device_spi_cs(void); + +const struct region_device *backup_boot_device_rw(void); +const struct spi_flash *backup_boot_device_spi_flash(void); +int backup_boot_device_rw_subregion(const struct region *sub, + struct region_device *subrd); +int backup_boot_device_sync_subregion(const struct region *sub, + const bool direction_prim_to_sec); + +#endif diff --git a/src/soc/amd/common/block/spi/Kconfig b/src/soc/amd/common/block/spi/Kconfig index 10e88a98bb..76ae3b7a0b 100644 --- a/src/soc/amd/common/block/spi/Kconfig +++ b/src/soc/amd/common/block/spi/Kconfig @@ -170,3 +170,23 @@ config SOC_AMD_PSP_ROM_ARMOR_64K_ERASE Enable 64KB erase block size support in addition to 4KB blocks. This can improve erase performance when erasing large regions. The PSP firmware must support 64KB erase commands for this to work. + +config SOC_AMD_COMMON_BLOCK_SPI_BACKUP_SPI_FLASH + bool + depends on SOC_AMD_COMMON_BLOCK_SPI + help + Select this option when there is a second SPI flash + which can be booted of when the primary SPI flash is + corrupted. The recovery mechanism is board specific. + The secondary SPI flash must be of the same type and + same size as the primary SPI flash. + +config BACKUP_BOOT_DEVICE_SPI_CHIP_SELECT + int + depends on SOC_AMD_COMMON_BLOCK_SPI_BACKUP_SPI_FLASH + default 1 + help + Which chip select line the backup boot device is connected to. + The assumption here is that chip select 0 and this one have an SPI flash connected to + it. Depending on which chip select of these two we booted from, we deem one of them + our backup/secondary (the one we didn't boot from) SPI flash. diff --git a/src/soc/amd/common/block/spi/Makefile.mk b/src/soc/amd/common/block/spi/Makefile.mk index 0dc78d9f3b..36e61e89f5 100644 --- a/src/soc/amd/common/block/spi/Makefile.mk +++ b/src/soc/amd/common/block/spi/Makefile.mk @@ -28,4 +28,9 @@ ramstage-y += fch_spi_util.c verstage-y += fch_spi_util.c smm-$(CONFIG_SPI_FLASH_SMM) += fch_spi_util.c +ifeq ($(CONFIG_SOC_AMD_COMMON_BLOCK_SPI_BACKUP_SPI_FLASH),y) +all_x86-y += backup_boot_device_rw_nommap.c +smm-$(CONFIG_SPI_FLASH_SMM) += backup_boot_device_rw_nommap.c +endif # CONFIG_SOC_AMD_COMMON_BLOCK_SPI_BACKUP_SPI_FLASH + endif # CONFIG_SOC_AMD_COMMON_BLOCK_SPI diff --git a/src/soc/amd/common/block/spi/backup_boot_device_rw_nommap.c b/src/soc/amd/common/block/spi/backup_boot_device_rw_nommap.c new file mode 100644 index 0000000000..d1f7b1523e --- /dev/null +++ b/src/soc/amd/common/block/spi/backup_boot_device_rw_nommap.c @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include + +static struct spi_flash sfg; +static bool sfg_init_done; + +/* + * The assumption here is that chip select 0 and CONFIG_BACKUP_BOOT_DEVICE_SPI_CHIP_SELECT have + * an SPI flash connected to it. Depending on which chip select of these two we booted from, we + * deem one of them our backup/secondary (the one we didn't boot from) SPI flash. + */ +int backup_boot_device_spi_cs(void) +{ + if (boot_device_spi_cs() == 0) + return CONFIG_BACKUP_BOOT_DEVICE_SPI_CHIP_SELECT; + return 0; +} + +static ssize_t spi_readat(const struct region_device *rd, void *b, + size_t offset, size_t size) +{ + if (spi_flash_read(&sfg, offset, size, b)) + return -1; + + return size; +} + +static ssize_t spi_writeat(const struct region_device *rd, const void *b, + size_t offset, size_t size) +{ + if (spi_flash_write(&sfg, offset, size, b)) + return -1; + + return size; +} + +static ssize_t spi_eraseat(const struct region_device *rd, + size_t offset, size_t size) +{ + if (spi_flash_erase(&sfg, offset, size)) + return -1; + + return size; +} + +static const struct region_device_ops spi_ops = { + .readat = spi_readat, + .writeat = spi_writeat, + .eraseat = spi_eraseat, +}; + +static const struct region_device spi_rw = + REGION_DEV_INIT(&spi_ops, 0, CONFIG_ROM_SIZE); + +static void backup_boot_device_rw_init(void) +{ + const int bus = CONFIG_BOOT_DEVICE_SPI_FLASH_BUS; + const int cs = backup_boot_device_spi_cs(); + + if (sfg_init_done == true) + return; + + /* Ensure any necessary setup is performed by the drivers. */ + spi_init(); + + if (!spi_flash_probe(bus, cs, &sfg)) + sfg_init_done = true; +} + +/** + * Returns the secondary SPI flash as read-writable region device. + * + * @return NULL on error. The region_device on success. + */ +const struct region_device *backup_boot_device_rw(void) +{ + /* Probe for the SPI flash device if not already done. */ + backup_boot_device_rw_init(); + + if (sfg_init_done != true) + return NULL; + + return &spi_rw; +} + +/** + * Returns the secondary SPI flash as spi_flash device. + * + * @return NULL on error. The region_device on success. + */ +const struct spi_flash *backup_boot_device_spi_flash(void) +{ + backup_boot_device_rw_init(); + + if (sfg_init_done != true) + return NULL; + + return &sfg; +} + +/** + * Returns a sub-region of the secondary SPI flash. + * + * @param sub The subregion within the SPI flash + * @param subrd The region_device to return + * + * @return 0 on success. + */ +int backup_boot_device_rw_subregion(const struct region *sub, + struct region_device *subrd) +{ + /* Ensure boot device has been initialized at least once. */ + backup_boot_device_rw_init(); + + const struct region_device *parent = backup_boot_device_rw(); + + if (!parent) + return -1; + + return rdev_chain(subrd, parent, region_offset(sub), region_sz(sub)); +} + +/** + * Synchronize the SPI flash contents from one chip to the other. + * + * @param sub The region to synchronize + * @param direction_prim_to_sec When true synchronize the primary (boot_device_rw()) + * to the secondary (backup_boot_device_rw()). + * When false synchronize the secondary (backup_boot_device_rw()) + * to the primary (boot_device_rw()): + * + * @return 0 on success. + */ +int backup_boot_device_sync_subregion(const struct region *sub, + const bool direction_prim_to_sec) +{ + struct region_device prim, sec; + static uint8_t buffer_prim[64 * KiB], buffer_sec[64 * KiB]; + struct region_device *rdev; + void *data; + int ret; + + const struct spi_flash *flash = boot_device_spi_flash(); + if (!flash) + return -1; + + ret = boot_device_rw_subregion(sub, &prim); + if (ret) + return ret; + + ret = backup_boot_device_rw_subregion(sub, &sec); + if (ret) + return ret; + + /* Set target and source for transfer */ + rdev = direction_prim_to_sec ? &sec : &prim; + data = direction_prim_to_sec ? buffer_prim : buffer_sec; + + size_t remaining = region_device_sz(&prim); + size_t offset = 0, written = 0; + do { + size_t chunk = MIN(remaining, ARRAY_SIZE(buffer_prim)); + chunk = MIN(chunk, flash->sector_size); + + if (rdev_readat(&prim, buffer_prim, offset, chunk) != chunk) + return -1; + if (rdev_readat(&sec, buffer_sec, offset, chunk) != chunk) + return -1; + + if (memcmp(buffer_prim, buffer_sec, chunk) != 0) { + if (rdev_eraseat(rdev, offset, chunk) != chunk) + return -1; + if (rdev_writeat(rdev, data, offset, chunk) != chunk) + return -1; + written += chunk; + } + + remaining -= chunk; + offset += chunk; + } while (remaining); + + printk(BIOS_INFO, "%s: Synced %ld bytes from SPI flash %s->%s\n", __func__, + written, direction_prim_to_sec ? "PRIM" : "SEC", + direction_prim_to_sec ? "SEC" : "PRIM"); + return 0; +}