diff options
author | Patrick Rudolph <patrick.rudolph@9elements.com> | 2020-04-17 16:16:49 +0200 |
---|---|---|
committer | Michał Żygowski <michal.zygowski@3mdeb.com> | 2020-10-22 12:29:47 +0000 |
commit | bc744f5893fc4d53275ed26dd8d968011c6a09c1 (patch) | |
tree | fb283c9d57431020405a5404db04b425428f7709 /src/drivers/smmstore/store.c | |
parent | a693fa06cd32da8239f820d833bb7a1bf55bf351 (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/drivers/smmstore/store.c')
-rw-r--r-- | src/drivers/smmstore/store.c | 197 |
1 files changed, 197 insertions, 0 deletions
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; +} |