From bc744f5893fc4d53275ed26dd8d968011c6a09c1 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Fri, 17 Apr 2020 16:16:49 +0200 Subject: [PATCH] drivers/smmstore: Implement SMMSTORE version 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Christian Walter Reviewed-on: https://review.coreboot.org/c/coreboot/+/40520 Tested-by: build bot (Jenkins) Reviewed-by: Michał Żygowski Reviewed-by: Matt DeVillier --- Documentation/drivers/index.md | 1 + Documentation/drivers/smmstorev2.md | 221 ++++++++++++++++++ payloads/libpayload/include/coreboot_tables.h | 1 + src/commonlib/include/commonlib/cbmem_id.h | 1 + .../include/commonlib/coreboot_tables.h | 17 ++ src/drivers/smmstore/Kconfig | 12 + src/drivers/smmstore/Makefile.inc | 1 + src/drivers/smmstore/ramstage.c | 76 ++++++ src/drivers/smmstore/smi.c | 83 ++++++- src/drivers/smmstore/store.c | 197 ++++++++++++++++ src/include/smmstore.h | 94 +++++++- src/lib/coreboot_table.c | 6 + 12 files changed, 702 insertions(+), 8 deletions(-) create mode 100644 Documentation/drivers/smmstorev2.md create mode 100644 src/drivers/smmstore/ramstage.c diff --git a/Documentation/drivers/index.md b/Documentation/drivers/index.md index e215c6ab11..508beaf40a 100644 --- a/Documentation/drivers/index.md +++ b/Documentation/drivers/index.md @@ -7,3 +7,4 @@ they allow to easily reuse existing code accross platforms. * [IPMI KCS](ipmi_kcs.md) * [SMMSTORE](smmstore.md) * [SoundWire](soundwire.md) +* [SMMSTOREv2](smmstorev2.md) diff --git a/Documentation/drivers/smmstorev2.md b/Documentation/drivers/smmstorev2.md new file mode 100644 index 0000000000..cb79b8b6b8 --- /dev/null +++ b/Documentation/drivers/smmstorev2.md @@ -0,0 +1,221 @@ +# SMM based flash storage driver Version 2 + +This documents the API exposed by the x86 system management based +storage driver. + +## SMMSTOREv2 + +SMMSTOREv2 is a [SMM] mediated driver to read from, write to and erase +a predefined region in flash. It can be enabled by setting +`CONFIG_SMMSTORE=y` and `CONFIG_SMMSTORE_V2=y` in menuconfig. + +This can be used by the OS or the payload to implement persistent +storage to hold for instance configuration data, without needing to +implement a (platform specific) storage driver in the payload itself. + +### Storage size and alignment + +SMMSTORE version 2 requires a minimum alignment of 64 KiB, which should +be supported by all flash chips. Not having to perform read-modify-write +operations is desired, as it reduces complexity and potential for bugs. + +This can be used by a FTW (FaultTolerantWrite) implementation that uses +at least two regions in an A/B update scheme. The FTW implementation in +EDK2 uses three different regions in the store: + +- The variable store +- The FTW spare block +- The FTW working block + +All regions must be block-aligned, and the FTW spare size must be larger +than that of the variable store. FTW working block can be much smaller. +With 64 KiB as block size, the minimum size of the FTW-enabled store is: + +- The variable store: 1 block = 64 KiB +- The FTW spare block: 2 blocks = 2 * 64 KiB +- The FTW working block: 1 block = 64 KiB + +Therefore, the minimum size for EDK2 FTW is 4 blocks, or 256 KiB. + +## API + +The API provides read and write access to an unformatted block storage. + +### Storage region + +By default SMMSTOREv2 will operate on a separate FMAP region called +`SMMSTORE`. The default generated FMAP will include such a region. On +systems with a locked FMAP, e.g. in an existing vboot setup with a +locked RO region, the option exists to add a cbfsfile called `smm_store` +in the `RW_LEGACY` (if CHROMEOS) or in the `COREBOOT` FMAP regions. It +is recommended for new builds using a handcrafted FMD that intend to +make use of SMMSTORE to include a sufficiently large `SMMSTORE` FMAP +region. It is mandatory to align the `SMMSTORE` region to 64KiB for +compatibility with the largest flash erase operation. + +When a default generated FMAP is used, the size of the FMAP region is +equal to `CONFIG_SMMSTORE_SIZE`. UEFI payloads expect at least 64 KiB. +To support a fault tolerant write mechanism, at least a multiple of +this size is recommended. + +### Communication buffer + +To prevent malicious ring0 code to access arbitrary memory locations, +SMMSTOREv2 uses a communication buffer in CBMEM/HOB for all transfers. +This buffer has to be at least 64 KiB in size and must be installed +before calling any of the SMMSTORE read or write operations. Usually, +coreboot will install this buffer to transfer data between ring0 and +the [SMM] handler. + +In order to get the communication buffer address, the payload or OS +has to read the coreboot table with tag `0x0039`, containing: + +```C +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 byte */ + uint8_t apm_cmd; /* The command byte to write to the APM I/O port */ + uint8_t unused[3]; /* Set to zero */ +}; +``` + +The absence of this coreboot table entry indicates that there's no +SMMSTOREv2 support. + +### Blocks + +The SMMSTOREv2 splits the SMMSTORE FMAP partition into smaller chunks +called *blocks*. Every block is at least the size of 64KiB to support +arbitrary NOR flash erase ops. A payload or OS must make no further +assumptions about the block or communication buffer size. + +### Generating the SMI + +SMMSTOREv2 is called via an SMI, which is generated via a write to the +IO port defined in the smi_cmd entry of the FADT ACPI table. `%al` +contains `APM_CNT_SMMSTORE=0xed` and is written to the smi_cmd IO +port. `%ah` contains the SMMSTOREv2 command. `%ebx` contains the +parameter buffer to the SMMSTOREv2 command. + +### Return values + +If a command succeeds, SMMSTOREv2 will return with +`SMMSTORE_RET_SUCCESS=0` in `%eax`. On failure SMMSTORE will return +`SMMSTORE_RET_FAILURE=1`. For unsupported SMMSTORE commands +`SMMSTORE_REG_UNSUPPORTED=2` is returned. + +**NOTE 1**: The caller **must** check the return value and should make +no assumption on the returned data if `%eax` does not contain +`SMMSTORE_RET_SUCCESS`. + +**NOTE 2**: If the SMI returns without changing `%ax`, it can be assumed +that the SMMSTOREv2 feature is not installed. + +### Calling arguments + +SMMSTOREv2 supports 3 subcommands that are passed via `%ah`, the +additional calling arguments are passed via `%ebx`. + +**NOTE**: The size of the struct entries are in the native word size of +smihandler. This means 32 bits in almost all cases. + +#### - SMMSTORE_CMD_INIT = 4 + +This installs the communication buffer to use and thus enables the +SMMSTORE handler. This command can only be executed once and is done +by the firmware. Calling this function at runtime has no effect. + +The additional parameter buffer `%ebx` contains a pointer to the +following struct: + +```C +struct smmstore_params_init { + uint32_t com_buffer; + uint32_t com_buffer_size; +} __packed; +``` + +INPUT: +- `com_buffer`: Physical address of the communication buffer (CBMEM) +- `com_buffer_size`: Size in bytes of the communication buffer + +#### - SMMSTORE_CMD_RAW_READ = 5 + +SMMSTOREv2 allows reading arbitrary data. It is up to the caller to +initialize the store with meaningful data before using it. + +The additional parameter buffer `%ebx` contains a pointer to the +following struct: + +```C +struct smmstore_params_raw_read { + uint32_t bufsize; + uint32_t bufoffset; + uint32_t block_id; +} __packed; +``` + +INPUT: +- `bufsize`: Size of data to read within the communication buffer +- `bufoffset`: Offset within the communication buffer +- `block_id`: Block to read from + +#### - SMMSTORE_CMD_RAW_WRITE = 6 + +SMMSTOREv2 allows writing arbitrary data. It is up to the caller to +erase a block before writing it. + +The additional parameter buffer `%ebx` contains a pointer to +the following struct: + +```C +struct smmstore_params_raw_write { + uint32_t bufsize; + uint32_t bufoffset; + uint32_t block_id; +} __packed; +``` + +INPUT: +- `bufsize`: Size of data to write within the communication buffer +- `bufoffset`: Offset within the communication buffer +- `block_id`: Block to write to + +#### - SMMSTORE_CMD_RAW_CLEAR = 7 + +SMMSTOREv2 allows clearing blocks. A cleared block will read as `0xff`. +By providing multiple blocks the caller can implement a fault tolerant +write mechanism. It is up to the caller to clear blocks before writing +to them. + + +```C +struct smmstore_params_raw_clear { + uint32_t block_id; +} __packed; +``` + +INPUT: +- `block_id`: Block to erase + +#### Security + +Pointers provided by the payload or OS are checked to not overlap with +SMM. This protects the SMM handler from being compromised. + +As all information is exchanged using the communication buffer and +coreboot tables, there's no risk that a malicious application capable +of issuing SMIs could extract arbitrary data or modify the currently +running kernel. + +## External links + +* [A Tour Beyond BIOS Implementing UEFI Authenticated Variables in SMM with EDKI](https://software.intel.com/sites/default/files/managed/cf/ea/a_tour_beyond_bios_implementing_uefi_authenticated_variables_in_smm_with_edkii.pdf) +Note that this differs significantly from coreboot's implementation. + +[SMM]: ../security/smm.md diff --git a/payloads/libpayload/include/coreboot_tables.h b/payloads/libpayload/include/coreboot_tables.h index bf8c0d9303..bfdd21e692 100644 --- a/payloads/libpayload/include/coreboot_tables.h +++ b/payloads/libpayload/include/coreboot_tables.h @@ -79,6 +79,7 @@ enum { CB_TAG_MMC_INFO = 0x0035, CB_TAG_TCPA_LOG = 0x0036, CB_TAG_FMAP = 0x0037, + CB_TAG_SMMSTOREV2 = 0x0039, CB_TAG_CMOS_OPTION_TABLE = 0x00c8, CB_TAG_OPTION = 0x00c9, CB_TAG_OPTION_ENUM = 0x00ca, diff --git a/src/commonlib/include/commonlib/cbmem_id.h b/src/commonlib/include/commonlib/cbmem_id.h index ac271a06bc..6b4d60469e 100644 --- a/src/commonlib/include/commonlib/cbmem_id.h +++ b/src/commonlib/include/commonlib/cbmem_id.h @@ -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 " }, \ diff --git a/src/commonlib/include/commonlib/coreboot_tables.h b/src/commonlib/include/commonlib/coreboot_tables.h index 6393c01e6e..44060025b3 100644 --- a/src/commonlib/include/commonlib/coreboot_tables.h +++ b/src/commonlib/include/commonlib/coreboot_tables.h @@ -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 diff --git a/src/drivers/smmstore/Kconfig b/src/drivers/smmstore/Kconfig index 7ee8676014..ba8268e378 100644 --- a/src/drivers/smmstore/Kconfig +++ b/src/drivers/smmstore/Kconfig @@ -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 diff --git a/src/drivers/smmstore/Makefile.inc b/src/drivers/smmstore/Makefile.inc index 1cafe3a3cf..90bcdece9d 100644 --- a/src/drivers/smmstore/Makefile.inc +++ b/src/drivers/smmstore/Makefile.inc @@ -1,3 +1,4 @@ ramstage-$(CONFIG_SMMSTORE) += store.c +ramstage-$(CONFIG_SMMSTORE_V2) += ramstage.c smm-$(CONFIG_SMMSTORE) += store.c smi.c diff --git a/src/drivers/smmstore/ramstage.c b/src/drivers/smmstore/ramstage.c new file mode 100644 index 0000000000..ef80e221bc --- /dev/null +++ b/src/drivers/smmstore/ramstage.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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); diff --git a/src/drivers/smmstore/smi.c b/src/drivers/smmstore/smi.c index b21423e90e..b90338c619 100644 --- a/src/drivers/smmstore/smi.c +++ b/src/drivers/smmstore/smi.c @@ -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); +} 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; +} diff --git a/src/include/smmstore.h b/src/include/smmstore.h index ff0b72001a..2c37ca39b9 100644 --- a/src/include/smmstore.h +++ b/src/include/smmstore.h @@ -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 diff --git a/src/lib/coreboot_table.c b/src/lib/coreboot_table.c index 9148405879..857f5a52c3 100644 --- a/src/lib/coreboot_table.c +++ b/src/lib/coreboot_table.c @@ -20,6 +20,8 @@ #include #include #include +#include + #if CONFIG(USE_OPTION_TABLE) #include #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);