From bc744f5893fc4d53275ed26dd8d968011c6a09c1 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Fri, 17 Apr 2020 16:16:49 +0200 Subject: drivers/smmstore: Implement SMMSTORE version 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Christian Walter Reviewed-on: https://review.coreboot.org/c/coreboot/+/40520 Tested-by: build bot (Jenkins) Reviewed-by: Michał Żygowski Reviewed-by: Matt DeVillier --- src/commonlib/include/commonlib/cbmem_id.h | 1 + src/commonlib/include/commonlib/coreboot_tables.h | 17 ++ src/drivers/smmstore/Kconfig | 12 ++ src/drivers/smmstore/Makefile.inc | 1 + src/drivers/smmstore/ramstage.c | 76 +++++++++ src/drivers/smmstore/smi.c | 81 ++++++++- src/drivers/smmstore/store.c | 197 ++++++++++++++++++++++ src/include/smmstore.h | 94 ++++++++++- src/lib/coreboot_table.c | 6 + 9 files changed, 478 insertions(+), 7 deletions(-) create mode 100644 src/drivers/smmstore/ramstage.c (limited to 'src') 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 +#include +#include +#include +#include +#include +#include +#include + +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 #include #include +#include + #if CONFIG(USE_OPTION_TABLE) #include #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); -- cgit v1.2.3