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 <maximilian.brune@9elements.com>
Change-Id: Ied683408d36850416fc1bbfaef0c415703ff183e
Reviewed-on: https://review.coreboot.org/c/coreboot/+/90780
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Felix Held <felix-coreboot@felixheld.de>
This commit is contained in:
Patrick Rudolph 2025-07-21 15:54:44 +02:00 committed by Matt DeVillier
commit 97d616b927
4 changed files with 236 additions and 0 deletions

View file

@ -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 <commonlib/region.h>
#include <spi_flash.h>
/* 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

View file

@ -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.

View file

@ -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

View file

@ -0,0 +1,192 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <amdblocks/backup_boot_device.h>
#include <boot_device.h>
#include <spi_flash.h>
#include <spi-generic.h>
#include <stdint.h>
#include <string.h>
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;
}