Commit 853540c84fdad4c2b0755c0041e87efba376aec2
Committed by
Alexander Graf
1 parent
ac02019616
Exists in
smarc_8mq_lf_v2020.04
and in
17 other branches
efi_selftest: colored test output
Add color coding to output: test section blue success green errors red todo yellow summary white others light gray Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de> Reviewed-by: Simon Glass <sjg@chromium.org> [agraf: Fold in move of set_attribute before the print] Signed-off-by: Alexander Graf <agraf@suse.de>
Showing 3 changed files with 40 additions and 25 deletions Inline Diff
include/efi_selftest.h
1 | /* | 1 | /* |
2 | * EFI application loader | 2 | * EFI application loader |
3 | * | 3 | * |
4 | * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> | 4 | * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> |
5 | * | 5 | * |
6 | * SPDX-License-Identifier: GPL-2.0+ | 6 | * SPDX-License-Identifier: GPL-2.0+ |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #ifndef _EFI_SELFTEST_H | 9 | #ifndef _EFI_SELFTEST_H |
10 | #define _EFI_SELFTEST_H | 10 | #define _EFI_SELFTEST_H |
11 | 11 | ||
12 | #include <common.h> | 12 | #include <common.h> |
13 | #include <efi.h> | 13 | #include <efi.h> |
14 | #include <efi_api.h> | 14 | #include <efi_api.h> |
15 | #include <efi_loader.h> | 15 | #include <efi_loader.h> |
16 | #include <linker_lists.h> | 16 | #include <linker_lists.h> |
17 | 17 | ||
18 | #define EFI_ST_SUCCESS 0 | 18 | #define EFI_ST_SUCCESS 0 |
19 | #define EFI_ST_FAILURE 1 | 19 | #define EFI_ST_FAILURE 1 |
20 | 20 | ||
21 | /* | 21 | /* |
22 | * Prints a message. | ||
23 | */ | ||
24 | #define efi_st_printf(...) \ | ||
25 | (efi_st_printc(-1, __VA_ARGS__)) | ||
26 | |||
27 | /* | ||
22 | * Prints an error message. | 28 | * Prints an error message. |
23 | * | 29 | * |
24 | * @... format string followed by fields to print | 30 | * @... format string followed by fields to print |
25 | */ | 31 | */ |
26 | #define efi_st_error(...) \ | 32 | #define efi_st_error(...) \ |
27 | (efi_st_printf("%s(%u):\nERROR: ", __FILE__, __LINE__), \ | 33 | (efi_st_printc(EFI_LIGHTRED, "%s(%u):\nERROR: ", __FILE__, __LINE__), \ |
28 | efi_st_printf(__VA_ARGS__)) \ | 34 | efi_st_printc(EFI_LIGHTRED, __VA_ARGS__)) |
29 | 35 | ||
30 | /* | 36 | /* |
31 | * Prints a TODO message. | 37 | * Prints a TODO message. |
32 | * | 38 | * |
33 | * @... format string followed by fields to print | 39 | * @... format string followed by fields to print |
34 | */ | 40 | */ |
35 | #define efi_st_todo(...) \ | 41 | #define efi_st_todo(...) \ |
36 | (efi_st_printf("%s(%u):\nTODO: ", __FILE__, __LINE__), \ | 42 | (efi_st_printc(EFI_YELLOW, "%s(%u):\nTODO: ", __FILE__, __LINE__), \ |
37 | efi_st_printf(__VA_ARGS__)) \ | 43 | efi_st_printc(EFI_YELLOW, __VA_ARGS__)) \ |
38 | 44 | ||
39 | /* | 45 | /* |
40 | * A test may be setup and executed at boottime, | 46 | * A test may be setup and executed at boottime, |
41 | * it may be setup at boottime and executed at runtime, | 47 | * it may be setup at boottime and executed at runtime, |
42 | * or it may be setup and executed at runtime. | 48 | * or it may be setup and executed at runtime. |
43 | */ | 49 | */ |
44 | enum efi_test_phase { | 50 | enum efi_test_phase { |
45 | EFI_EXECUTE_BEFORE_BOOTTIME_EXIT = 1, | 51 | EFI_EXECUTE_BEFORE_BOOTTIME_EXIT = 1, |
46 | EFI_SETUP_BEFORE_BOOTTIME_EXIT, | 52 | EFI_SETUP_BEFORE_BOOTTIME_EXIT, |
47 | EFI_SETUP_AFTER_BOOTTIME_EXIT, | 53 | EFI_SETUP_AFTER_BOOTTIME_EXIT, |
48 | }; | 54 | }; |
49 | 55 | ||
50 | extern struct efi_simple_text_output_protocol *con_out; | 56 | extern struct efi_simple_text_output_protocol *con_out; |
51 | extern struct efi_simple_input_interface *con_in; | 57 | extern struct efi_simple_input_interface *con_in; |
52 | 58 | ||
53 | /* | 59 | /* |
54 | * Exit the boot services. | 60 | * Exit the boot services. |
55 | * | 61 | * |
56 | * The size of the memory map is determined. | 62 | * The size of the memory map is determined. |
57 | * Pool memory is allocated to copy the memory map. | 63 | * Pool memory is allocated to copy the memory map. |
58 | * The memory amp is copied and the map key is obtained. | 64 | * The memory amp is copied and the map key is obtained. |
59 | * The map key is used to exit the boot services. | 65 | * The map key is used to exit the boot services. |
60 | */ | 66 | */ |
61 | void efi_st_exit_boot_services(void); | 67 | void efi_st_exit_boot_services(void); |
62 | 68 | ||
63 | /* | 69 | /* |
64 | * Print a pointer to an u16 string | 70 | * Print a colored message |
65 | * | 71 | * |
66 | * @pointer: pointer | 72 | * @color color, see constants in efi_api.h, use -1 for no color |
67 | * @buf: pointer to buffer address | 73 | * @fmt printf format |
68 | * on return position of terminating zero word | 74 | * @... arguments to be printed |
75 | * on return position of terminating zero word | ||
69 | */ | 76 | */ |
70 | void efi_st_printf(const char *fmt, ...) | 77 | void efi_st_printc(int color, const char *fmt, ...) |
71 | __attribute__ ((format (__printf__, 1, 2))); | 78 | __attribute__ ((format (__printf__, 2, 3))); |
72 | 79 | ||
73 | /* | 80 | /* |
74 | * Compare memory. | 81 | * Compare memory. |
75 | * We cannot use lib/string.c due to different CFLAGS values. | 82 | * We cannot use lib/string.c due to different CFLAGS values. |
76 | * | 83 | * |
77 | * @buf1: first buffer | 84 | * @buf1: first buffer |
78 | * @buf2: second buffer | 85 | * @buf2: second buffer |
79 | * @length: number of bytes to compare | 86 | * @length: number of bytes to compare |
80 | * @return: 0 if both buffers contain the same bytes | 87 | * @return: 0 if both buffers contain the same bytes |
81 | */ | 88 | */ |
82 | int efi_st_memcmp(const void *buf1, const void *buf2, size_t length); | 89 | int efi_st_memcmp(const void *buf1, const void *buf2, size_t length); |
83 | 90 | ||
84 | /* | 91 | /* |
85 | * Compare an u16 string to a char string. | 92 | * Compare an u16 string to a char string. |
86 | * | 93 | * |
87 | * @buf1: u16 string | 94 | * @buf1: u16 string |
88 | * @buf2: char string | 95 | * @buf2: char string |
89 | * @return: 0 if both buffers contain the same bytes | 96 | * @return: 0 if both buffers contain the same bytes |
90 | */ | 97 | */ |
91 | int efi_st_strcmp_16_8(const u16 *buf1, const char *buf2); | 98 | int efi_st_strcmp_16_8(const u16 *buf1, const char *buf2); |
92 | 99 | ||
93 | /* | 100 | /* |
94 | * Reads an Unicode character from the input device. | 101 | * Reads an Unicode character from the input device. |
95 | * | 102 | * |
96 | * @return: Unicode character | 103 | * @return: Unicode character |
97 | */ | 104 | */ |
98 | u16 efi_st_get_key(void); | 105 | u16 efi_st_get_key(void); |
99 | 106 | ||
100 | /** | 107 | /** |
101 | * struct efi_unit_test - EFI unit test | 108 | * struct efi_unit_test - EFI unit test |
102 | * | 109 | * |
103 | * An efi_unit_test provides a interface to an EFI unit test. | 110 | * An efi_unit_test provides a interface to an EFI unit test. |
104 | * | 111 | * |
105 | * @name: name of unit test | 112 | * @name: name of unit test |
106 | * @phase: specifies when setup and execute are executed | 113 | * @phase: specifies when setup and execute are executed |
107 | * @setup: set up the unit test | 114 | * @setup: set up the unit test |
108 | * @teardown: tear down the unit test | 115 | * @teardown: tear down the unit test |
109 | * @execute: execute the unit test | 116 | * @execute: execute the unit test |
110 | * @on_request: test is only executed on request | 117 | * @on_request: test is only executed on request |
111 | */ | 118 | */ |
112 | struct efi_unit_test { | 119 | struct efi_unit_test { |
113 | const char *name; | 120 | const char *name; |
114 | const enum efi_test_phase phase; | 121 | const enum efi_test_phase phase; |
115 | int (*setup)(const efi_handle_t handle, | 122 | int (*setup)(const efi_handle_t handle, |
116 | const struct efi_system_table *systable); | 123 | const struct efi_system_table *systable); |
117 | int (*execute)(void); | 124 | int (*execute)(void); |
118 | int (*teardown)(void); | 125 | int (*teardown)(void); |
119 | bool on_request; | 126 | bool on_request; |
120 | }; | 127 | }; |
121 | 128 | ||
122 | /* Declare a new EFI unit test */ | 129 | /* Declare a new EFI unit test */ |
123 | #define EFI_UNIT_TEST(__name) \ | 130 | #define EFI_UNIT_TEST(__name) \ |
124 | ll_entry_declare(struct efi_unit_test, __name, efi_unit_test) | 131 | ll_entry_declare(struct efi_unit_test, __name, efi_unit_test) |
125 | 132 | ||
126 | #endif /* _EFI_SELFTEST_H */ | 133 | #endif /* _EFI_SELFTEST_H */ |
127 | 134 |
lib/efi_selftest/efi_selftest.c
1 | /* | 1 | /* |
2 | * EFI efi_selftest | 2 | * EFI efi_selftest |
3 | * | 3 | * |
4 | * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> | 4 | * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> |
5 | * | 5 | * |
6 | * SPDX-License-Identifier: GPL-2.0+ | 6 | * SPDX-License-Identifier: GPL-2.0+ |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #include <efi_selftest.h> | 9 | #include <efi_selftest.h> |
10 | #include <vsprintf.h> | 10 | #include <vsprintf.h> |
11 | 11 | ||
12 | /* | 12 | /* |
13 | * Constants for test step bitmap | 13 | * Constants for test step bitmap |
14 | */ | 14 | */ |
15 | #define EFI_ST_SETUP 1 | 15 | #define EFI_ST_SETUP 1 |
16 | #define EFI_ST_EXECUTE 2 | 16 | #define EFI_ST_EXECUTE 2 |
17 | #define EFI_ST_TEARDOWN 4 | 17 | #define EFI_ST_TEARDOWN 4 |
18 | 18 | ||
19 | static const struct efi_system_table *systable; | 19 | static const struct efi_system_table *systable; |
20 | static const struct efi_boot_services *boottime; | 20 | static const struct efi_boot_services *boottime; |
21 | static const struct efi_runtime_services *runtime; | 21 | static const struct efi_runtime_services *runtime; |
22 | static efi_handle_t handle; | 22 | static efi_handle_t handle; |
23 | static u16 reset_message[] = L"Selftest completed"; | 23 | static u16 reset_message[] = L"Selftest completed"; |
24 | 24 | ||
25 | /* | 25 | /* |
26 | * Exit the boot services. | 26 | * Exit the boot services. |
27 | * | 27 | * |
28 | * The size of the memory map is determined. | 28 | * The size of the memory map is determined. |
29 | * Pool memory is allocated to copy the memory map. | 29 | * Pool memory is allocated to copy the memory map. |
30 | * The memory amp is copied and the map key is obtained. | 30 | * The memory amp is copied and the map key is obtained. |
31 | * The map key is used to exit the boot services. | 31 | * The map key is used to exit the boot services. |
32 | */ | 32 | */ |
33 | void efi_st_exit_boot_services(void) | 33 | void efi_st_exit_boot_services(void) |
34 | { | 34 | { |
35 | efi_uintn_t map_size = 0; | 35 | efi_uintn_t map_size = 0; |
36 | efi_uintn_t map_key; | 36 | efi_uintn_t map_key; |
37 | efi_uintn_t desc_size; | 37 | efi_uintn_t desc_size; |
38 | u32 desc_version; | 38 | u32 desc_version; |
39 | efi_status_t ret; | 39 | efi_status_t ret; |
40 | struct efi_mem_desc *memory_map; | 40 | struct efi_mem_desc *memory_map; |
41 | 41 | ||
42 | ret = boottime->get_memory_map(&map_size, NULL, &map_key, &desc_size, | 42 | ret = boottime->get_memory_map(&map_size, NULL, &map_key, &desc_size, |
43 | &desc_version); | 43 | &desc_version); |
44 | if (ret != EFI_BUFFER_TOO_SMALL) { | 44 | if (ret != EFI_BUFFER_TOO_SMALL) { |
45 | efi_st_error( | 45 | efi_st_error( |
46 | "GetMemoryMap did not return EFI_BUFFER_TOO_SMALL\n"); | 46 | "GetMemoryMap did not return EFI_BUFFER_TOO_SMALL\n"); |
47 | return; | 47 | return; |
48 | } | 48 | } |
49 | /* Allocate extra space for newly allocated memory */ | 49 | /* Allocate extra space for newly allocated memory */ |
50 | map_size += sizeof(struct efi_mem_desc); | 50 | map_size += sizeof(struct efi_mem_desc); |
51 | ret = boottime->allocate_pool(EFI_BOOT_SERVICES_DATA, map_size, | 51 | ret = boottime->allocate_pool(EFI_BOOT_SERVICES_DATA, map_size, |
52 | (void **)&memory_map); | 52 | (void **)&memory_map); |
53 | if (ret != EFI_SUCCESS) { | 53 | if (ret != EFI_SUCCESS) { |
54 | efi_st_error("AllocatePool did not return EFI_SUCCESS\n"); | 54 | efi_st_error("AllocatePool did not return EFI_SUCCESS\n"); |
55 | return; | 55 | return; |
56 | } | 56 | } |
57 | ret = boottime->get_memory_map(&map_size, memory_map, &map_key, | 57 | ret = boottime->get_memory_map(&map_size, memory_map, &map_key, |
58 | &desc_size, &desc_version); | 58 | &desc_size, &desc_version); |
59 | if (ret != EFI_SUCCESS) { | 59 | if (ret != EFI_SUCCESS) { |
60 | efi_st_error("GetMemoryMap did not return EFI_SUCCESS\n"); | 60 | efi_st_error("GetMemoryMap did not return EFI_SUCCESS\n"); |
61 | return; | 61 | return; |
62 | } | 62 | } |
63 | ret = boottime->exit_boot_services(handle, map_key); | 63 | ret = boottime->exit_boot_services(handle, map_key); |
64 | if (ret != EFI_SUCCESS) { | 64 | if (ret != EFI_SUCCESS) { |
65 | efi_st_error("ExitBootServices did not return EFI_SUCCESS\n"); | 65 | efi_st_error("ExitBootServices did not return EFI_SUCCESS\n"); |
66 | return; | 66 | return; |
67 | } | 67 | } |
68 | efi_st_printf("\nBoot services terminated\n"); | 68 | efi_st_printc(EFI_WHITE, "\nBoot services terminated\n"); |
69 | } | 69 | } |
70 | 70 | ||
71 | /* | 71 | /* |
72 | * Set up a test. | 72 | * Set up a test. |
73 | * | 73 | * |
74 | * @test the test to be executed | 74 | * @test the test to be executed |
75 | * @failures counter that will be incremented if a failure occurs | 75 | * @failures counter that will be incremented if a failure occurs |
76 | * @return EFI_ST_SUCCESS for success | 76 | * @return EFI_ST_SUCCESS for success |
77 | */ | 77 | */ |
78 | static int setup(struct efi_unit_test *test, unsigned int *failures) | 78 | static int setup(struct efi_unit_test *test, unsigned int *failures) |
79 | { | 79 | { |
80 | int ret; | 80 | int ret; |
81 | 81 | ||
82 | if (!test->setup) | 82 | if (!test->setup) |
83 | return EFI_ST_SUCCESS; | 83 | return EFI_ST_SUCCESS; |
84 | efi_st_printf("\nSetting up '%s'\n", test->name); | 84 | efi_st_printc(EFI_LIGHTBLUE, "\nSetting up '%s'\n", test->name); |
85 | ret = test->setup(handle, systable); | 85 | ret = test->setup(handle, systable); |
86 | if (ret != EFI_ST_SUCCESS) { | 86 | if (ret != EFI_ST_SUCCESS) { |
87 | efi_st_error("Setting up '%s' failed\n", test->name); | 87 | efi_st_error("Setting up '%s' failed\n", test->name); |
88 | ++*failures; | 88 | ++*failures; |
89 | } else { | 89 | } else { |
90 | efi_st_printf("Setting up '%s' succeeded\n", test->name); | 90 | efi_st_printc(EFI_LIGHTGREEN, |
91 | "Setting up '%s' succeeded\n", test->name); | ||
91 | } | 92 | } |
92 | return ret; | 93 | return ret; |
93 | } | 94 | } |
94 | 95 | ||
95 | /* | 96 | /* |
96 | * Execute a test. | 97 | * Execute a test. |
97 | * | 98 | * |
98 | * @test the test to be executed | 99 | * @test the test to be executed |
99 | * @failures counter that will be incremented if a failure occurs | 100 | * @failures counter that will be incremented if a failure occurs |
100 | * @return EFI_ST_SUCCESS for success | 101 | * @return EFI_ST_SUCCESS for success |
101 | */ | 102 | */ |
102 | static int execute(struct efi_unit_test *test, unsigned int *failures) | 103 | static int execute(struct efi_unit_test *test, unsigned int *failures) |
103 | { | 104 | { |
104 | int ret; | 105 | int ret; |
105 | 106 | ||
106 | if (!test->execute) | 107 | if (!test->execute) |
107 | return EFI_ST_SUCCESS; | 108 | return EFI_ST_SUCCESS; |
108 | efi_st_printf("\nExecuting '%s'\n", test->name); | 109 | efi_st_printc(EFI_LIGHTBLUE, "\nExecuting '%s'\n", test->name); |
109 | ret = test->execute(); | 110 | ret = test->execute(); |
110 | if (ret != EFI_ST_SUCCESS) { | 111 | if (ret != EFI_ST_SUCCESS) { |
111 | efi_st_error("Executing '%s' failed\n", test->name); | 112 | efi_st_error("Executing '%s' failed\n", test->name); |
112 | ++*failures; | 113 | ++*failures; |
113 | } else { | 114 | } else { |
114 | efi_st_printf("Executing '%s' succeeded\n", test->name); | 115 | efi_st_printc(EFI_LIGHTGREEN, |
116 | "Executing '%s' succeeded\n", test->name); | ||
115 | } | 117 | } |
116 | return ret; | 118 | return ret; |
117 | } | 119 | } |
118 | 120 | ||
119 | /* | 121 | /* |
120 | * Tear down a test. | 122 | * Tear down a test. |
121 | * | 123 | * |
122 | * @test the test to be torn down | 124 | * @test the test to be torn down |
123 | * @failures counter that will be incremented if a failure occurs | 125 | * @failures counter that will be incremented if a failure occurs |
124 | * @return EFI_ST_SUCCESS for success | 126 | * @return EFI_ST_SUCCESS for success |
125 | */ | 127 | */ |
126 | static int teardown(struct efi_unit_test *test, unsigned int *failures) | 128 | static int teardown(struct efi_unit_test *test, unsigned int *failures) |
127 | { | 129 | { |
128 | int ret; | 130 | int ret; |
129 | 131 | ||
130 | if (!test->teardown) | 132 | if (!test->teardown) |
131 | return EFI_ST_SUCCESS; | 133 | return EFI_ST_SUCCESS; |
132 | efi_st_printf("\nTearing down '%s'\n", test->name); | 134 | efi_st_printc(EFI_LIGHTBLUE, "\nTearing down '%s'\n", test->name); |
133 | ret = test->teardown(); | 135 | ret = test->teardown(); |
134 | if (ret != EFI_ST_SUCCESS) { | 136 | if (ret != EFI_ST_SUCCESS) { |
135 | efi_st_error("Tearing down '%s' failed\n", test->name); | 137 | efi_st_error("Tearing down '%s' failed\n", test->name); |
136 | ++*failures; | 138 | ++*failures; |
137 | } else { | 139 | } else { |
138 | efi_st_printf("Tearing down '%s' succeeded\n", test->name); | 140 | efi_st_printc(EFI_LIGHTGREEN, |
141 | "Tearing down '%s' succeeded\n", test->name); | ||
139 | } | 142 | } |
140 | return ret; | 143 | return ret; |
141 | } | 144 | } |
142 | 145 | ||
143 | /* | 146 | /* |
144 | * Check that a test exists. | 147 | * Check that a test exists. |
145 | * | 148 | * |
146 | * @testname: name of the test | 149 | * @testname: name of the test |
147 | * @return: test | 150 | * @return: test |
148 | */ | 151 | */ |
149 | static struct efi_unit_test *find_test(const u16 *testname) | 152 | static struct efi_unit_test *find_test(const u16 *testname) |
150 | { | 153 | { |
151 | struct efi_unit_test *test; | 154 | struct efi_unit_test *test; |
152 | 155 | ||
153 | for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); | 156 | for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); |
154 | test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { | 157 | test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { |
155 | if (!efi_st_strcmp_16_8(testname, test->name)) | 158 | if (!efi_st_strcmp_16_8(testname, test->name)) |
156 | return test; | 159 | return test; |
157 | } | 160 | } |
158 | efi_st_printf("\nTest '%ps' not found\n", testname); | 161 | efi_st_printf("\nTest '%ps' not found\n", testname); |
159 | return NULL; | 162 | return NULL; |
160 | } | 163 | } |
161 | 164 | ||
162 | /* | 165 | /* |
163 | * List all available tests. | 166 | * List all available tests. |
164 | */ | 167 | */ |
165 | static void list_all_tests(void) | 168 | static void list_all_tests(void) |
166 | { | 169 | { |
167 | struct efi_unit_test *test; | 170 | struct efi_unit_test *test; |
168 | 171 | ||
169 | /* List all tests */ | 172 | /* List all tests */ |
170 | efi_st_printf("\nAvailable tests:\n"); | 173 | efi_st_printf("\nAvailable tests:\n"); |
171 | for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); | 174 | for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); |
172 | test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { | 175 | test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { |
173 | efi_st_printf("'%s'%s\n", test->name, | 176 | efi_st_printf("'%s'%s\n", test->name, |
174 | test->on_request ? " - on request" : ""); | 177 | test->on_request ? " - on request" : ""); |
175 | } | 178 | } |
176 | } | 179 | } |
177 | 180 | ||
178 | /* | 181 | /* |
179 | * Execute test steps of one phase. | 182 | * Execute test steps of one phase. |
180 | * | 183 | * |
181 | * @testname name of a single selected test or NULL | 184 | * @testname name of a single selected test or NULL |
182 | * @phase test phase | 185 | * @phase test phase |
183 | * @steps steps to execute | 186 | * @steps steps to execute |
184 | * failures returns EFI_ST_SUCCESS if all test steps succeeded | 187 | * failures returns EFI_ST_SUCCESS if all test steps succeeded |
185 | */ | 188 | */ |
186 | void efi_st_do_tests(const u16 *testname, unsigned int phase, | 189 | void efi_st_do_tests(const u16 *testname, unsigned int phase, |
187 | unsigned int steps, unsigned int *failures) | 190 | unsigned int steps, unsigned int *failures) |
188 | { | 191 | { |
189 | struct efi_unit_test *test; | 192 | struct efi_unit_test *test; |
190 | 193 | ||
191 | for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); | 194 | for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); |
192 | test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { | 195 | test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { |
193 | if (testname ? | 196 | if (testname ? |
194 | efi_st_strcmp_16_8(testname, test->name) : test->on_request) | 197 | efi_st_strcmp_16_8(testname, test->name) : test->on_request) |
195 | continue; | 198 | continue; |
196 | if (test->phase != phase) | 199 | if (test->phase != phase) |
197 | continue; | 200 | continue; |
198 | if (steps & EFI_ST_SETUP) | 201 | if (steps & EFI_ST_SETUP) |
199 | setup(test, failures); | 202 | setup(test, failures); |
200 | if (steps & EFI_ST_EXECUTE) | 203 | if (steps & EFI_ST_EXECUTE) |
201 | execute(test, failures); | 204 | execute(test, failures); |
202 | if (steps & EFI_ST_TEARDOWN) | 205 | if (steps & EFI_ST_TEARDOWN) |
203 | teardown(test, failures); | 206 | teardown(test, failures); |
204 | } | 207 | } |
205 | } | 208 | } |
206 | 209 | ||
207 | /* | 210 | /* |
208 | * Execute selftest of the EFI API | 211 | * Execute selftest of the EFI API |
209 | * | 212 | * |
210 | * This is the main entry point of the EFI selftest application. | 213 | * This is the main entry point of the EFI selftest application. |
211 | * | 214 | * |
212 | * All tests use a driver model and are run in three phases: | 215 | * All tests use a driver model and are run in three phases: |
213 | * setup, execute, teardown. | 216 | * setup, execute, teardown. |
214 | * | 217 | * |
215 | * A test may be setup and executed at boottime, | 218 | * A test may be setup and executed at boottime, |
216 | * it may be setup at boottime and executed at runtime, | 219 | * it may be setup at boottime and executed at runtime, |
217 | * or it may be setup and executed at runtime. | 220 | * or it may be setup and executed at runtime. |
218 | * | 221 | * |
219 | * After executing all tests the system is reset. | 222 | * After executing all tests the system is reset. |
220 | * | 223 | * |
221 | * @image_handle: handle of the loaded EFI image | 224 | * @image_handle: handle of the loaded EFI image |
222 | * @systab: EFI system table | 225 | * @systab: EFI system table |
223 | */ | 226 | */ |
224 | efi_status_t EFIAPI efi_selftest(efi_handle_t image_handle, | 227 | efi_status_t EFIAPI efi_selftest(efi_handle_t image_handle, |
225 | struct efi_system_table *systab) | 228 | struct efi_system_table *systab) |
226 | { | 229 | { |
227 | unsigned int failures = 0; | 230 | unsigned int failures = 0; |
228 | const u16 *testname = NULL; | 231 | const u16 *testname = NULL; |
229 | struct efi_loaded_image *loaded_image; | 232 | struct efi_loaded_image *loaded_image; |
230 | efi_status_t ret; | 233 | efi_status_t ret; |
231 | 234 | ||
232 | systable = systab; | 235 | systable = systab; |
233 | boottime = systable->boottime; | 236 | boottime = systable->boottime; |
234 | runtime = systable->runtime; | 237 | runtime = systable->runtime; |
235 | handle = image_handle; | 238 | handle = image_handle; |
236 | con_out = systable->con_out; | 239 | con_out = systable->con_out; |
237 | con_in = systable->con_in; | 240 | con_in = systable->con_in; |
238 | 241 | ||
239 | ret = boottime->handle_protocol(image_handle, &efi_guid_loaded_image, | 242 | ret = boottime->handle_protocol(image_handle, &efi_guid_loaded_image, |
240 | (void **)&loaded_image); | 243 | (void **)&loaded_image); |
241 | if (ret != EFI_SUCCESS) { | 244 | if (ret != EFI_SUCCESS) { |
242 | efi_st_error("Cannot open loaded image protocol\n"); | 245 | efi_st_error("Cannot open loaded image protocol\n"); |
243 | return ret; | 246 | return ret; |
244 | } | 247 | } |
245 | 248 | ||
246 | if (loaded_image->load_options) | 249 | if (loaded_image->load_options) |
247 | testname = (u16 *)loaded_image->load_options; | 250 | testname = (u16 *)loaded_image->load_options; |
248 | 251 | ||
249 | if (testname) { | 252 | if (testname) { |
250 | if (!efi_st_strcmp_16_8(testname, "list") || | 253 | if (!efi_st_strcmp_16_8(testname, "list") || |
251 | !find_test(testname)) { | 254 | !find_test(testname)) { |
252 | list_all_tests(); | 255 | list_all_tests(); |
253 | /* | 256 | /* |
254 | * TODO: | 257 | * TODO: |
255 | * Once the Exit boottime service is correctly | 258 | * Once the Exit boottime service is correctly |
256 | * implemented we should call | 259 | * implemented we should call |
257 | * boottime->exit(image_handle, EFI_SUCCESS, 0, NULL); | 260 | * boottime->exit(image_handle, EFI_SUCCESS, 0, NULL); |
258 | * here, cf. | 261 | * here, cf. |
259 | * https://lists.denx.de/pipermail/u-boot/2017-October/308720.html | 262 | * https://lists.denx.de/pipermail/u-boot/2017-October/308720.html |
260 | */ | 263 | */ |
261 | return EFI_SUCCESS; | 264 | return EFI_SUCCESS; |
262 | } | 265 | } |
263 | } | 266 | } |
264 | 267 | ||
265 | efi_st_printf("\nTesting EFI API implementation\n"); | 268 | efi_st_printc(EFI_WHITE, "\nTesting EFI API implementation\n"); |
266 | 269 | ||
267 | if (testname) | 270 | if (testname) |
268 | efi_st_printf("\nSelected test: '%ps'\n", testname); | 271 | efi_st_printc(EFI_WHITE, "\nSelected test: '%ps'\n", testname); |
269 | else | 272 | else |
270 | efi_st_printf("\nNumber of tests to execute: %u\n", | 273 | efi_st_printc(EFI_WHITE, "\nNumber of tests to execute: %u\n", |
271 | ll_entry_count(struct efi_unit_test, | 274 | ll_entry_count(struct efi_unit_test, |
272 | efi_unit_test)); | 275 | efi_unit_test)); |
273 | 276 | ||
274 | /* Execute boottime tests */ | 277 | /* Execute boottime tests */ |
275 | efi_st_do_tests(testname, EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, | 278 | efi_st_do_tests(testname, EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, |
276 | EFI_ST_SETUP | EFI_ST_EXECUTE | EFI_ST_TEARDOWN, | 279 | EFI_ST_SETUP | EFI_ST_EXECUTE | EFI_ST_TEARDOWN, |
277 | &failures); | 280 | &failures); |
278 | 281 | ||
279 | /* Execute mixed tests */ | 282 | /* Execute mixed tests */ |
280 | efi_st_do_tests(testname, EFI_SETUP_BEFORE_BOOTTIME_EXIT, | 283 | efi_st_do_tests(testname, EFI_SETUP_BEFORE_BOOTTIME_EXIT, |
281 | EFI_ST_SETUP, &failures); | 284 | EFI_ST_SETUP, &failures); |
282 | 285 | ||
283 | efi_st_exit_boot_services(); | 286 | efi_st_exit_boot_services(); |
284 | 287 | ||
285 | efi_st_do_tests(testname, EFI_SETUP_BEFORE_BOOTTIME_EXIT, | 288 | efi_st_do_tests(testname, EFI_SETUP_BEFORE_BOOTTIME_EXIT, |
286 | EFI_ST_EXECUTE | EFI_ST_TEARDOWN, &failures); | 289 | EFI_ST_EXECUTE | EFI_ST_TEARDOWN, &failures); |
287 | 290 | ||
288 | /* Execute runtime tests */ | 291 | /* Execute runtime tests */ |
289 | efi_st_do_tests(testname, EFI_SETUP_AFTER_BOOTTIME_EXIT, | 292 | efi_st_do_tests(testname, EFI_SETUP_AFTER_BOOTTIME_EXIT, |
290 | EFI_ST_SETUP | EFI_ST_EXECUTE | EFI_ST_TEARDOWN, | 293 | EFI_ST_SETUP | EFI_ST_EXECUTE | EFI_ST_TEARDOWN, |
291 | &failures); | 294 | &failures); |
292 | 295 | ||
293 | /* Give feedback */ | 296 | /* Give feedback */ |
294 | efi_st_printf("\nSummary: %u failures\n\n", failures); | 297 | efi_st_printc(EFI_WHITE, "\nSummary: %u failures\n\n", failures); |
295 | 298 | ||
296 | /* Reset system */ | 299 | /* Reset system */ |
297 | efi_st_printf("Preparing for reset. Press any key.\n"); | 300 | efi_st_printf("Preparing for reset. Press any key.\n"); |
298 | efi_st_get_key(); | 301 | efi_st_get_key(); |
299 | runtime->reset_system(EFI_RESET_WARM, EFI_NOT_READY, | 302 | runtime->reset_system(EFI_RESET_WARM, EFI_NOT_READY, |
300 | sizeof(reset_message), reset_message); | 303 | sizeof(reset_message), reset_message); |
301 | efi_st_printf("\n"); | 304 | efi_st_printf("\n"); |
302 | efi_st_error("Reset failed.\n"); | 305 | efi_st_error("Reset failed.\n"); |
303 | 306 | ||
304 | return EFI_UNSUPPORTED; | 307 | return EFI_UNSUPPORTED; |
305 | } | 308 | } |
306 | 309 |
lib/efi_selftest/efi_selftest_console.c
1 | /* | 1 | /* |
2 | * EFI efi_selftest | 2 | * EFI efi_selftest |
3 | * | 3 | * |
4 | * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> | 4 | * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> |
5 | * | 5 | * |
6 | * SPDX-License-Identifier: GPL-2.0+ | 6 | * SPDX-License-Identifier: GPL-2.0+ |
7 | */ | 7 | */ |
8 | 8 | ||
9 | #include <efi_selftest.h> | 9 | #include <efi_selftest.h> |
10 | #include <vsprintf.h> | 10 | #include <vsprintf.h> |
11 | 11 | ||
12 | struct efi_simple_text_output_protocol *con_out; | 12 | struct efi_simple_text_output_protocol *con_out; |
13 | struct efi_simple_input_interface *con_in; | 13 | struct efi_simple_input_interface *con_in; |
14 | 14 | ||
15 | /* | 15 | /* |
16 | * Print a MAC address to an u16 string | 16 | * Print a MAC address to an u16 string |
17 | * | 17 | * |
18 | * @pointer: mac address | 18 | * @pointer: mac address |
19 | * @buf: pointer to buffer address | 19 | * @buf: pointer to buffer address |
20 | * on return position of terminating zero word | 20 | * on return position of terminating zero word |
21 | */ | 21 | */ |
22 | static void mac(void *pointer, u16 **buf) | 22 | static void mac(void *pointer, u16 **buf) |
23 | { | 23 | { |
24 | int i, j; | 24 | int i, j; |
25 | u16 c; | 25 | u16 c; |
26 | u8 *p = (u8 *)pointer; | 26 | u8 *p = (u8 *)pointer; |
27 | u8 byte; | 27 | u8 byte; |
28 | u16 *pos = *buf; | 28 | u16 *pos = *buf; |
29 | 29 | ||
30 | for (i = 0; i < ARP_HLEN; ++i) { | 30 | for (i = 0; i < ARP_HLEN; ++i) { |
31 | if (i) | 31 | if (i) |
32 | *pos++ = ':'; | 32 | *pos++ = ':'; |
33 | byte = p[i]; | 33 | byte = p[i]; |
34 | for (j = 4; j >= 0; j -= 4) { | 34 | for (j = 4; j >= 0; j -= 4) { |
35 | c = (byte >> j) & 0x0f; | 35 | c = (byte >> j) & 0x0f; |
36 | c += '0'; | 36 | c += '0'; |
37 | if (c > '9') | 37 | if (c > '9') |
38 | c += 'a' - '9' - 1; | 38 | c += 'a' - '9' - 1; |
39 | *pos++ = c; | 39 | *pos++ = c; |
40 | } | 40 | } |
41 | } | 41 | } |
42 | *pos = 0; | 42 | *pos = 0; |
43 | *buf = pos; | 43 | *buf = pos; |
44 | } | 44 | } |
45 | 45 | ||
46 | /* | 46 | /* |
47 | * Print a pointer to an u16 string | 47 | * Print a pointer to an u16 string |
48 | * | 48 | * |
49 | * @pointer: pointer | 49 | * @pointer: pointer |
50 | * @buf: pointer to buffer address | 50 | * @buf: pointer to buffer address |
51 | * on return position of terminating zero word | 51 | * on return position of terminating zero word |
52 | */ | 52 | */ |
53 | static void pointer(void *pointer, u16 **buf) | 53 | static void pointer(void *pointer, u16 **buf) |
54 | { | 54 | { |
55 | int i; | 55 | int i; |
56 | u16 c; | 56 | u16 c; |
57 | uintptr_t p = (uintptr_t)pointer; | 57 | uintptr_t p = (uintptr_t)pointer; |
58 | u16 *pos = *buf; | 58 | u16 *pos = *buf; |
59 | 59 | ||
60 | for (i = 8 * sizeof(p) - 4; i >= 0; i -= 4) { | 60 | for (i = 8 * sizeof(p) - 4; i >= 0; i -= 4) { |
61 | c = (p >> i) & 0x0f; | 61 | c = (p >> i) & 0x0f; |
62 | c += '0'; | 62 | c += '0'; |
63 | if (c > '9') | 63 | if (c > '9') |
64 | c += 'a' - '9' - 1; | 64 | c += 'a' - '9' - 1; |
65 | *pos++ = c; | 65 | *pos++ = c; |
66 | } | 66 | } |
67 | *pos = 0; | 67 | *pos = 0; |
68 | *buf = pos; | 68 | *buf = pos; |
69 | } | 69 | } |
70 | 70 | ||
71 | /* | 71 | /* |
72 | * Print an unsigned 32bit value as decimal number to an u16 string | 72 | * Print an unsigned 32bit value as decimal number to an u16 string |
73 | * | 73 | * |
74 | * @value: value to be printed | 74 | * @value: value to be printed |
75 | * @buf: pointer to buffer address | 75 | * @buf: pointer to buffer address |
76 | * on return position of terminating zero word | 76 | * on return position of terminating zero word |
77 | */ | 77 | */ |
78 | static void uint2dec(u32 value, u16 **buf) | 78 | static void uint2dec(u32 value, u16 **buf) |
79 | { | 79 | { |
80 | u16 *pos = *buf; | 80 | u16 *pos = *buf; |
81 | int i; | 81 | int i; |
82 | u16 c; | 82 | u16 c; |
83 | u64 f; | 83 | u64 f; |
84 | 84 | ||
85 | /* | 85 | /* |
86 | * Increment by .5 and multiply with | 86 | * Increment by .5 and multiply with |
87 | * (2 << 60) / 1,000,000,000 = 0x44B82FA0.9B5A52CC | 87 | * (2 << 60) / 1,000,000,000 = 0x44B82FA0.9B5A52CC |
88 | * to move the first digit to bit 60-63. | 88 | * to move the first digit to bit 60-63. |
89 | */ | 89 | */ |
90 | f = 0x225C17D0; | 90 | f = 0x225C17D0; |
91 | f += (0x9B5A52DULL * value) >> 28; | 91 | f += (0x9B5A52DULL * value) >> 28; |
92 | f += 0x44B82FA0ULL * value; | 92 | f += 0x44B82FA0ULL * value; |
93 | 93 | ||
94 | for (i = 0; i < 10; ++i) { | 94 | for (i = 0; i < 10; ++i) { |
95 | /* Write current digit */ | 95 | /* Write current digit */ |
96 | c = f >> 60; | 96 | c = f >> 60; |
97 | if (c || pos != *buf) | 97 | if (c || pos != *buf) |
98 | *pos++ = c + '0'; | 98 | *pos++ = c + '0'; |
99 | /* Eliminate current digit */ | 99 | /* Eliminate current digit */ |
100 | f &= 0xfffffffffffffff; | 100 | f &= 0xfffffffffffffff; |
101 | /* Get next digit */ | 101 | /* Get next digit */ |
102 | f *= 0xaULL; | 102 | f *= 0xaULL; |
103 | } | 103 | } |
104 | if (pos == *buf) | 104 | if (pos == *buf) |
105 | *pos++ = '0'; | 105 | *pos++ = '0'; |
106 | *pos = 0; | 106 | *pos = 0; |
107 | *buf = pos; | 107 | *buf = pos; |
108 | } | 108 | } |
109 | 109 | ||
110 | /* | 110 | /* |
111 | * Print a signed 32bit value as decimal number to an u16 string | 111 | * Print a signed 32bit value as decimal number to an u16 string |
112 | * | 112 | * |
113 | * @value: value to be printed | 113 | * @value: value to be printed |
114 | * @buf: pointer to buffer address | 114 | * @buf: pointer to buffer address |
115 | * on return position of terminating zero word | 115 | * on return position of terminating zero word |
116 | */ | 116 | */ |
117 | static void int2dec(s32 value, u16 **buf) | 117 | static void int2dec(s32 value, u16 **buf) |
118 | { | 118 | { |
119 | u32 u; | 119 | u32 u; |
120 | u16 *pos = *buf; | 120 | u16 *pos = *buf; |
121 | 121 | ||
122 | if (value < 0) { | 122 | if (value < 0) { |
123 | *pos++ = '-'; | 123 | *pos++ = '-'; |
124 | u = -value; | 124 | u = -value; |
125 | } else { | 125 | } else { |
126 | u = value; | 126 | u = value; |
127 | } | 127 | } |
128 | uint2dec(u, &pos); | 128 | uint2dec(u, &pos); |
129 | *buf = pos; | 129 | *buf = pos; |
130 | } | 130 | } |
131 | 131 | ||
132 | /* | 132 | /* |
133 | * Print a formatted string to the EFI console | 133 | * Print a colored formatted string to the EFI console |
134 | * | 134 | * |
135 | * @fmt: format string | 135 | * @color color, see constants in efi_api.h, use -1 for no color |
136 | * @...: optional arguments | 136 | * @fmt format string |
137 | * @... optional arguments | ||
137 | */ | 138 | */ |
138 | void efi_st_printf(const char *fmt, ...) | 139 | void efi_st_printc(int color, const char *fmt, ...) |
139 | { | 140 | { |
140 | va_list args; | 141 | va_list args; |
141 | u16 buf[160]; | 142 | u16 buf[160]; |
142 | const char *c; | 143 | const char *c; |
143 | u16 *pos = buf; | 144 | u16 *pos = buf; |
144 | const char *s; | 145 | const char *s; |
145 | u16 *u; | 146 | u16 *u; |
146 | 147 | ||
147 | va_start(args, fmt); | 148 | va_start(args, fmt); |
148 | 149 | ||
150 | if (color >= 0) | ||
151 | con_out->set_attribute(con_out, (unsigned long)color); | ||
149 | c = fmt; | 152 | c = fmt; |
150 | for (; *c; ++c) { | 153 | for (; *c; ++c) { |
151 | switch (*c) { | 154 | switch (*c) { |
152 | case '\\': | 155 | case '\\': |
153 | ++c; | 156 | ++c; |
154 | switch (*c) { | 157 | switch (*c) { |
155 | case '\0': | 158 | case '\0': |
156 | --c; | 159 | --c; |
157 | break; | 160 | break; |
158 | case 'n': | 161 | case 'n': |
159 | *pos++ = '\n'; | 162 | *pos++ = '\n'; |
160 | break; | 163 | break; |
161 | case 'r': | 164 | case 'r': |
162 | *pos++ = '\r'; | 165 | *pos++ = '\r'; |
163 | break; | 166 | break; |
164 | case 't': | 167 | case 't': |
165 | *pos++ = '\t'; | 168 | *pos++ = '\t'; |
166 | break; | 169 | break; |
167 | default: | 170 | default: |
168 | *pos++ = *c; | 171 | *pos++ = *c; |
169 | } | 172 | } |
170 | break; | 173 | break; |
171 | case '%': | 174 | case '%': |
172 | ++c; | 175 | ++c; |
173 | switch (*c) { | 176 | switch (*c) { |
174 | case '\0': | 177 | case '\0': |
175 | --c; | 178 | --c; |
176 | break; | 179 | break; |
177 | case 'd': | 180 | case 'd': |
178 | int2dec(va_arg(args, s32), &pos); | 181 | int2dec(va_arg(args, s32), &pos); |
179 | break; | 182 | break; |
180 | case 'p': | 183 | case 'p': |
181 | ++c; | 184 | ++c; |
182 | switch (*c) { | 185 | switch (*c) { |
183 | /* MAC address */ | 186 | /* MAC address */ |
184 | case 'm': | 187 | case 'm': |
185 | mac(va_arg(args, void*), &pos); | 188 | mac(va_arg(args, void*), &pos); |
186 | break; | 189 | break; |
187 | 190 | ||
188 | /* u16 string */ | 191 | /* u16 string */ |
189 | case 's': | 192 | case 's': |
190 | u = va_arg(args, u16*); | 193 | u = va_arg(args, u16*); |
191 | if (pos > buf) { | 194 | if (pos > buf) { |
192 | *pos = 0; | 195 | *pos = 0; |
193 | con_out->output_string(con_out, | 196 | con_out->output_string(con_out, |
194 | buf); | 197 | buf); |
195 | } | 198 | } |
196 | con_out->output_string(con_out, u); | 199 | con_out->output_string(con_out, u); |
197 | pos = buf; | 200 | pos = buf; |
198 | break; | 201 | break; |
199 | default: | 202 | default: |
200 | --c; | 203 | --c; |
201 | pointer(va_arg(args, void*), &pos); | 204 | pointer(va_arg(args, void*), &pos); |
202 | } | 205 | } |
203 | break; | 206 | break; |
204 | case 's': | 207 | case 's': |
205 | s = va_arg(args, const char *); | 208 | s = va_arg(args, const char *); |
206 | for (; *s; ++s) | 209 | for (; *s; ++s) |
207 | *pos++ = *s; | 210 | *pos++ = *s; |
208 | break; | 211 | break; |
209 | case 'u': | 212 | case 'u': |
210 | uint2dec(va_arg(args, u32), &pos); | 213 | uint2dec(va_arg(args, u32), &pos); |
211 | break; | 214 | break; |
212 | default: | 215 | default: |
213 | break; | 216 | break; |
214 | } | 217 | } |
215 | break; | 218 | break; |
216 | default: | 219 | default: |
217 | *pos++ = *c; | 220 | *pos++ = *c; |
218 | } | 221 | } |
219 | } | 222 | } |
220 | va_end(args); | 223 | va_end(args); |
221 | *pos = 0; | 224 | *pos = 0; |
222 | con_out->output_string(con_out, buf); | 225 | con_out->output_string(con_out, buf); |
226 | if (color >= 0) | ||
227 | con_out->set_attribute(con_out, EFI_LIGHTGRAY); | ||
223 | } | 228 | } |
224 | 229 | ||
225 | /* | 230 | /* |
226 | * Reads an Unicode character from the input device. | 231 | * Reads an Unicode character from the input device. |
227 | * | 232 | * |
228 | * @return: Unicode character | 233 | * @return: Unicode character |
229 | */ | 234 | */ |
230 | u16 efi_st_get_key(void) | 235 | u16 efi_st_get_key(void) |
231 | { | 236 | { |
232 | struct efi_input_key input_key; | 237 | struct efi_input_key input_key; |
233 | efi_status_t ret; | 238 | efi_status_t ret; |
234 | 239 | ||
235 | /* Wait for next key */ | 240 | /* Wait for next key */ |
236 | do { | 241 | do { |
237 | ret = con_in->read_key_stroke(con_in, &input_key); | 242 | ret = con_in->read_key_stroke(con_in, &input_key); |
238 | } while (ret == EFI_NOT_READY); | 243 | } while (ret == EFI_NOT_READY); |
239 | return input_key.unicode_char; | 244 | return input_key.unicode_char; |
240 | } | 245 | } |
241 | 246 |