drivers/efi: Add EFI variable store option support

Add a driver to read and write EFI variables stored in a region device.
This is particullary useful for EDK2 as payload and allows to reuse
existing EFI tools to set/get options used by the firmware.

The write implementation is fault tolerant and doesn't corrupt the
variable store. A faulting write might result in using the old value
even though a 'newer' had been completely written.

Implemented basic unit tests for header corruption, writing existing data
and append new data into the store.

Initial firmware region state:
Initially the variable store region isn't formatted. Usually this is done
in the EDK2 payload when no valid firmware volume could be found.
It might be useful to do this offline or in coreboot to have a working
option store on the first boot or when it was corrupted.

Performance improvements:
Right now the code always checks if the firmware volume header is valid.
This could be optimised by caching the test result in heap. For write
operations it would be good to cache the end of the variable store in
the heap as well, instead of walking the whole store.

Reclaiming memory:
The EFI variable store is append write only. To update an existing
variable, first a new is written to the end of the store and then the
previous is marked invalid. This only works on PNOR flash that allow to
clear set bits, but keep cleared bits state.
This mechanisms allows a fault tolerant write, but it also requires to
"clean" the variable store for time to time. This cleaning would remove
variables that have been marked "deleted".
Such cleaning mechanism in turn must be fault tolerant and thus must use
a second parition in the SPI flash as backup/working region.
For now to cleaning is done in coreboot.

Fault checking:
The driver should check if a previous write was successfull and if not mark
variables as deleted on the next operation.

Tested and working:
- Could enumerate all existing variables
- Could read variables
- Could write variables

Change-Id: I8079f71d29da5dc2db956fc68bef1486fe3906bb
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
This commit is contained in:
Patrick Rudolph
2021-04-21 10:02:55 +02:00
committed by Michał Żygowski
parent 1c4a7a5ed0
commit 76fa128731
7 changed files with 931 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
config DRIVERS_EFI_VARIABLE_STORE
bool "Include EFI variable store driver"
help
Adds a driver that is able to read and write an EFI formatted
VariableStore as used by tianocore.
+7
View File
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
all-$(CONFIG_DRIVERS_EFI_VARIABLE_STORE) += efivars.c
smm-$(CONFIG_DRIVERS_EFI_VARIABLE_STORE) += efivars.c
all-$(CONFIG_USE_UEFI_VARIABLE_STORE) += option.c
smm-$(CONFIG_USE_UEFI_VARIABLE_STORE) += option.c
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _EDK2_OPTION_H_
#define _EDK2_OPTION_H_
#include <types.h>
#include <commonlib/region.h>
#include <Uefi/UefiBaseType.h>
/**
* efi_fv_get_option
* Use the provided EFI variable store inside the region device as variable store.
* @rdev: the readable region to operate on
* @guid: the vendor guid to look for
* @name: the variable name to look for. NULL terminated.
* @dest: memory buffer to place the result into
* @size: on input the size of buffer pointed to by dest.
* on output the number of bytes written.
*/
enum cb_err efi_fv_get_option(struct region_device *rdev,
const EFI_GUID *guid,
const char *name,
void *dest,
uint32_t *size);
/**
* efi_fv_set_option
* Use the provided EFI variable store inside the region device as variable store.
* If the variable exists with the same size and contents, nothing will be written
* to the region device.
* @rdev: the read/writable region to operate on
* @guid: the vendor guid to write
* @name: the variable name to write. NULL terminated.
* @data: memory buffer where to read data from
* @size: the size of buffer pointed to by data
*/
enum cb_err efi_fv_set_option(struct region_device *rdev,
const EFI_GUID *guid,
const char *name,
void *data,
uint32_t size);
enum cb_err efi_fv_print_options(struct region_device *rdev);
#endif /* _EDK2_OPTION_H_ */
+3 -3
View File
@@ -21,9 +21,9 @@ stages += ramstage rmodule postcar libagesa
alltests :=
subdirs := tests/arch tests/acpi tests/commonlib tests/console tests/cpu
subdirs += tests/device tests/drivers tests/ec tests/lib tests/mainboard
subdirs += tests/northbridge tests/security tests/soc tests/southbridge
subdirs += tests/superio tests/vendorcode
subdirs += tests/device tests/drivers tests/ec tests/lib
subdirs += tests/mainboard tests/northbridge tests/security tests/soc
subdirs += tests/southbridge tests/superio tests/vendorcode
define tests-handler
alltests += $(1)$(2)
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
tests-y += efivars-test
efivars-test-srcs += tests/drivers/efivars.c
efivars-test-srcs += src/drivers/efi/efivars.c
efivars-test-srcs += tests/stubs/console.c
efivars-test-srcs += src/commonlib/region.c
efivars-test-cflags += -I src/vendorcode/intel/edk2/UDK2017/MdePkg/Include/
efivars-test-cflags += -I src/vendorcode/intel/edk2/UDK2017/MdePkg/Include/Ia32/
efivars-test-cflags += -I src/vendorcode/intel/edk2/UDK2017/MdePkg/Include/Pi/
efivars-test-cflags += -I src/vendorcode/intel/edk2/UDK2017/MdeModulePkg/Include/
+201
View File
@@ -0,0 +1,201 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <drivers/efi/efivars.h>
#include <limits.h>
#include <Pi/PiFirmwareVolume.h>
#include <Guid/VariableFormat.h>
#include <string.h>
#include <tests/test.h>
#include <types.h>
/* Dummy firmware volume header for a 0x30000 byte partition with a single entry
* in a formatted variable store.
*/
static const uint8_t FVH[] = {
/* EFI_FIRMWARE_VOLUME_HEADER */
/* UINT8 ZeroVector[16] */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
/* EFI_GUID FileSystemGuid */
0x8d, 0x2b, 0xf1, 0xff, 0x96, 0x76, 0x8b, 0x4c, 0xa9, 0x85, 0x27, 0x47, 0x07, 0x5b,
0x4f, 0x50,
/* UINT64 FvLength */
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
/* UINT32 Signature */
0x5f, 0x46, 0x56, 0x48,
/* EFI_FVB_ATTRIBUTES_2 Attributes */
0x36, 0x0e, 0x00, 0x00,
/* UINT16 HeaderLength */
0x48, 0x00,
/* UINT16 Checksum */
0x00, 0xfa,
/* UINT16 ExtHeaderOffset */
0x00, 0x00,
/* UINT8 Reserved[1] */
0x00,
/* UINT8 Revision */
0x02,
/* EFI_FV_BLOCK_MAP_ENTRY BlockMap[2] */
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Variable Info Header */
/* EFI_GUID Signature */
0x78, 0x2c, 0xf3, 0xaa, 0x7b, 0x94, 0x9a, 0x43, 0xa1, 0x80, 0x2e, 0x14, 0x4e, 0xc3,
0x77, 0x92,
/* UINT32 Size */
0xb8, 0xff, 0x00, 0x00,
/* UINT8 Format */
0x5a,
/* UINT8 State */
0xfe,
/* UINT16 Reserved */
0x00, 0x00,
/* UINT32 Reserved1 */
0x00, 0x00, 0x00, 0x00,
/* AUTHENTICATED_VARIABLE_HEADER */
/* UINT16 StartId */
0xaa, 0x55,
/* UINT8 State */
0x3f,
/* UINT8 Reserved */
0xff,
/* UINT32 Attributes */
0x07, 0x00, 0x00, 0x00,
/* UINT64 MonotonicCount */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
/* EFI_TIME TimeStamp */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
/* UINT32 PubKeyIndex */
0xff, 0xff, 0xff, 0xff,
/* UINT32 NameSize */
0x12, 0x00, 0x00, 0x00,
/* UINT32 DataSize */
0x09, 0x00, 0x00, 0x00,
/* EFI_GUID VendorGuid */
0x1d, 0x4c, 0xae, 0xce, 0x5b, 0x33, 0x85, 0x46, 0xa4, 0xa0, 0xfc, 0x4a,
0x94, 0xee, 0xa0, 0x85,
/* L"coreboot" */
0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x62, 0x00,
0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x00, 0x00,
/* "is great" */
0x69, 0x73, 0x20, 0x67, 0x72, 0x65, 0x61, 0x74, 0x00,
};
#define FVH_CHECKSUMMED_SIZE (sizeof(EFI_FIRMWARE_VOLUME_HEADER) + 8 + sizeof(EFI_GUID))
static struct region_device flash_rdev_rw;
static uint8_t flash_buffer[0x30000];
static const char *name = "coreboot";
static void mock_rdev(bool init)
{
if (init) {
/* Emulate NOR flash by setting all bits to 1 */
memset(flash_buffer, 0xff, sizeof(flash_buffer));
/* Place _FVH and VIH headers, as well as test data */
memcpy(flash_buffer, FVH, sizeof(FVH));
}
rdev_chain_mem_rw(&flash_rdev_rw, flash_buffer, sizeof(flash_buffer));
}
static const EFI_GUID EficorebootNvDataGuid = {
0xceae4c1d, 0x335b, 0x4685, { 0xa4, 0xa0, 0xfc, 0x4a, 0x94, 0xee, 0xa0, 0x85 } };
/* Test valid and corrupted FVH header */
static void efi_test_header(void **state)
{
enum cb_err ret;
uint8_t buf[16];
uint32_t size;
int i;
mock_rdev(true);
/* Test variable lookup with intact header */
size = sizeof(buf);
ret = efi_fv_get_option(&flash_rdev_rw, &EficorebootNvDataGuid, name, buf, &size);
assert_int_equal(ret, CB_SUCCESS);
assert_int_equal(size, strlen("is great")+1);
assert_string_equal(buf, "is great");
for (i = 0; i < FVH_CHECKSUMMED_SIZE; i++) {
mock_rdev(true);
/* Flip some bits */
flash_buffer[i] ^= 0xff;
size = sizeof(buf);
ret = efi_fv_get_option(&flash_rdev_rw, &EficorebootNvDataGuid, name, buf,
&size);
assert_int_not_equal(ret, CB_SUCCESS);
}
}
/* Write with the same key and value should not modify the store */
static void efi_test_noop_existing_write(void **state)
{
enum cb_err ret;
int i;
mock_rdev(true);
ret = efi_fv_set_option(&flash_rdev_rw,
&EficorebootNvDataGuid,
name,
"is great",
strlen("is great") + 1);
assert_int_equal(ret, CB_SUCCESS);
for (i = sizeof(FVH); i < sizeof(flash_buffer); i++)
assert_int_equal(flash_buffer[i], 0xff);
}
static void efi_test_new_write(void **state)
{
enum cb_err ret;
uint8_t buf[16];
uint32_t size;
int i;
mock_rdev(true);
ret = efi_fv_set_option(&flash_rdev_rw, &EficorebootNvDataGuid,
name, "is awesome", strlen("is awesome") + 1);
assert_int_equal(ret, CB_SUCCESS);
/* New variable has been written */
assert_int_equal(flash_buffer[ALIGN_UP(sizeof(FVH), 4)], 0xaa);
assert_int_equal(flash_buffer[ALIGN_UP(sizeof(FVH), 4) + 1], 0x55);
/* Remaining space is blank */
for (i = ALIGN_UP(sizeof(FVH), 4) + 89; i < sizeof(flash_buffer); i++)
assert_int_equal(flash_buffer[i], 0xff);
mock_rdev(false);
memset(buf, 0, sizeof(buf));
size = sizeof(buf);
ret = efi_fv_get_option(&flash_rdev_rw, &EficorebootNvDataGuid, name, buf,
&size);
assert_int_equal(ret, CB_SUCCESS);
assert_int_equal(size, strlen("is awesome")+1);
assert_int_equal(flash_buffer[ALIGN_UP(sizeof(FVH), 4) + 1], 0x55);
assert_string_equal(buf, "is awesome");
}
int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(efi_test_header),
cmocka_unit_test(efi_test_noop_existing_write),
cmocka_unit_test(efi_test_new_write)
};
return cb_run_group_tests(tests, NULL, NULL);
}