summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPatrick Rudolph <patrick.rudolph@9elements.com>2020-04-17 16:16:49 +0200
committerMichał Żygowski <michal.zygowski@3mdeb.com>2020-10-22 12:29:47 +0000
commitbc744f5893fc4d53275ed26dd8d968011c6a09c1 (patch)
treefb283c9d57431020405a5404db04b425428f7709 /src
parenta693fa06cd32da8239f820d833bb7a1bf55bf351 (diff)
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>
Diffstat (limited to 'src')
-rw-r--r--src/commonlib/include/commonlib/cbmem_id.h1
-rw-r--r--src/commonlib/include/commonlib/coreboot_tables.h17
-rw-r--r--src/drivers/smmstore/Kconfig12
-rw-r--r--src/drivers/smmstore/Makefile.inc1
-rw-r--r--src/drivers/smmstore/ramstage.c76
-rw-r--r--src/drivers/smmstore/smi.c81
-rw-r--r--src/drivers/smmstore/store.c197
-rw-r--r--src/include/smmstore.h94
-rw-r--r--src/lib/coreboot_table.c6
9 files changed, 478 insertions, 7 deletions
diff --git a/src/commonlib/include/commonlib/cbmem_id.h b/src/commonlib/include/commonlib/cbmem_id.h
index ac271a06bc..6b4d60469e 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 6393c01e6e..44060025b3 100644
--- a/src/commonlib/include/commonlib/coreboot_tables.h
+++ b/src/commonlib/include/commonlib/coreboot_tables.h
@@ -80,6 +80,7 @@ enum {
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 @@ struct cmos_checksum {
#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 7ee8676014..ba8268e378 100644
--- a/src/drivers/smmstore/Kconfig
+++ b/src/drivers/smmstore/Kconfig
@@ -6,6 +6,18 @@ config SMMSTORE
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 1cafe3a3cf..90bcdece9d 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 0000000000..ef80e221bc
--- /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 b21423e90e..b90338c619 100644
--- a/src/drivers/smmstore/smi.c
+++ b/src/drivers/smmstore/smi.c
@@ -23,8 +23,7 @@ static int range_check(void *start, size_t size)
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 @@ uint32_t smmstore_exec(uint8_t command, void *param)
ret = SMMSTORE_RET_SUCCESS;
break;
}
+ default:
+ printk(BIOS_DEBUG,
+ "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 command: 0x%02x\n", command);
+ "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 252ea8d47b..9f9ab0199d 100644
--- a/src/drivers/smmstore/store.c
+++ b/src/drivers/smmstore/store.c
@@ -262,3 +262,200 @@ int smmstore_clear_region(void)
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 ff0b72001a..2c37ca39b9 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 @@ struct smmstore_params_append {
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 9148405879..857f5a52c3 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 @@ static uintptr_t write_coreboot_table(uintptr_t rom_table_end)
add_cbmem_pointers(head);
+ /* SMMSTORE v2 */
+ if (CONFIG(SMMSTORE_V2))
+ lb_smmstorev2(head);
+
/* Add board-specific table entries, if any. */
lb_board(head);