drivers/smmstore: Implement SMMSTORE version 2

SMMSTORE version 2 is a complete redesign of the current driver. It is
not backwards-compatible with version 1, and only one version can be
used at a time.

Key features:
* Uses a fixed communication buffer instead of writing to arbitrary
  memory addresses provided by untrusted ring0 code.
* Gives the caller full control over the used data format.
* Splits the store into smaller chunks to allow fault tolerant updates.
* Doesn't provide feedback about the actual read/written bytes, just
  returns error or success in registers.
* Returns an error if the requested operation would overflow the
  communication buffer.

Separate the SMMSTORE into 64 KiB blocks that can individually be
read/written/erased. To be used by payloads that implement a
FaultTolerant Variable store like TianoCore.

The implementation has been tested against EDK2 master.

An example EDK2 implementation can be found here:
https://github.com/9elements/edk2-1/commit/eb1127744a3a5d5c8ac4e8eb76f07e79c736dbe2

Change-Id: I25e49d184135710f3e6dd1ad3bed95de950fe057
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Signed-off-by: Christian Walter <christian.walter@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/40520
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Michał Żygowski <michal.zygowski@3mdeb.com>
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
diff --git a/Documentation/drivers/index.md b/Documentation/drivers/index.md
index e215c6a..508beaf 100644
--- a/Documentation/drivers/index.md
+++ b/Documentation/drivers/index.md
@@ -7,3 +7,4 @@
 * [IPMI KCS](ipmi_kcs.md)
 * [SMMSTORE](smmstore.md)
 * [SoundWire](soundwire.md)
+* [SMMSTOREv2](smmstorev2.md)
diff --git a/Documentation/drivers/smmstorev2.md b/Documentation/drivers/smmstorev2.md
new file mode 100644
index 0000000..cb79b8b
--- /dev/null
+++ b/Documentation/drivers/smmstorev2.md
@@ -0,0 +1,221 @@
+# SMM based flash storage driver Version 2
+
+This documents the API exposed by the x86 system management based
+storage driver.
+
+## SMMSTOREv2
+
+SMMSTOREv2 is a [SMM] mediated driver to read from, write to and erase
+a predefined region in flash. It can be enabled by setting
+`CONFIG_SMMSTORE=y` and `CONFIG_SMMSTORE_V2=y` in menuconfig.
+
+This can be used by the OS or the payload to implement persistent
+storage to hold for instance configuration data, without needing to
+implement a (platform specific) storage driver in the payload itself.
+
+### Storage size and alignment
+
+SMMSTORE version 2 requires a minimum alignment of 64 KiB, which should
+be supported by all flash chips. Not having to perform read-modify-write
+operations is desired, as it reduces complexity and potential for bugs.
+
+This can be used by a FTW (FaultTolerantWrite) implementation that uses
+at least two regions in an A/B update scheme. The FTW implementation in
+EDK2 uses three different regions in the store:
+
+- The variable store
+- The FTW spare block
+- The FTW working block
+
+All regions must be block-aligned, and the FTW spare size must be larger
+than that of the variable store. FTW working block can be much smaller.
+With 64 KiB as block size, the minimum size of the FTW-enabled store is:
+
+- The variable store: 1 block = 64 KiB
+- The FTW spare block: 2 blocks = 2 * 64 KiB
+- The FTW working block: 1 block = 64 KiB
+
+Therefore, the minimum size for EDK2 FTW is 4 blocks, or 256 KiB.
+
+## API
+
+The API provides read and write access to an unformatted block storage.
+
+### Storage region
+
+By default SMMSTOREv2 will operate on a separate FMAP region called
+`SMMSTORE`. The default generated FMAP will include such a region. On
+systems with a locked FMAP, e.g. in an existing vboot setup with a
+locked RO region, the option exists to add a cbfsfile called `smm_store`
+in the `RW_LEGACY` (if CHROMEOS) or in the `COREBOOT` FMAP regions. It
+is recommended for new builds using a handcrafted FMD that intend to
+make use of SMMSTORE to include a sufficiently large `SMMSTORE` FMAP
+region. It is mandatory to align the `SMMSTORE` region to 64KiB for
+compatibility with the largest flash erase operation.
+
+When a default generated FMAP is used, the size of the FMAP region is
+equal to `CONFIG_SMMSTORE_SIZE`. UEFI payloads expect at least 64 KiB.
+To support a fault tolerant write mechanism, at least a multiple of
+this size is recommended.
+
+### Communication buffer
+
+To prevent malicious ring0 code to access arbitrary memory locations,
+SMMSTOREv2 uses a communication buffer in CBMEM/HOB for all transfers.
+This buffer has to be at least 64 KiB in size and must be installed
+before calling any of the SMMSTORE read or write operations. Usually,
+coreboot will install this buffer to transfer data between ring0 and
+the [SMM] handler.
+
+In order to get the communication buffer address, the payload or OS
+has to read the coreboot table with tag `0x0039`, containing:
+
+```C
+struct lb_smmstorev2 {
+	uint32_t tag;
+	uint32_t size;
+	uint32_t num_blocks;	/* Number of writeable blocks in SMM */
+	uint32_t block_size;	/* Size of a block in byte. Default: 64 KiB */
+	uint32_t mmap_addr;	/* MMIO address of the store for read only access */
+	uint32_t com_buffer;	/* Physical address of the communication buffer */
+	uint32_t com_buffer_size;	/* Size of the communication buffer in byte */
+	uint8_t apm_cmd;	/* The command byte to write to the APM I/O port */
+	uint8_t unused[3];	/* Set to zero */
+};
+```
+
+The absence of this coreboot table entry indicates that there's no
+SMMSTOREv2 support.
+
+### Blocks
+
+The SMMSTOREv2 splits the SMMSTORE FMAP partition into smaller chunks
+called *blocks*. Every block is at least the size of 64KiB to support
+arbitrary NOR flash erase ops. A payload or OS must make no further
+assumptions about the block or communication buffer size.
+
+### Generating the SMI
+
+SMMSTOREv2 is called via an SMI, which is generated via a write to the
+IO port defined in the smi_cmd entry of the FADT ACPI table. `%al`
+contains `APM_CNT_SMMSTORE=0xed` and is written to the smi_cmd IO
+port. `%ah` contains the SMMSTOREv2 command. `%ebx` contains the
+parameter buffer to the SMMSTOREv2 command.
+
+### Return values
+
+If a command succeeds, SMMSTOREv2 will return with
+`SMMSTORE_RET_SUCCESS=0` in `%eax`. On failure SMMSTORE will return
+`SMMSTORE_RET_FAILURE=1`. For unsupported SMMSTORE commands
+`SMMSTORE_REG_UNSUPPORTED=2` is returned.
+
+**NOTE 1**: The caller **must** check the return value and should make
+no assumption on the returned data if `%eax` does not contain
+`SMMSTORE_RET_SUCCESS`.
+
+**NOTE 2**: If the SMI returns without changing `%ax`, it can be assumed
+that the SMMSTOREv2 feature is not installed.
+
+### Calling arguments
+
+SMMSTOREv2 supports 3 subcommands that are passed via `%ah`, the
+additional calling arguments are passed via `%ebx`.
+
+**NOTE**: The size of the struct entries are in the native word size of
+smihandler. This means 32 bits in almost all cases.
+
+#### - SMMSTORE_CMD_INIT = 4
+
+This installs the communication buffer to use and thus enables the
+SMMSTORE handler. This command can only be executed once and is done
+by the firmware. Calling this function at runtime has no effect.
+
+The additional parameter buffer `%ebx` contains a pointer to the
+following struct:
+
+```C
+struct smmstore_params_init {
+	uint32_t com_buffer;
+	uint32_t com_buffer_size;
+} __packed;
+```
+
+INPUT:
+- `com_buffer`: Physical address of the communication buffer (CBMEM)
+- `com_buffer_size`: Size in bytes of the communication buffer
+
+#### - SMMSTORE_CMD_RAW_READ = 5
+
+SMMSTOREv2 allows reading arbitrary data. It is up to the caller to
+initialize the store with meaningful data before using it.
+
+The additional parameter buffer `%ebx` contains a pointer to the
+following struct:
+
+```C
+struct smmstore_params_raw_read {
+	uint32_t bufsize;
+	uint32_t bufoffset;
+	uint32_t block_id;
+} __packed;
+```
+
+INPUT:
+- `bufsize`: Size of data to read within the communication buffer
+- `bufoffset`: Offset within the communication buffer
+- `block_id`: Block to read from
+
+#### - SMMSTORE_CMD_RAW_WRITE = 6
+
+SMMSTOREv2 allows writing arbitrary data. It is up to the caller to
+erase a block before writing it.
+
+The additional parameter buffer `%ebx` contains a pointer to
+the following struct:
+
+```C
+struct smmstore_params_raw_write {
+        uint32_t bufsize;
+        uint32_t bufoffset;
+        uint32_t block_id;
+} __packed;
+```
+
+INPUT:
+- `bufsize`: Size of data to write within the communication buffer
+- `bufoffset`: Offset within the communication buffer
+- `block_id`: Block to write to
+
+#### - SMMSTORE_CMD_RAW_CLEAR = 7
+
+SMMSTOREv2 allows clearing blocks. A cleared block will read as `0xff`.
+By providing multiple blocks the caller can implement a fault tolerant
+write mechanism. It is up to the caller to clear blocks before writing
+to them.
+
+
+```C
+struct smmstore_params_raw_clear {
+	uint32_t block_id;
+} __packed;
+```
+
+INPUT:
+- `block_id`: Block to erase
+
+#### Security
+
+Pointers provided by the payload or OS are checked to not overlap with
+SMM. This protects the SMM handler from being compromised.
+
+As all information is exchanged using the communication buffer and
+coreboot tables, there's no risk that a malicious application capable
+of issuing SMIs could extract arbitrary data or modify the currently
+running kernel.
+
+## External links
+
+* [A Tour Beyond BIOS Implementing UEFI Authenticated Variables in SMM with EDKI](https://software.intel.com/sites/default/files/managed/cf/ea/a_tour_beyond_bios_implementing_uefi_authenticated_variables_in_smm_with_edkii.pdf)
+Note that this differs significantly from coreboot's implementation.
+
+[SMM]: ../security/smm.md
diff --git a/payloads/libpayload/include/coreboot_tables.h b/payloads/libpayload/include/coreboot_tables.h
index bf8c0d9..bfdd21e 100644
--- a/payloads/libpayload/include/coreboot_tables.h
+++ b/payloads/libpayload/include/coreboot_tables.h
@@ -79,6 +79,7 @@
 	CB_TAG_MMC_INFO			= 0x0035,
 	CB_TAG_TCPA_LOG			= 0x0036,
 	CB_TAG_FMAP			= 0x0037,
+	CB_TAG_SMMSTOREV2		= 0x0039,
 	CB_TAG_CMOS_OPTION_TABLE	= 0x00c8,
 	CB_TAG_OPTION			= 0x00c9,
 	CB_TAG_OPTION_ENUM		= 0x00ca,
diff --git a/src/commonlib/include/commonlib/cbmem_id.h b/src/commonlib/include/commonlib/cbmem_id.h
index ac271a0..6b4d604 100644
--- a/src/commonlib/include/commonlib/cbmem_id.h
+++ b/src/commonlib/include/commonlib/cbmem_id.h
@@ -68,6 +68,7 @@
 #define CBMEM_ID_ROM3		0x524f4d33
 #define CBMEM_ID_FMAP		0x464d4150
 #define CBMEM_ID_FSP_LOGO	0x4c4f474f
+#define CBMEM_ID_SMM_COMBUFFER	0x53534d32
 
 #define CBMEM_ID_TO_NAME_TABLE				 \
 	{ CBMEM_ID_ACPI,		"ACPI       " }, \
diff --git a/src/commonlib/include/commonlib/coreboot_tables.h b/src/commonlib/include/commonlib/coreboot_tables.h
index 6393c01..4406002 100644
--- a/src/commonlib/include/commonlib/coreboot_tables.h
+++ b/src/commonlib/include/commonlib/coreboot_tables.h
@@ -80,6 +80,7 @@
 	LB_TAG_TCPA_LOG			= 0x0036,
 	LB_TAG_FMAP			= 0x0037,
 	LB_TAG_PLATFORM_BLOB_VERSION	= 0x0038,
+	LB_TAG_SMMSTOREV2		= 0x0039,
 	LB_TAG_CMOS_OPTION_TABLE	= 0x00c8,
 	LB_TAG_OPTION			= 0x00c9,
 	LB_TAG_OPTION_ENUM		= 0x00ca,
@@ -484,4 +485,20 @@
 #define CHECKSUM_PCBIOS	1
 };
 
+/* SMMSTOREv2 record
+ * This record contains information to use SMMSTOREv2.
+ */
+
+struct lb_smmstorev2 {
+	uint32_t tag;
+	uint32_t size;
+	uint32_t num_blocks;		/* Number of writeable blocks in SMM */
+	uint32_t block_size;		/* Size of a block in byte. Default: 64 KiB */
+	uint32_t mmap_addr;		/* MMIO address of the store for read only access */
+	uint32_t com_buffer;		/* Physical address of the communication buffer */
+	uint32_t com_buffer_size;	/* Size of the communication buffer in bytes */
+	uint8_t apm_cmd;		/* The command byte to write to the APM I/O port */
+	uint8_t unused[3];		/* Set to zero */
+};
+
 #endif
diff --git a/src/drivers/smmstore/Kconfig b/src/drivers/smmstore/Kconfig
index 7ee8676..ba8268e 100644
--- a/src/drivers/smmstore/Kconfig
+++ b/src/drivers/smmstore/Kconfig
@@ -6,6 +6,18 @@
 	default y if PAYLOAD_TIANOCORE
 	select SPI_FLASH_SMM if BOOT_DEVICE_SPI_FLASH_RW_NOMMAP
 
+config SMMSTORE_V2
+	bool "Use version 2 of SMMSTORE API"
+	depends on SMMSTORE
+	default n
+	help
+	  Version 2 of SMMSTORE allows secure communication with SMM and
+	  makes no assumptions on the structure of the data stored within.
+	  It splits the store into chunks to allows fault tolerant writes.
+
+	  By using version 2 you cannot make use of software that expects
+	  a version 1 SMMSTORE.
+
 config SMMSTORE_IN_CBFS
 	bool
 	default n
diff --git a/src/drivers/smmstore/Makefile.inc b/src/drivers/smmstore/Makefile.inc
index 1cafe3a..90bcdec 100644
--- a/src/drivers/smmstore/Makefile.inc
+++ b/src/drivers/smmstore/Makefile.inc
@@ -1,3 +1,4 @@
 ramstage-$(CONFIG_SMMSTORE) += store.c
+ramstage-$(CONFIG_SMMSTORE_V2) += ramstage.c
 
 smm-$(CONFIG_SMMSTORE) += store.c smi.c
diff --git a/src/drivers/smmstore/ramstage.c b/src/drivers/smmstore/ramstage.c
new file mode 100644
index 0000000..ef80e22
--- /dev/null
+++ b/src/drivers/smmstore/ramstage.c
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <bootstate.h>
+#include <cpu/x86/smm.h>
+#include <commonlib/helpers.h>
+#include <commonlib/region.h>
+#include <console/console.h>
+#include <smmstore.h>
+#include <types.h>
+#include <cbmem.h>
+
+static struct smmstore_params_info info;
+
+void lb_smmstorev2(struct lb_header *header)
+{
+	struct lb_record *rec;
+	struct lb_smmstorev2 *store;
+	const struct cbmem_entry *e;
+
+	e = cbmem_entry_find(CBMEM_ID_SMM_COMBUFFER);
+	if (!e)
+		return;
+
+	rec = lb_new_record(header);
+	store = (struct lb_smmstorev2 *)rec;
+
+	store->tag = LB_TAG_SMMSTOREV2;
+	store->size = sizeof(*store);
+	store->com_buffer = (uintptr_t)cbmem_entry_start(e);
+	store->com_buffer_size = cbmem_entry_size(e);
+	store->mmap_addr = info.mmap_addr;
+	store->num_blocks = info.num_blocks;
+	store->block_size = info.block_size;
+	store->apm_cmd = APM_CNT_SMMSTORE;
+}
+
+static void init_store(void *unused)
+{
+	struct smmstore_params_init args;
+	uint32_t eax = ~0;
+	uint32_t ebx;
+
+	if (smmstore_get_info(&info) < 0) {
+		printk(BIOS_INFO, "SMMSTORE: Failed to get meta data\n");
+		return;
+	}
+
+	void *ptr = cbmem_add(CBMEM_ID_SMM_COMBUFFER, info.block_size);
+	if (!ptr) {
+		printk(BIOS_ERR, "SMMSTORE: Failed to add com buffer\n");
+		return;
+	}
+
+	args.com_buffer = (uintptr_t)ptr;
+	args.com_buffer_size = info.block_size;
+	ebx = (uintptr_t)&args;
+
+	printk(BIOS_INFO, "SMMSTORE: Setting up SMI handler\n");
+
+	/* Issue SMI using APM to update the com buffer and to lock the SMMSTORE */
+	__asm__ __volatile__ (
+		"outb %%al, %%dx"
+		: "=a" (eax)
+		: "a" ((SMMSTORE_CMD_INIT << 8) | APM_CNT_SMMSTORE),
+		  "b" (ebx),
+		  "d" (APM_CNT)
+		: "memory");
+
+	if (eax != SMMSTORE_RET_SUCCESS) {
+		printk(BIOS_ERR, "SMMSTORE: Failed to install com buffer\n");
+		return;
+	}
+}
+
+/* The SMI APM handler is installed at DEV_INIT phase */
+BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_EXIT, init_store, NULL);
diff --git a/src/drivers/smmstore/smi.c b/src/drivers/smmstore/smi.c
index b21423e..b90338c 100644
--- a/src/drivers/smmstore/smi.c
+++ b/src/drivers/smmstore/smi.c
@@ -23,8 +23,7 @@
 	return 0;
 }
 
-/* Param is usually EBX, ret in EAX */
-uint32_t smmstore_exec(uint8_t command, void *param)
+static uint32_t smmstorev1_exec(uint8_t command, void *param)
 {
 	uint32_t ret = SMMSTORE_RET_FAILURE;
 
@@ -66,13 +65,89 @@
 			ret = SMMSTORE_RET_SUCCESS;
 		break;
 	}
-
 	default:
 		printk(BIOS_DEBUG,
-			"Unknown SMM store command: 0x%02x\n", command);
+		       "Unknown SMM store v1 command: 0x%02x\n", command);
 		ret = SMMSTORE_RET_UNSUPPORTED;
 		break;
 	}
 
 	return ret;
 }
+
+static uint32_t smmstorev2_exec(uint8_t command, void *param)
+{
+	uint32_t ret = SMMSTORE_RET_FAILURE;
+
+	switch (command) {
+	case SMMSTORE_CMD_INIT: {
+		printk(BIOS_DEBUG, "Init SMM store\n");
+		struct smmstore_params_init *params = param;
+
+		if (range_check(params, sizeof(*params)) != 0)
+			break;
+
+		void *buf = (void *)(uintptr_t)params->com_buffer;
+
+		if (range_check(buf, params->com_buffer_size) != 0)
+			break;
+
+		if (smmstore_init(buf, params->com_buffer_size) == 0)
+			ret = SMMSTORE_RET_SUCCESS;
+		break;
+	}
+	case SMMSTORE_CMD_RAW_READ: {
+		printk(BIOS_DEBUG, "Raw read from SMM store, param = %p\n", param);
+		struct smmstore_params_raw_read *params = param;
+
+		if (range_check(params, sizeof(*params)) != 0)
+			break;
+
+		if (smmstore_rawread_region(params->block_id, params->bufoffset,
+					    params->bufsize) == 0)
+			ret = SMMSTORE_RET_SUCCESS;
+		break;
+	}
+	case SMMSTORE_CMD_RAW_WRITE: {
+		printk(BIOS_DEBUG, "Raw write to SMM store, param = %p\n", param);
+		struct smmstore_params_raw_write *params = param;
+
+		if (range_check(params, sizeof(*params)) != 0)
+			break;
+
+		if (smmstore_rawwrite_region(params->block_id, params->bufoffset,
+					     params->bufsize) == 0)
+			ret = SMMSTORE_RET_SUCCESS;
+		break;
+	}
+	case SMMSTORE_CMD_RAW_CLEAR: {
+		printk(BIOS_DEBUG, "Raw clear SMM store, param = %p\n", param);
+		struct smmstore_params_raw_clear *params = param;
+
+		if (range_check(params, sizeof(*params)) != 0)
+			break;
+
+		if (smmstore_rawclear_region(params->block_id) == 0)
+			ret = SMMSTORE_RET_SUCCESS;
+		break;
+	}
+	default:
+		printk(BIOS_DEBUG,
+			"Unknown SMM store v2 command: 0x%02x\n", command);
+		ret = SMMSTORE_RET_UNSUPPORTED;
+		break;
+	}
+
+	return ret;
+}
+
+uint32_t smmstore_exec(uint8_t command, void *param)
+{
+	if (!param)
+		return SMMSTORE_RET_FAILURE;
+
+	if (CONFIG(SMMSTORE_V2))
+		return smmstorev2_exec(command, param);
+	else
+		return smmstorev1_exec(command, param);
+}
diff --git a/src/drivers/smmstore/store.c b/src/drivers/smmstore/store.c
index 252ea8d..9f9ab01 100644
--- a/src/drivers/smmstore/store.c
+++ b/src/drivers/smmstore/store.c
@@ -262,3 +262,200 @@
 
 	return 0;
 }
+
+
+/* Implementation of Version 2 */
+
+static bool store_initialized;
+static struct mem_region_device mdev_com_buf;
+
+static int smmstore_rdev_chain(struct region_device *rdev)
+{
+	if (!store_initialized)
+		return -1;
+
+	return rdev_chain_full(rdev, &mdev_com_buf.rdev);
+}
+
+/**
+ * Call once before using the store. In SMM this must be called through an
+ * APM SMI handler providing the communication buffer address and length.
+ */
+int smmstore_init(void *buf, size_t len)
+{
+	if (!buf || len < SMM_BLOCK_SIZE)
+		return -1;
+
+	if (store_initialized)
+		return -1;
+
+	mem_region_device_rw_init(&mdev_com_buf, buf, len);
+
+	store_initialized = true;
+
+	return 0;
+}
+
+#if ENV_RAMSTAGE
+/**
+ * Provide metadata for the coreboot tables.
+ * Must only be called in ramstage, but not in SMM.
+ */
+int smmstore_get_info(struct smmstore_params_info *out)
+{
+	struct region_device store;
+
+	if (lookup_store(&store) < 0) {
+		printk(BIOS_ERR, "smm store: lookup of store failed\n");
+		return -1;
+	}
+
+	if (!IS_ALIGNED(region_device_offset(&store), SMM_BLOCK_SIZE)) {
+		printk(BIOS_ERR, "smm store: store not aligned to block size\n");
+		return -1;
+	}
+
+	out->block_size = SMM_BLOCK_SIZE;
+	out->num_blocks = region_device_sz(&store) / SMM_BLOCK_SIZE;
+
+	/* FIXME: Broken EDK2 always assumes memory mapped Firmware Block Volumes */
+	out->mmap_addr = (uintptr_t)rdev_mmap_full(&store);
+
+	printk(BIOS_DEBUG, "smm store: %d # blocks with size 0x%x\n",
+	       out->num_blocks, out->block_size);
+
+	return 0;
+}
+#endif
+
+/* Returns -1 on error, 0 on success */
+static int lookup_block_in_store(struct region_device *store, uint32_t block_id)
+{
+	if (lookup_store(store) < 0) {
+		printk(BIOS_ERR, "smm store: lookup of store failed\n");
+		return -1;
+	}
+
+	if ((block_id * SMM_BLOCK_SIZE) >= region_device_sz(store)) {
+		printk(BIOS_ERR, "smm store: block ID out of range\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* Returns NULL on error, pointer from rdev_mmap on success */
+static void *mmap_com_buf(struct region_device *com_buf, uint32_t offset, uint32_t bufsize)
+{
+	if (smmstore_rdev_chain(com_buf) < 0) {
+		printk(BIOS_ERR, "smm store: lookup of com buffer failed\n");
+		return NULL;
+	}
+
+	if (offset >= region_device_sz(com_buf)) {
+		printk(BIOS_ERR, "smm store: offset out of range\n");
+		return NULL;
+	}
+
+	void *ptr = rdev_mmap(com_buf, offset, bufsize);
+	if (!ptr)
+		printk(BIOS_ERR, "smm store: not enough space for new data\n");
+
+	return ptr;
+}
+
+/**
+ * Reads the specified block of the SMMSTORE and places it in the communication
+ * buffer.
+ * @param block_id The id of the block to operate on
+ * @param offset Offset within the block.
+ *               Must be smaller than the block size.
+ * @param bufsize Size of chunk to read within the block.
+ *                Must be smaller than the block size.
+
+ * @return Returns -1 on error, 0 on success.
+ */
+int smmstore_rawread_region(uint32_t block_id, uint32_t offset, uint32_t bufsize)
+{
+	struct region_device store;
+	struct region_device com_buf;
+
+	if (lookup_block_in_store(&store, block_id) < 0)
+		return -1;
+
+	void *ptr = mmap_com_buf(&com_buf, offset, bufsize);
+	if (!ptr)
+		return -1;
+
+	printk(BIOS_DEBUG, "smm store: reading %p block %d, offset=0x%x, size=%x\n",
+	       ptr, block_id, offset, bufsize);
+
+	ssize_t ret = rdev_readat(&store, ptr, block_id * SMM_BLOCK_SIZE + offset, bufsize);
+	rdev_munmap(&com_buf, ptr);
+	if (ret < 0)
+		return -1;
+
+	return 0;
+}
+
+/**
+ * Writes the specified block of the SMMSTORE by reading it from the communication
+ * buffer.
+ * @param block_id The id of the block to operate on
+ * @param offset Offset within the block.
+ *               Must be smaller than the block size.
+ * @param bufsize Size of chunk to read within the block.
+ *                Must be smaller than the block size.
+
+ * @return Returns -1 on error, 0 on success.
+ */
+int smmstore_rawwrite_region(uint32_t block_id, uint32_t offset, uint32_t bufsize)
+{
+	struct region_device store;
+	struct region_device com_buf;
+
+	if (lookup_block_in_store(&store, block_id) < 0)
+		return -1;
+
+	if (rdev_chain(&store, &store, block_id * SMM_BLOCK_SIZE + offset, bufsize)) {
+		printk(BIOS_ERR, "smm store: not enough space for new data\n");
+		return -1;
+	}
+
+	void *ptr = mmap_com_buf(&com_buf, offset, bufsize);
+	if (!ptr)
+		return -1;
+
+	printk(BIOS_DEBUG, "smm store: writing %p block %d, offset=0x%x, size=%x\n",
+	       ptr, block_id, offset, bufsize);
+
+	ssize_t ret = rdev_writeat(&store, ptr, 0, bufsize);
+	rdev_munmap(&com_buf, ptr);
+	if (ret < 0)
+		return -1;
+
+	return 0;
+}
+
+/**
+ * Erases the specified block of the SMMSTORE. The communication buffer remains untouched.
+ *
+ * @param block_id The id of the block to operate on
+ *
+ * @return Returns -1 on error, 0 on success.
+ */
+int smmstore_rawclear_region(uint32_t block_id)
+{
+	struct region_device store;
+
+	if (lookup_block_in_store(&store, block_id) < 0)
+		return -1;
+
+	ssize_t ret = rdev_eraseat(&store, block_id * SMM_BLOCK_SIZE, SMM_BLOCK_SIZE);
+	if (ret != SMM_BLOCK_SIZE) {
+		printk(BIOS_ERR, "smm store: erasing block failed\n");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/src/include/smmstore.h b/src/include/smmstore.h
index ff0b720..2c37ca3 100644
--- a/src/include/smmstore.h
+++ b/src/include/smmstore.h
@@ -10,10 +10,18 @@
 #define SMMSTORE_RET_FAILURE 1
 #define SMMSTORE_RET_UNSUPPORTED 2
 
+/* Version 1 */
 #define SMMSTORE_CMD_CLEAR 1
 #define SMMSTORE_CMD_READ 2
 #define SMMSTORE_CMD_APPEND 3
 
+/* Version 2 */
+#define SMMSTORE_CMD_INIT 4
+#define SMMSTORE_CMD_RAW_READ 5
+#define SMMSTORE_CMD_RAW_WRITE 6
+#define SMMSTORE_CMD_RAW_CLEAR 7
+
+/* Version 1 */
 struct smmstore_params_read {
 	void *buf;
 	ssize_t bufsize;
@@ -26,12 +34,90 @@
 	size_t valsize;
 };
 
-/* SMM responder */
+/* Version 2 */
+/*
+ * The Version 2 protocol separates the SMMSTORE into 64KiB blocks, each
+ * of which can be read/written/cleared in an independent manner. The
+ * data format isn't specified. See documentation page for more details.
+ */
+
+#define SMM_BLOCK_SIZE (64 * KiB)
+
+/*
+ * Sets the communication buffer to use for read and write operations.
+ */
+struct smmstore_params_init {
+	uint32_t com_buffer;
+	uint32_t com_buffer_size;
+} __packed;
+
+/*
+ * Returns the number of blocks the SMMSTORE supports and their size.
+ * For EDK2 this should be at least two blocks with 64 KiB each.
+ * The mmap_addr is set the memory mapped physical address of the SMMSTORE.
+ */
+struct smmstore_params_info {
+	uint32_t num_blocks;
+	uint32_t block_size;
+	uint32_t mmap_addr;
+} __packed;
+
+/*
+ * Reads a chunk of raw data with size @bufsize from the block specified by
+ * @block_id starting at @bufoffset.
+ * The read data is placed in memory pointed to by @buf.
+ *
+ * @block_id must be less than num_blocks
+ * @bufoffset + @bufsize must be less than block_size
+ */
+struct smmstore_params_raw_write {
+	uint32_t bufsize;
+	uint32_t bufoffset;
+	uint32_t block_id;
+} __packed;
+
+/*
+ * Writes a chunk of raw data with size @bufsize to the block specified by
+ * @block_id starting at @bufoffset.
+ *
+ * @block_id must be less than num_blocks
+ * @bufoffset + @bufsize must be less than block_size
+ */
+struct smmstore_params_raw_read {
+	uint32_t bufsize;
+	uint32_t bufoffset;
+	uint32_t block_id;
+} __packed;
+
+/*
+ * Erases the specified block.
+ *
+ * @block_id must be less than num_blocks
+ */
+struct smmstore_params_raw_clear {
+	uint32_t block_id;
+} __packed;
+
+
+/* SMM handler */
 uint32_t smmstore_exec(uint8_t command, void *param);
 
-/* implementation */
+/* Implementation of Version 1 */
 int smmstore_read_region(void *buf, ssize_t *bufsize);
-int smmstore_append_data(void *key, uint32_t key_sz,
-	void *value, uint32_t value_sz);
+int smmstore_append_data(void *key, uint32_t key_sz, void *value, uint32_t value_sz);
 int smmstore_clear_region(void);
+
+/* Implementation of Version 2 */
+int smmstore_init(void *buf, size_t len);
+int smmstore_rawread_region(uint32_t block_id, uint32_t offset, uint32_t bufsize);
+int smmstore_rawwrite_region(uint32_t block_id, uint32_t offset, uint32_t bufsize);
+int smmstore_rawclear_region(uint32_t block_id);
+#if ENV_RAMSTAGE
+int smmstore_get_info(struct smmstore_params_info *info);
+#endif
+
+/* Advertise SMMSTORE v2 support */
+struct lb_header;
+void lb_smmstorev2(struct lb_header *header);
+
 #endif
diff --git a/src/lib/coreboot_table.c b/src/lib/coreboot_table.c
index 9148405..857f5a5 100644
--- a/src/lib/coreboot_table.c
+++ b/src/lib/coreboot_table.c
@@ -20,6 +20,8 @@
 #include <spi_flash.h>
 #include <security/vboot/misc.h>
 #include <security/vboot/vbnv_layout.h>
+#include <smmstore.h>
+
 #if CONFIG(USE_OPTION_TABLE)
 #include <option_table.h>
 #endif
@@ -548,6 +550,10 @@
 
 	add_cbmem_pointers(head);
 
+	/* SMMSTORE v2 */
+	if (CONFIG(SMMSTORE_V2))
+		lb_smmstorev2(head);
+
 	/* Add board-specific table entries, if any. */
 	lb_board(head);