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:
parent
a693fa06cd
commit
bc744f5893
12 changed files with 702 additions and 8 deletions
|
@ -7,3 +7,4 @@ they allow to easily reuse existing code accross platforms.
|
||||||
* [IPMI KCS](ipmi_kcs.md)
|
* [IPMI KCS](ipmi_kcs.md)
|
||||||
* [SMMSTORE](smmstore.md)
|
* [SMMSTORE](smmstore.md)
|
||||||
* [SoundWire](soundwire.md)
|
* [SoundWire](soundwire.md)
|
||||||
|
* [SMMSTOREv2](smmstorev2.md)
|
||||||
|
|
221
Documentation/drivers/smmstorev2.md
Normal file
221
Documentation/drivers/smmstorev2.md
Normal file
|
@ -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
|
|
@ -79,6 +79,7 @@ enum {
|
||||||
CB_TAG_MMC_INFO = 0x0035,
|
CB_TAG_MMC_INFO = 0x0035,
|
||||||
CB_TAG_TCPA_LOG = 0x0036,
|
CB_TAG_TCPA_LOG = 0x0036,
|
||||||
CB_TAG_FMAP = 0x0037,
|
CB_TAG_FMAP = 0x0037,
|
||||||
|
CB_TAG_SMMSTOREV2 = 0x0039,
|
||||||
CB_TAG_CMOS_OPTION_TABLE = 0x00c8,
|
CB_TAG_CMOS_OPTION_TABLE = 0x00c8,
|
||||||
CB_TAG_OPTION = 0x00c9,
|
CB_TAG_OPTION = 0x00c9,
|
||||||
CB_TAG_OPTION_ENUM = 0x00ca,
|
CB_TAG_OPTION_ENUM = 0x00ca,
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
#define CBMEM_ID_ROM3 0x524f4d33
|
#define CBMEM_ID_ROM3 0x524f4d33
|
||||||
#define CBMEM_ID_FMAP 0x464d4150
|
#define CBMEM_ID_FMAP 0x464d4150
|
||||||
#define CBMEM_ID_FSP_LOGO 0x4c4f474f
|
#define CBMEM_ID_FSP_LOGO 0x4c4f474f
|
||||||
|
#define CBMEM_ID_SMM_COMBUFFER 0x53534d32
|
||||||
|
|
||||||
#define CBMEM_ID_TO_NAME_TABLE \
|
#define CBMEM_ID_TO_NAME_TABLE \
|
||||||
{ CBMEM_ID_ACPI, "ACPI " }, \
|
{ CBMEM_ID_ACPI, "ACPI " }, \
|
||||||
|
|
|
@ -80,6 +80,7 @@ enum {
|
||||||
LB_TAG_TCPA_LOG = 0x0036,
|
LB_TAG_TCPA_LOG = 0x0036,
|
||||||
LB_TAG_FMAP = 0x0037,
|
LB_TAG_FMAP = 0x0037,
|
||||||
LB_TAG_PLATFORM_BLOB_VERSION = 0x0038,
|
LB_TAG_PLATFORM_BLOB_VERSION = 0x0038,
|
||||||
|
LB_TAG_SMMSTOREV2 = 0x0039,
|
||||||
LB_TAG_CMOS_OPTION_TABLE = 0x00c8,
|
LB_TAG_CMOS_OPTION_TABLE = 0x00c8,
|
||||||
LB_TAG_OPTION = 0x00c9,
|
LB_TAG_OPTION = 0x00c9,
|
||||||
LB_TAG_OPTION_ENUM = 0x00ca,
|
LB_TAG_OPTION_ENUM = 0x00ca,
|
||||||
|
@ -484,4 +485,20 @@ struct cmos_checksum {
|
||||||
#define CHECKSUM_PCBIOS 1
|
#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
|
#endif
|
||||||
|
|
|
@ -6,6 +6,18 @@ config SMMSTORE
|
||||||
default y if PAYLOAD_TIANOCORE
|
default y if PAYLOAD_TIANOCORE
|
||||||
select SPI_FLASH_SMM if BOOT_DEVICE_SPI_FLASH_RW_NOMMAP
|
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
|
config SMMSTORE_IN_CBFS
|
||||||
bool
|
bool
|
||||||
default n
|
default n
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
ramstage-$(CONFIG_SMMSTORE) += store.c
|
ramstage-$(CONFIG_SMMSTORE) += store.c
|
||||||
|
ramstage-$(CONFIG_SMMSTORE_V2) += ramstage.c
|
||||||
|
|
||||||
smm-$(CONFIG_SMMSTORE) += store.c smi.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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Param is usually EBX, ret in EAX */
|
static uint32_t smmstorev1_exec(uint8_t command, void *param)
|
||||||
uint32_t smmstore_exec(uint8_t command, void *param)
|
|
||||||
{
|
{
|
||||||
uint32_t ret = SMMSTORE_RET_FAILURE;
|
uint32_t ret = SMMSTORE_RET_FAILURE;
|
||||||
|
|
||||||
|
@ -66,13 +65,89 @@ uint32_t smmstore_exec(uint8_t command, void *param)
|
||||||
ret = SMMSTORE_RET_SUCCESS;
|
ret = SMMSTORE_RET_SUCCESS;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printk(BIOS_DEBUG,
|
printk(BIOS_DEBUG,
|
||||||
"Unknown SMM store command: 0x%02x\n", command);
|
"Unknown SMM store v1 command: 0x%02x\n", command);
|
||||||
ret = SMMSTORE_RET_UNSUPPORTED;
|
ret = SMMSTORE_RET_UNSUPPORTED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
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;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -10,10 +10,18 @@
|
||||||
#define SMMSTORE_RET_FAILURE 1
|
#define SMMSTORE_RET_FAILURE 1
|
||||||
#define SMMSTORE_RET_UNSUPPORTED 2
|
#define SMMSTORE_RET_UNSUPPORTED 2
|
||||||
|
|
||||||
|
/* Version 1 */
|
||||||
#define SMMSTORE_CMD_CLEAR 1
|
#define SMMSTORE_CMD_CLEAR 1
|
||||||
#define SMMSTORE_CMD_READ 2
|
#define SMMSTORE_CMD_READ 2
|
||||||
#define SMMSTORE_CMD_APPEND 3
|
#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 {
|
struct smmstore_params_read {
|
||||||
void *buf;
|
void *buf;
|
||||||
ssize_t bufsize;
|
ssize_t bufsize;
|
||||||
|
@ -26,12 +34,90 @@ struct smmstore_params_append {
|
||||||
size_t valsize;
|
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);
|
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_read_region(void *buf, ssize_t *bufsize);
|
||||||
int smmstore_append_data(void *key, uint32_t key_sz,
|
int smmstore_append_data(void *key, uint32_t key_sz, void *value, uint32_t value_sz);
|
||||||
void *value, uint32_t value_sz);
|
|
||||||
int smmstore_clear_region(void);
|
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
|
#endif
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#include <spi_flash.h>
|
#include <spi_flash.h>
|
||||||
#include <security/vboot/misc.h>
|
#include <security/vboot/misc.h>
|
||||||
#include <security/vboot/vbnv_layout.h>
|
#include <security/vboot/vbnv_layout.h>
|
||||||
|
#include <smmstore.h>
|
||||||
|
|
||||||
#if CONFIG(USE_OPTION_TABLE)
|
#if CONFIG(USE_OPTION_TABLE)
|
||||||
#include <option_table.h>
|
#include <option_table.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -548,6 +550,10 @@ static uintptr_t write_coreboot_table(uintptr_t rom_table_end)
|
||||||
|
|
||||||
add_cbmem_pointers(head);
|
add_cbmem_pointers(head);
|
||||||
|
|
||||||
|
/* SMMSTORE v2 */
|
||||||
|
if (CONFIG(SMMSTORE_V2))
|
||||||
|
lb_smmstorev2(head);
|
||||||
|
|
||||||
/* Add board-specific table entries, if any. */
|
/* Add board-specific table entries, if any. */
|
||||||
lb_board(head);
|
lb_board(head);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue