efi_selftest: provide an EFI selftest application
authorHeinrich Schuchardt <xypron.glpk@gmx.de>
Fri, 15 Sep 2017 08:06:11 +0000 (10:06 +0200)
committerAlexander Graf <agraf@suse.de>
Mon, 18 Sep 2017 21:53:57 +0000 (23:53 +0200)
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>
MAINTAINERS
cmd/Kconfig
cmd/bootefi.c
include/efi_loader.h
include/efi_selftest.h [new file with mode: 0644]
lib/Makefile
lib/efi_selftest/Kconfig [new file with mode: 0644]
lib/efi_selftest/Makefile [new file with mode: 0644]
lib/efi_selftest/efi_selftest.c [new file with mode: 0644]
lib/efi_selftest/efi_selftest_console.c [new file with mode: 0644]

index 04acf2b89dcbb17112e58085e9a853003a0f635f..0b7b2bbeb2438e2658a4278dd03337838ccc84ca 100644 (file)
@@ -259,8 +259,9 @@ EFI PAYLOAD
 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
index d6d130edfa917c7f6af938be710e0f4e581d8276..3ef9b16b082169189facfadd3ffbeba27d4b21ae 100644 (file)
@@ -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
index ffd50ba15926f0d630d1e97aa6fb7edea1e475aa..788f8694797576d8bc0406279cef8838e8953d4a 100644 (file)
@@ -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[])
 {
@@ -306,6 +305,22 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
                        addr = CONFIG_SYS_LOAD_ADDR;
                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 <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
index f27192555e55be977f752086ab5f7f15741bd167..f74b33d5895b72c76d220f2d374014350061d4b1 100644 (file)
@@ -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 (file)
index 0000000..76304a2
--- /dev/null
@@ -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 */
index faf4538fb5fe200146809a8ae6afd0affe97a397..8e1c9d1bb70a787d70941e02547e66cb77712429 100644 (file)
@@ -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 (file)
index 0000000..3b5f3a1
--- /dev/null
@@ -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 (file)
index 0000000..34f5ff1
--- /dev/null
@@ -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 (file)
index 0000000..efec832
--- /dev/null
@@ -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 (file)
index 0000000..7b5b724
--- /dev/null
@@ -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;
+}