soc/amd/picasso: Enable APOB/MRC training data cache

Picasso doesn't really make use of the common mrc_cache driver because
of the PSP/ABL requirements for APOB NV data. The APOB NV data
gets consumed by PSP/ABLs before x86 comes out of reset. Hence, we cannot
really add any metadata to this saved data or use multiple slots as
done by the default MRC cache driver
(CACHE_MRC_SETTINGS). Additionally, FSP-M requires access to this APOB
NV data which coreboot needs to pass in from different locations
depending upon boot mode:
1. Non-S3 boot: PSP/ABLs store APOB NV data in DRAM at predetermined
location which is present in BIOS directory table.
2. S3 boot: PSP/ABLs do not store APOB NV data in DRAM.

Thus, coreboot needs to set FSP-M UPD NvsBufferPtr as the DRAM
location in non-S3 boot and the address of RW_MRC_CACHE on SPI flash
in case of S3 resume.

This change enables MRC cache support in Picasso in order to meet the
above requirements.
1. NvsBufferPtr is set based on boot mode.
2. APOB NV data is not stashed to CBMEM. Instead it is written right
away to SPI flash in romstage.

BUG=b:155990176

Change-Id: I8661a4cf2d34502967e936bf22a13f6f1b88e544
Signed-off-by: Furquan Shaikh <furquan@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/42107
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Raul Rangel <rrangel@chromium.org>
diff --git a/src/soc/amd/picasso/Kconfig b/src/soc/amd/picasso/Kconfig
index e3d145d..5ba0c1b 100644
--- a/src/soc/amd/picasso/Kconfig
+++ b/src/soc/amd/picasso/Kconfig
@@ -53,7 +53,6 @@
 	select FSP_COMPRESS_FSP_S_LZMA
 	select FSP_USES_CB_STACK
 	select UDK_2017_BINDING
-	select CACHE_MRC_SETTINGS
 	select HAVE_CF9_RESET
 
 config PRERAM_CBMEM_CONSOLE_SIZE
@@ -303,17 +302,6 @@
 	  Location in DRAM where the PSP will copy the AGESA PSP Output
 	  Block.
 
-# This value is currently the same as the default defined in
-# drivers/mrc_cache/Kconfig. We do this in in case the default
-# changes. The PSP requires this value to be 64KiB.
-config MRC_SETTINGS_CACHE_SIZE
-	hex
-	default 0x10000
-	help
-	  Size of flash area used to save APOB NV data which occupies the
-	  RW_MRC_CACHE region.  Make this granularity the flash device can
-	  erase.
-
 config USE_PSPSCUREOS
 	bool
 	default y
diff --git a/src/soc/amd/picasso/Makefile.inc b/src/soc/amd/picasso/Makefile.inc
index 3b59684..8012f97 100644
--- a/src/soc/amd/picasso/Makefile.inc
+++ b/src/soc/amd/picasso/Makefile.inc
@@ -31,6 +31,7 @@
 romstage-$(CONFIG_HAVE_SMI_HANDLER) += smi_util.c
 romstage-y += psp.c
 romstage-y += config.c
+romstage-y += mrc_cache.c
 
 verstage-y += gpio.c
 verstage-y += i2c.c
diff --git a/src/soc/amd/picasso/include/soc/mrc_cache.h b/src/soc/amd/picasso/include/soc/mrc_cache.h
new file mode 100644
index 0000000..ed63ba8
--- /dev/null
+++ b/src/soc/amd/picasso/include/soc/mrc_cache.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __PICASSO_MRC_CACHE_H__
+#define __PICASSO_MRC_CACHE_H__
+
+void *soc_fill_mrc_cache(void);
+void soc_update_mrc_cache(void);
+
+#endif /* __PICASSO_MRC_CACHE_H__ */
diff --git a/src/soc/amd/picasso/mrc_cache.c b/src/soc/amd/picasso/mrc_cache.c
new file mode 100644
index 0000000..ad0d964
--- /dev/null
+++ b/src/soc/amd/picasso/mrc_cache.c
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <acpi/acpi.h>
+#include <assert.h>
+#include <boot_device.h>
+#include <bootstate.h>
+#include <commonlib/region.h>
+#include <console/console.h>
+#include <fmap.h>
+#include <soc/mrc_cache.h>
+#include <spi_flash.h>
+#include <stdint.h>
+#include <string.h>
+
+#define DEFAULT_MRC_CACHE "RW_MRC_CACHE"
+/* PSP requires this value to be 64KiB */
+#define DEFAULT_MRC_CACHE_SIZE 0x10000
+
+#if !CONFIG_PSP_APOB_DRAM_ADDRESS
+#error Incorrect APOB configuration setting(s)
+#endif
+
+#define APOB_SIGNATURE 0x424F5041	/* 'APOB' */
+
+/* APOB_BASE_HEADER from AGESA */
+struct apob_base_header {
+	uint32_t   signature;			/* APOB signature */
+	uint32_t   version;			/* Version */
+	uint32_t   size;			/* APOB Size */
+	uint32_t   offset_of_first_entry;	/* APOB Header Size */
+};
+
+static bool apob_header_valid(const struct apob_base_header *apob_header_ptr, const char *where)
+{
+	if (apob_header_ptr->signature != APOB_SIGNATURE) {
+		printk(BIOS_WARNING, "Invalid %s APOB signature %x\n",
+			where, apob_header_ptr->signature);
+		return false;
+	}
+
+	if (apob_header_ptr->size == 0 || apob_header_ptr->size > DEFAULT_MRC_CACHE_SIZE) {
+		printk(BIOS_WARNING, "%s APOB data is too large %x > %x\n",
+			where, apob_header_ptr->size, DEFAULT_MRC_CACHE_SIZE);
+		return false;
+	}
+
+	return true;
+}
+
+static void *get_apob_dram_address(void)
+{
+	/*
+	 * TODO: Find the APOB destination by parsing the PSP's tables
+	 * (once vboot is implemented).
+	 */
+	void *apob_src_ram = (void *)(uintptr_t)CONFIG_PSP_APOB_DRAM_ADDRESS;
+
+	if (apob_header_valid(apob_src_ram, "RAM") == false)
+		return NULL;
+
+	return apob_src_ram;
+}
+
+static int get_nv_region(struct region *r)
+{
+	if  (fmap_locate_area(DEFAULT_MRC_CACHE, r) < 0) {
+		printk(BIOS_ERR, "Error: No APOB NV region is found in flash\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static void *get_apob_from_nv_region(struct region *region)
+{
+	struct region_device read_rdev;
+	struct apob_base_header apob_header;
+
+	if (boot_device_ro_subregion(region, &read_rdev) < 0) {
+		printk(BIOS_ERR, "Failed boot_device_ro_subregion\n");
+		return NULL;
+	}
+
+	if (rdev_readat(&read_rdev, &apob_header, 0, sizeof(apob_header)) < 0) {
+		printk(BIOS_ERR, "Couldn't read APOB header!\n");
+		return NULL;
+	}
+
+	if (apob_header_valid(&apob_header, "ROM") == false) {
+		printk(BIOS_ERR, "No APOB NV data!\n");
+		return NULL;
+	}
+
+	assert(CONFIG(BOOT_DEVICE_MEMORY_MAPPED));
+	return rdev_mmap_full(&read_rdev);
+}
+
+/* Save APOB buffer to flash */
+void soc_update_mrc_cache(void)
+{
+	struct apob_base_header *apob_rom;
+	struct region_device write_rdev;
+	struct region region;
+	bool update_needed = false;
+	const struct apob_base_header *apob_src_ram;
+
+	/* Nothing to update in case of S3 resume. */
+	if (acpi_is_wakeup_s3())
+		return;
+
+	apob_src_ram = get_apob_dram_address();
+	if (apob_src_ram == NULL)
+		return;
+
+	if (get_nv_region(&region) != 0)
+		return;
+
+	apob_rom = get_apob_from_nv_region(&region);
+	if (apob_rom == NULL) {
+		update_needed = true;
+	} else if (memcmp(apob_src_ram, apob_rom, apob_src_ram->size)) {
+		printk(BIOS_INFO, "APOB RAM copy differs from flash\n");
+		update_needed = true;
+	} else
+		printk(BIOS_DEBUG, "APOB valid copy is already in flash\n");
+
+	if (!update_needed)
+		return;
+
+	printk(BIOS_SPEW, "Copy APOB from RAM 0x%p/0x%x to flash 0x%zx/0x%zx\n",
+		apob_src_ram, apob_src_ram->size,
+		region_offset(&region), region_sz(&region));
+
+	if (boot_device_rw_subregion(&region, &write_rdev) < 0) {
+		printk(BIOS_ERR, "Failed boot_device_rw_subregion\n");
+		return;
+	}
+
+	/* write data to flash region */
+	if (rdev_eraseat(&write_rdev, 0, DEFAULT_MRC_CACHE_SIZE) < 0) {
+		printk(BIOS_ERR, "Error: APOB flash region erase failed\n");
+		return;
+	}
+
+	if (rdev_writeat(&write_rdev, apob_src_ram, 0, apob_src_ram->size) < 0) {
+		printk(BIOS_ERR, "Error: APOB flash region update failed\n");
+		return;
+	}
+
+	printk(BIOS_INFO, "Updated APOB in flash\n");
+}
+
+static void *get_apob_nv_address(void)
+{
+	struct region region;
+
+	if (get_nv_region(&region) != 0)
+		return NULL;
+
+	return get_apob_from_nv_region(&region);
+}
+
+void *soc_fill_mrc_cache(void)
+{
+	/* If this is non-S3 boot, then use the APOB data placed by PSP in DRAM. */
+	if (!acpi_is_wakeup_s3())
+		return get_apob_dram_address();
+
+	/*
+	 * In case of S3 resume, PSP does not copy APOB data to DRAM. Thus, coreboot needs to
+	 * provide the APOB NV data from RW_MRC_CACHE on SPI flash so that FSP can use it
+	 * without having to traverse the BIOS directory table.
+	 */
+	return get_apob_nv_address();
+}
diff --git a/src/soc/amd/picasso/romstage.c b/src/soc/amd/picasso/romstage.c
index 35d2aa2..5416e8a 100644
--- a/src/soc/amd/picasso/romstage.c
+++ b/src/soc/amd/picasso/romstage.c
@@ -12,6 +12,7 @@
 #include <program_loading.h>
 #include <elog.h>
 #include <soc/romstage.h>
+#include <soc/mrc_cache.h>
 #include <types.h>
 #include "chip.h"
 #include <fsp/api.h>
@@ -26,6 +27,8 @@
 	FSP_M_CONFIG *mcfg = &mupd->FspmConfig;
 	const config_t *config = config_of_soc();
 
+	mupd->FspmArchUpd.NvsBufferPtr = soc_fill_mrc_cache();
+
 	mcfg->pci_express_base_addr = CONFIG_MMCONF_BASE_ADDRESS;
 	mcfg->tseg_size = CONFIG_SMM_TSEG_SIZE;
 	mcfg->serial_port_base = uart_platform_base(CONFIG_UART_FOR_CONSOLE);
@@ -85,6 +88,7 @@
 
 	post_code(0x43);
 	fsp_memory_init(s3_resume);
+	soc_update_mrc_cache();
 
 	post_code(0x44);
 	run_ramstage();