aboutsummaryrefslogtreecommitdiff
path: root/src/drivers/smmstore/store.c
diff options
context:
space:
mode:
authorPatrick Rudolph <patrick.rudolph@9elements.com>2020-04-17 16:16:49 +0200
committerMichał Żygowski <michal.zygowski@3mdeb.com>2020-10-22 12:29:47 +0000
commitbc744f5893fc4d53275ed26dd8d968011c6a09c1 (patch)
treefb283c9d57431020405a5404db04b425428f7709 /src/drivers/smmstore/store.c
parenta693fa06cd32da8239f820d833bb7a1bf55bf351 (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.c197
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;
+}