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:
eb1127744a

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>
This commit is contained in:
Patrick Rudolph
2020-04-17 16:16:49 +02:00
committed by Michał Żygowski
parent a693fa06cd
commit bc744f5893
12 changed files with 702 additions and 8 deletions

View File

@@ -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 " }, \

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
ramstage-$(CONFIG_SMMSTORE) += store.c
ramstage-$(CONFIG_SMMSTORE_V2) += ramstage.c
smm-$(CONFIG_SMMSTORE) += store.c smi.c

View File

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

View File

@@ -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 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);
}

View File

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

View File

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

View File

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