1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * efi_selftest_textinput
4  *
5  * Copyright (c) 2018 Heinrich Schuchardt <xypron.glpk@gmx.de>
6  *
7  * Provides a unit test for the EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL.
8  * The unicode character and the scan code are printed for text
9  * input. To run the test:
10  *
11  *	setenv efi_selftest extended text input
12  *	bootefi selftest
13  */
14 
15 #include <efi_selftest.h>
16 
17 static const efi_guid_t text_input_ex_protocol_guid =
18 		EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID;
19 
20 static struct efi_simple_text_input_ex_protocol *con_in_ex;
21 
22 static struct efi_boot_services *boottime;
23 
24 static void *efi_key_notify_handle;
25 static bool efi_running;
26 
27 /**
28  * efi_key_notify_function() - key notification function
29  *
30  * This function is called when the registered key is hit.
31  *
32  * @key_data:		next key
33  * Return:		status code
34  */
35 static efi_status_t EFIAPI efi_key_notify_function
36 				(struct efi_key_data *key_data)
37 {
38 	efi_running = false;
39 
40 	return EFI_SUCCESS;
41 }
42 
43 /*
44  * Setup unit test.
45  *
46  * @handle:	handle of the loaded image
47  * @systable:	system table
48  * @return:	EFI_ST_SUCCESS for success
49  */
50 static int setup(const efi_handle_t handle,
51 		 const struct efi_system_table *systable)
52 {
53 	efi_status_t ret;
54 	struct efi_key_data key_data = {
55 		.key = {
56 			.scan_code = 0,
57 			.unicode_char = 0x18
58 		},
59 		.key_state = {
60 			.key_shift_state = EFI_SHIFT_STATE_VALID |
61 					   EFI_LEFT_CONTROL_PRESSED,
62 			.key_toggle_state = EFI_TOGGLE_STATE_INVALID,
63 		},
64 	};
65 
66 	boottime = systable->boottime;
67 
68 	ret = boottime->locate_protocol(&text_input_ex_protocol_guid, NULL,
69 					(void **)&con_in_ex);
70 	if (ret != EFI_SUCCESS) {
71 		con_in_ex = NULL;
72 		efi_st_error
73 			("Extended text input protocol is not available.\n");
74 		return EFI_ST_FAILURE;
75 	}
76 
77 	ret = con_in_ex->register_key_notify(con_in_ex, &key_data,
78 					     efi_key_notify_function,
79 					     &efi_key_notify_handle);
80 	if (ret != EFI_SUCCESS) {
81 		efi_key_notify_handle = NULL;
82 		efi_st_error
83 			("Notify function could not be registered.\n");
84 		return EFI_ST_FAILURE;
85 	}
86 	efi_running = true;
87 
88 	return EFI_ST_SUCCESS;
89 }
90 
91 /*
92  * Tear down unit test.
93  *
94  * Unregister notify function.
95  *
96  * @return:	EFI_ST_SUCCESS for success
97  */
98 static int teardown(void)
99 {
100 	efi_status_t ret;
101 
102 	ret = con_in_ex->unregister_key_notify
103 			(con_in_ex, efi_key_notify_handle);
104 	if (ret != EFI_SUCCESS) {
105 		efi_st_error
106 			("Notify function could not be registered.\n");
107 		return EFI_ST_FAILURE;
108 	}
109 
110 	return EFI_ST_SUCCESS;
111 }
112 /*
113  * Execute unit test.
114  *
115  * @return:	EFI_ST_SUCCESS for success
116  */
117 static int execute(void)
118 {
119 	struct efi_key_data input_key = { {0, 0}, {0, 0} };
120 	efi_status_t ret;
121 	efi_uintn_t index;
122 
123 	if (!con_in_ex) {
124 		efi_st_printf("Setup failed\n");
125 		return EFI_ST_FAILURE;
126 	}
127 
128 	/* Drain the console input */
129 	ret = con_in_ex->reset(con_in_ex, true);
130 	if (ret != EFI_SUCCESS) {
131 		efi_st_error("Reset failed\n");
132 		return EFI_ST_FAILURE;
133 	}
134 	ret = con_in_ex->read_key_stroke_ex(con_in_ex, &input_key);
135 	if (ret != EFI_NOT_READY) {
136 		efi_st_error("Empty buffer not reported\n");
137 		return EFI_ST_FAILURE;
138 	}
139 
140 	efi_st_printf("Waiting for your input\n");
141 	efi_st_printf("To terminate type 'CTRL+x'\n");
142 
143 	while (efi_running) {
144 		/* Wait for next key */
145 		ret = boottime->wait_for_event(1, &con_in_ex->wait_for_key_ex,
146 					       &index);
147 		if (ret != EFI_ST_SUCCESS) {
148 			efi_st_error("WaitForEvent failed\n");
149 			return EFI_ST_FAILURE;
150 		}
151 		ret = con_in_ex->read_key_stroke_ex(con_in_ex, &input_key);
152 		if (ret != EFI_SUCCESS) {
153 			efi_st_error("ReadKeyStroke failed\n");
154 			return EFI_ST_FAILURE;
155 		}
156 
157 		/* Allow 5 minutes until time out */
158 		boottime->set_watchdog_timer(300, 0, 0, NULL);
159 
160 		efi_st_printf("Unicode char %u (%ps), scan code %u (",
161 			      (unsigned int)input_key.key.unicode_char,
162 			      efi_st_translate_char(input_key.key.unicode_char),
163 			      (unsigned int)input_key.key.scan_code);
164 		if (input_key.key_state.key_shift_state &
165 		    EFI_SHIFT_STATE_VALID) {
166 			if (input_key.key_state.key_shift_state &
167 			    (EFI_LEFT_SHIFT_PRESSED | EFI_RIGHT_SHIFT_PRESSED))
168 				efi_st_printf("SHIFT+");
169 			if (input_key.key_state.key_shift_state &
170 			    (EFI_LEFT_ALT_PRESSED | EFI_RIGHT_ALT_PRESSED))
171 				efi_st_printf("ALT+");
172 			if (input_key.key_state.key_shift_state &
173 			    (EFI_LEFT_CONTROL_PRESSED |
174 			     EFI_RIGHT_CONTROL_PRESSED))
175 				efi_st_printf("CTRL+");
176 			if (input_key.key_state.key_shift_state &
177 			    (EFI_LEFT_LOGO_PRESSED | EFI_RIGHT_LOGO_PRESSED))
178 				efi_st_printf("META+");
179 			if (input_key.key_state.key_shift_state ==
180 			    EFI_SHIFT_STATE_VALID)
181 				efi_st_printf("+");
182 		}
183 
184 		efi_st_printf("%ps)\n",
185 			      efi_st_translate_code(input_key.key.scan_code));
186 
187 	}
188 	return EFI_ST_SUCCESS;
189 }
190 
191 EFI_UNIT_TEST(textinputex) = {
192 	.name = "extended text input",
193 	.phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
194 	.setup = setup,
195 	.execute = execute,
196 	.teardown = teardown,
197 	.on_request = true,
198 };
199