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

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