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:
committed by
Michał Żygowski
parent
a693fa06cd
commit
bc744f5893
@@ -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
|
||||
|
@@ -1,3 +1,4 @@
|
||||
ramstage-$(CONFIG_SMMSTORE) += store.c
|
||||
ramstage-$(CONFIG_SMMSTORE_V2) += ramstage.c
|
||||
|
||||
smm-$(CONFIG_SMMSTORE) += store.c smi.c
|
||||
|
76
src/drivers/smmstore/ramstage.c
Normal file
76
src/drivers/smmstore/ramstage.c
Normal 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);
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user