diff --git a/MAINTAINERS b/MAINTAINERS index 04acf2b..0b7b2bb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -259,8 +259,9 @@ EFI PAYLOAD M: Alexander Graf 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 @@ config CMD_BOOTEFI_HELLO 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 @@ static unsigned long do_bootefi_exec(void *efi, void *fdt) 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 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) 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 @@ static char bootefi_help_text[] = " If specified, the device tree located at 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 @@ efi_status_t __efi_runtime EFIAPI efi_get_time( 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 + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _EFI_SELFTEST_H +#define _EFI_SELFTEST_H + +#include +#include +#include +#include + +/* + * 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 @@ ifndef CONFIG_SPL_BUILD 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 +# +# 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 + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +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 + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +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; +}