efi_selftest: provide an EFI selftest application
A testing framework for the EFI API is provided.
It can be executed with the 'bootefi selftest' command.
It is coded in a way that at a later stage we may turn it
into a standalone EFI application. The current build system
does not allow this yet.
All tests use a driver model and are run in three phases:
setup, execute, teardown.
A test may be setup and executed at boottime,
it may be setup at boottime and executed at runtime,
or it may be setup and executed at runtime.
After executing all tests the system is reset.
Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
Signed-off-by: Alexander Graf <agraf@suse.de>
diff --git a/MAINTAINERS b/MAINTAINERS
index 04acf2b..0b7b2bb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -259,8 +259,9 @@
M: Alexander Graf <agraf@suse.de>
S: Maintained
T: git git://github.com/agraf/u-boot.git
-F: include/efi_loader.h
-F: lib/efi_loader/
+F: include/efi*
+F: lib/efi*
+F: test/py/tests/test_efi*
F: cmd/bootefi.c
FLATTENED DEVICE TREE
diff --git a/cmd/Kconfig b/cmd/Kconfig
index d6d130e..3ef9b16 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -222,6 +222,8 @@
for testing that EFI is working at a basic level, and for bringing
up EFI support on a new architecture.
+source lib/efi_selftest/Kconfig
+
config CMD_BOOTMENU
bool "bootmenu"
select MENU
diff --git a/cmd/bootefi.c b/cmd/bootefi.c
index ffd50ba..788f869 100644
--- a/cmd/bootefi.c
+++ b/cmd/bootefi.c
@@ -285,7 +285,6 @@
return efi_do_enter(&loaded_image_info, &systab, entry);
}
-
/* Interpreter command to boot an arbitrary EFI image from memory */
static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
@@ -307,6 +306,22 @@
memcpy((char *)addr, __efi_helloworld_begin, size);
} else
#endif
+#ifdef CONFIG_CMD_BOOTEFI_SELFTEST
+ if (!strcmp(argv[1], "selftest")) {
+ /*
+ * gd lives in a fixed register which may get clobbered while we
+ * execute the payload. So save it here and restore it on every
+ * callback entry
+ */
+ efi_save_gd();
+ /* Initialize and populate EFI object list */
+ if (!efi_obj_list_initalized)
+ efi_init_obj_list();
+ loaded_image_info.device_handle = bootefi_device_path;
+ loaded_image_info.file_path = bootefi_image_path;
+ return efi_selftest(&loaded_image_info, &systab);
+ } else
+#endif
{
saddr = argv[1];
@@ -336,8 +351,12 @@
" If specified, the device tree located at <fdt address> gets\n"
" exposed as EFI configuration table.\n"
#ifdef CONFIG_CMD_BOOTEFI_HELLO
- "hello\n"
- " - boot a sample Hello World application stored within U-Boot"
+ "bootefi hello\n"
+ " - boot a sample Hello World application stored within U-Boot\n"
+#endif
+#ifdef CONFIG_CMD_BOOTEFI_SELFTEST
+ "bootefi selftest\n"
+ " - boot an EFI selftest application stored within U-Boot\n"
#endif
;
#endif
diff --git a/include/efi_loader.h b/include/efi_loader.h
index f271925..f74b33d 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -254,6 +254,15 @@
struct efi_time_cap *capabilities);
void efi_get_time_init(void);
+#ifdef CONFIG_CMD_BOOTEFI_SELFTEST
+/*
+ * Entry point for the tests of the EFI API.
+ * It is called by 'bootefi selftest'
+ */
+efi_status_t EFIAPI efi_selftest(efi_handle_t image_handle,
+ struct efi_system_table *systab);
+#endif
+
#else /* defined(EFI_LOADER) && !defined(CONFIG_SPL_BUILD) */
/* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */
diff --git a/include/efi_selftest.h b/include/efi_selftest.h
new file mode 100644
index 0000000..76304a2
--- /dev/null
+++ b/include/efi_selftest.h
@@ -0,0 +1,91 @@
+/*
+ * EFI application loader
+ *
+ * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef _EFI_SELFTEST_H
+#define _EFI_SELFTEST_H
+
+#include <common.h>
+#include <efi.h>
+#include <efi_api.h>
+#include <linker_lists.h>
+
+/*
+ * Prints an error message.
+ *
+ * @... format string followed by fields to print
+ */
+#define efi_st_error(...) \
+ efi_st_printf("%s(%u):\nERROR: ", __FILE__, __LINE__); \
+ efi_st_printf(__VA_ARGS__) \
+
+/*
+ * A test may be setup and executed at boottime,
+ * it may be setup at boottime and executed at runtime,
+ * or it may be setup and executed at runtime.
+ */
+enum efi_test_phase {
+ EFI_EXECUTE_BEFORE_BOOTTIME_EXIT = 1,
+ EFI_SETUP_BEFORE_BOOTTIME_EXIT,
+ EFI_SETUP_AFTER_BOOTTIME_EXIT,
+};
+
+extern struct efi_simple_text_output_protocol *con_out;
+extern struct efi_simple_input_interface *con_in;
+
+/*
+ * Exit the boot services.
+ *
+ * The size of the memory map is determined.
+ * Pool memory is allocated to copy the memory map.
+ * The memory amp is copied and the map key is obtained.
+ * The map key is used to exit the boot services.
+ */
+void efi_st_exit_boot_services(void);
+
+/*
+ * Print a pointer to an u16 string
+ *
+ * @pointer: pointer
+ * @buf: pointer to buffer address
+ * on return position of terminating zero word
+ */
+void efi_st_printf(const char *fmt, ...)
+ __attribute__ ((format (__printf__, 1, 2)));
+
+/*
+ * Reads an Unicode character from the input device.
+ *
+ * @return: Unicode character
+ */
+u16 efi_st_get_key(void);
+
+/**
+ * struct efi_unit_test - EFI unit test
+ *
+ * An efi_unit_test provides a interface to an EFI unit test.
+ *
+ * @name: name of unit test
+ * @phase: specifies when setup and execute are executed
+ * @setup: set up the unit test
+ * @teardown: tear down the unit test
+ * @execute: execute the unit test
+ */
+struct efi_unit_test {
+ const char *name;
+ const enum efi_test_phase phase;
+ int (*setup)(const efi_handle_t handle,
+ const struct efi_system_table *systable);
+ int (*execute)(void);
+ int (*teardown)(void);
+};
+
+/* Declare a new EFI unit test */
+#define EFI_UNIT_TEST(__name) \
+ ll_entry_declare(struct efi_unit_test, __name, efi_unit_test)
+
+#endif /* _EFI_SELFTEST_H */
diff --git a/lib/Makefile b/lib/Makefile
index faf4538..8e1c9d1 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -9,6 +9,7 @@
obj-$(CONFIG_EFI) += efi/
obj-$(CONFIG_EFI_LOADER) += efi_loader/
+obj-$(CONFIG_EFI_LOADER) += efi_selftest/
obj-$(CONFIG_LZMA) += lzma/
obj-$(CONFIG_LZO) += lzo/
obj-$(CONFIG_BZIP2) += bzip2/
diff --git a/lib/efi_selftest/Kconfig b/lib/efi_selftest/Kconfig
new file mode 100644
index 0000000..3b5f3a1
--- /dev/null
+++ b/lib/efi_selftest/Kconfig
@@ -0,0 +1,7 @@
+config CMD_BOOTEFI_SELFTEST
+ bool "Allow booting an EFI efi_selftest"
+ depends on CMD_BOOTEFI
+ help
+ This adds an EFI test application to U-Boot that can be executed
+ with the 'bootefi selftest' command. It provides extended tests of
+ the EFI API implementation.
diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile
new file mode 100644
index 0000000..34f5ff1
--- /dev/null
+++ b/lib/efi_selftest/Makefile
@@ -0,0 +1,17 @@
+:
+# (C) Copyright 2017, Heinrich Schuchardt <xypron.glpk@gmx.de>
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
+
+# This file only gets included with CONFIG_EFI_LOADER set, so all
+# object inclusion implicitly depends on it
+
+CFLAGS_efi_selftest.o := $(CFLAGS_EFI)
+CFLAGS_REMOVE_efi_selftest.o := $(CFLAGS_NON_EFI)
+CFLAGS_efi_selftest_console.o := $(CFLAGS_EFI)
+CFLAGS_REMOVE_efi_selftest_console.o := $(CFLAGS_NON_EFI)
+
+obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \
+efi_selftest.o \
+efi_selftest_console.o
diff --git a/lib/efi_selftest/efi_selftest.c b/lib/efi_selftest/efi_selftest.c
new file mode 100644
index 0000000..efec832
--- /dev/null
+++ b/lib/efi_selftest/efi_selftest.c
@@ -0,0 +1,219 @@
+/*
+ * EFI efi_selftest
+ *
+ * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <efi_selftest.h>
+#include <vsprintf.h>
+
+static const struct efi_system_table *systable;
+static const struct efi_boot_services *boottime;
+static const struct efi_runtime_services *runtime;
+static efi_handle_t handle;
+static u16 reset_message[] = L"Selftest completed";
+
+/*
+ * Exit the boot services.
+ *
+ * The size of the memory map is determined.
+ * Pool memory is allocated to copy the memory map.
+ * The memory amp is copied and the map key is obtained.
+ * The map key is used to exit the boot services.
+ */
+void efi_st_exit_boot_services(void)
+{
+ unsigned long map_size = 0;
+ unsigned long map_key;
+ unsigned long desc_size;
+ u32 desc_version;
+ efi_status_t ret;
+ struct efi_mem_desc *memory_map;
+
+ ret = boottime->get_memory_map(&map_size, NULL, &map_key, &desc_size,
+ &desc_version);
+ if (ret != EFI_BUFFER_TOO_SMALL) {
+ efi_st_printf("ERROR: GetMemoryMap did not return "
+ "EFI_BUFFER_TOO_SMALL\n");
+ return;
+ }
+ /* Allocate extra space for newly allocated memory */
+ map_size += sizeof(struct efi_mem_desc);
+ ret = boottime->allocate_pool(EFI_BOOT_SERVICES_DATA, map_size,
+ (void **)&memory_map);
+ if (ret != EFI_SUCCESS) {
+ efi_st_printf("ERROR: AllocatePool did not return "
+ "EFI_SUCCESS\n");
+ return;
+ }
+ ret = boottime->get_memory_map(&map_size, memory_map, &map_key,
+ &desc_size, &desc_version);
+ if (ret != EFI_SUCCESS) {
+ efi_st_printf("ERROR: GetMemoryMap did not return "
+ "EFI_SUCCESS\n");
+ return;
+ }
+ ret = boottime->exit_boot_services(handle, map_key);
+ if (ret != EFI_SUCCESS) {
+ efi_st_printf("ERROR: ExitBootServices did not return "
+ "EFI_SUCCESS\n");
+ return;
+ }
+ efi_st_printf("\nBoot services terminated\n");
+}
+
+/*
+ * Set up a test.
+ *
+ * @test the test to be executed
+ * @failures counter that will be incremented if a failure occurs
+ */
+static int setup(struct efi_unit_test *test, unsigned int *failures)
+{
+ int ret;
+
+ if (!test->setup)
+ return 0;
+ efi_st_printf("\nSetting up '%s'\n", test->name);
+ ret = test->setup(handle, systable);
+ if (ret) {
+ efi_st_printf("ERROR: Setting up '%s' failed\n", test->name);
+ ++*failures;
+ } else {
+ efi_st_printf("Setting up '%s' succeeded\n", test->name);
+ }
+ return ret;
+}
+
+/*
+ * Execute a test.
+ *
+ * @test the test to be executed
+ * @failures counter that will be incremented if a failure occurs
+ */
+static int execute(struct efi_unit_test *test, unsigned int *failures)
+{
+ int ret;
+
+ if (!test->execute)
+ return 0;
+ efi_st_printf("\nExecuting '%s'\n", test->name);
+ ret = test->execute();
+ if (ret) {
+ efi_st_printf("ERROR: Executing '%s' failed\n", test->name);
+ ++*failures;
+ } else {
+ efi_st_printf("Executing '%s' succeeded\n", test->name);
+ }
+ return ret;
+}
+
+/*
+ * Tear down a test.
+ *
+ * @test the test to be torn down
+ * @failures counter that will be incremented if a failure occurs
+ */
+static int teardown(struct efi_unit_test *test, unsigned int *failures)
+{
+ int ret;
+
+ if (!test->teardown)
+ return 0;
+ efi_st_printf("\nTearing down '%s'\n", test->name);
+ ret = test->teardown();
+ if (ret) {
+ efi_st_printf("ERROR: Tearing down '%s' failed\n", test->name);
+ ++*failures;
+ } else {
+ efi_st_printf("Tearing down '%s' succeeded\n", test->name);
+ }
+ return ret;
+}
+
+/*
+ * Execute selftest of the EFI API
+ *
+ * This is the main entry point of the EFI selftest application.
+ *
+ * All tests use a driver model and are run in three phases:
+ * setup, execute, teardown.
+ *
+ * A test may be setup and executed at boottime,
+ * it may be setup at boottime and executed at runtime,
+ * or it may be setup and executed at runtime.
+ *
+ * After executing all tests the system is reset.
+ *
+ * @image_handle: handle of the loaded EFI image
+ * @systab: EFI system table
+ */
+efi_status_t EFIAPI efi_selftest(efi_handle_t image_handle,
+ struct efi_system_table *systab)
+{
+ struct efi_unit_test *test;
+ unsigned int failures = 0;
+
+ systable = systab;
+ boottime = systable->boottime;
+ runtime = systable->runtime;
+ handle = image_handle;
+ con_out = systable->con_out;
+ con_in = systable->con_in;
+
+ efi_st_printf("\nTesting EFI API implementation\n");
+
+ efi_st_printf("\nNumber of tests to execute: %u\n",
+ ll_entry_count(struct efi_unit_test, efi_unit_test));
+
+ /* Execute boottime tests */
+ for (test = ll_entry_start(struct efi_unit_test, efi_unit_test);
+ test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) {
+ if (test->phase == EFI_EXECUTE_BEFORE_BOOTTIME_EXIT) {
+ setup(test, &failures);
+ execute(test, &failures);
+ teardown(test, &failures);
+ }
+ }
+
+ /* Execute mixed tests */
+ for (test = ll_entry_start(struct efi_unit_test, efi_unit_test);
+ test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) {
+ if (test->phase == EFI_SETUP_BEFORE_BOOTTIME_EXIT)
+ setup(test, &failures);
+ }
+
+ efi_st_exit_boot_services();
+
+ for (test = ll_entry_start(struct efi_unit_test, efi_unit_test);
+ test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) {
+ if (test->phase == EFI_SETUP_BEFORE_BOOTTIME_EXIT) {
+ execute(test, &failures);
+ teardown(test, &failures);
+ }
+ }
+
+ /* Execute runtime tests */
+ for (test = ll_entry_start(struct efi_unit_test, efi_unit_test);
+ test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) {
+ if (test->phase == EFI_SETUP_AFTER_BOOTTIME_EXIT) {
+ setup(test, &failures);
+ execute(test, &failures);
+ teardown(test, &failures);
+ }
+ }
+
+ /* Give feedback */
+ efi_st_printf("\nSummary: %u failures\n\n", failures);
+
+ /* Reset system */
+ efi_st_printf("Preparing for reset. Press any key.\n");
+ efi_st_get_key();
+ runtime->reset_system(EFI_RESET_WARM, EFI_NOT_READY,
+ sizeof(reset_message), reset_message);
+ efi_st_printf("\nERROR: reset failed.\n");
+
+ return EFI_UNSUPPORTED;
+}
diff --git a/lib/efi_selftest/efi_selftest_console.c b/lib/efi_selftest/efi_selftest_console.c
new file mode 100644
index 0000000..7b5b724
--- /dev/null
+++ b/lib/efi_selftest/efi_selftest_console.c
@@ -0,0 +1,187 @@
+/*
+ * EFI efi_selftest
+ *
+ * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <efi_selftest.h>
+#include <vsprintf.h>
+
+struct efi_simple_text_output_protocol *con_out;
+struct efi_simple_input_interface *con_in;
+
+/*
+ * Print a pointer to an u16 string
+ *
+ * @pointer: pointer
+ * @buf: pointer to buffer address
+ * on return position of terminating zero word
+ */
+static void pointer(void *pointer, u16 **buf)
+{
+ int i;
+ u16 c;
+ uintptr_t p = (uintptr_t)pointer;
+ u16 *pos = *buf;
+
+ for (i = 8 * sizeof(p) - 4; i >= 0; i -= 4) {
+ c = (p >> i) & 0x0f;
+ c += '0';
+ if (c > '9')
+ c += 'a' - '9' - 1;
+ *pos++ = c;
+ }
+ *pos = 0;
+ *buf = pos;
+}
+
+/*
+ * Print an unsigned 32bit value as decimal number to an u16 string
+ *
+ * @value: value to be printed
+ * @buf: pointer to buffer address
+ * on return position of terminating zero word
+ */
+static void uint2dec(u32 value, u16 **buf)
+{
+ u16 *pos = *buf;
+ int i;
+ u16 c;
+ u64 f;
+
+ /*
+ * Increment by .5 and multiply with
+ * (2 << 60) / 1,000,000,000 = 0x44B82FA0.9B5A52CC
+ * to move the first digit to bit 60-63.
+ */
+ f = 0x225C17D0;
+ f += (0x9B5A52DULL * value) >> 28;
+ f += 0x44B82FA0ULL * value;
+
+ for (i = 0; i < 10; ++i) {
+ /* Write current digit */
+ c = f >> 60;
+ if (c || pos != *buf)
+ *pos++ = c + '0';
+ /* Eliminate current digit */
+ f &= 0xfffffffffffffff;
+ /* Get next digit */
+ f *= 0xaULL;
+ }
+ if (pos == *buf)
+ *pos++ = '0';
+ *pos = 0;
+ *buf = pos;
+}
+
+/*
+ * Print a signed 32bit value as decimal number to an u16 string
+ *
+ * @value: value to be printed
+ * @buf: pointer to buffer address
+ * on return position of terminating zero word
+ */
+static void int2dec(s32 value, u16 **buf)
+{
+ u32 u;
+ u16 *pos = *buf;
+
+ if (value < 0) {
+ *pos++ = '-';
+ u = -value;
+ } else {
+ u = value;
+ }
+ uint2dec(u, &pos);
+ *buf = pos;
+}
+
+/*
+ * Print a formatted string to the EFI console
+ *
+ * @fmt: format string
+ * @...: optional arguments
+ */
+void efi_st_printf(const char *fmt, ...)
+{
+ va_list args;
+ u16 buf[160];
+ const char *c;
+ u16 *pos = buf;
+ const char *s;
+
+ va_start(args, fmt);
+
+ c = fmt;
+ for (; *c; ++c) {
+ switch (*c) {
+ case '\\':
+ ++c;
+ switch (*c) {
+ case '\0':
+ --c;
+ break;
+ case 'n':
+ *pos++ = '\n';
+ break;
+ case 'r':
+ *pos++ = '\r';
+ break;
+ case 't':
+ *pos++ = '\t';
+ break;
+ default:
+ *pos++ = *c;
+ }
+ break;
+ case '%':
+ ++c;
+ switch (*c) {
+ case '\0':
+ --c;
+ break;
+ case 'd':
+ int2dec(va_arg(args, s32), &pos);
+ break;
+ case 'p':
+ pointer(va_arg(args, void*), &pos);
+ break;
+ case 's':
+ s = va_arg(args, const char *);
+ for (; *s; ++s)
+ *pos++ = *s;
+ break;
+ case 'u':
+ uint2dec(va_arg(args, u32), &pos);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ *pos++ = *c;
+ }
+ }
+ va_end(args);
+ *pos = 0;
+ con_out->output_string(con_out, buf);
+}
+
+/*
+ * Reads an Unicode character from the input device.
+ *
+ * @return: Unicode character
+ */
+u16 efi_st_get_key(void)
+{
+ struct efi_input_key input_key;
+ efi_status_t ret;
+
+ /* Wait for next key */
+ do {
+ ret = con_in->read_key_stroke(con_in, &input_key);
+ } while (ret == EFI_NOT_READY);
+ return input_key.unicode_char;
+}