]>
Commit | Line | Data |
---|---|---|
c1311ad4 AG |
1 | /* |
2 | * EFI application console interface | |
3 | * | |
4 | * Copyright (c) 2016 Alexander Graf | |
5 | * | |
6 | * SPDX-License-Identifier: GPL-2.0+ | |
7 | */ | |
8 | ||
9 | #include <common.h> | |
78178bb0 | 10 | #include <charset.h> |
a18c5a83 | 11 | #include <dm/device.h> |
c1311ad4 | 12 | #include <efi_loader.h> |
a18c5a83 RC |
13 | #include <stdio_dev.h> |
14 | #include <video_console.h> | |
c1311ad4 | 15 | |
c1311ad4 AG |
16 | static bool console_size_queried; |
17 | ||
5be8b0a3 EV |
18 | #define EFI_COUT_MODE_2 2 |
19 | #define EFI_MAX_COUT_MODE 3 | |
20 | ||
21 | struct cout_mode { | |
22 | unsigned long columns; | |
23 | unsigned long rows; | |
24 | int present; | |
25 | }; | |
26 | ||
27 | static struct cout_mode efi_cout_modes[] = { | |
28 | /* EFI Mode 0 is 80x25 and always present */ | |
29 | { | |
30 | .columns = 80, | |
31 | .rows = 25, | |
32 | .present = 1, | |
33 | }, | |
34 | /* EFI Mode 1 is always 80x50 */ | |
35 | { | |
36 | .columns = 80, | |
37 | .rows = 50, | |
38 | .present = 0, | |
39 | }, | |
40 | /* Value are unknown until we query the console */ | |
41 | { | |
42 | .columns = 0, | |
43 | .rows = 0, | |
44 | .present = 0, | |
45 | }, | |
46 | }; | |
47 | ||
ebb4dd5b HS |
48 | const efi_guid_t efi_guid_text_output_protocol = |
49 | EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID; | |
50 | const efi_guid_t efi_guid_text_input_protocol = | |
51 | EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID; | |
c1311ad4 AG |
52 | |
53 | #define cESC '\x1b' | |
54 | #define ESC "\x1b" | |
55 | ||
5be8b0a3 | 56 | /* Default to mode 0 */ |
c1311ad4 | 57 | static struct simple_text_output_mode efi_con_mode = { |
5be8b0a3 | 58 | .max_mode = 1, |
c1311ad4 AG |
59 | .mode = 0, |
60 | .attribute = 0, | |
61 | .cursor_column = 0, | |
62 | .cursor_row = 0, | |
63 | .cursor_visible = 1, | |
64 | }; | |
65 | ||
66 | static int term_read_reply(int *n, int maxnum, char end_char) | |
67 | { | |
68 | char c; | |
69 | int i = 0; | |
70 | ||
71 | c = getc(); | |
72 | if (c != cESC) | |
73 | return -1; | |
74 | c = getc(); | |
75 | if (c != '[') | |
76 | return -1; | |
77 | ||
78 | n[0] = 0; | |
79 | while (1) { | |
80 | c = getc(); | |
81 | if (c == ';') { | |
82 | i++; | |
83 | if (i >= maxnum) | |
84 | return -1; | |
85 | n[i] = 0; | |
86 | continue; | |
87 | } else if (c == end_char) { | |
88 | break; | |
89 | } else if (c > '9' || c < '0') { | |
90 | return -1; | |
91 | } | |
92 | ||
93 | /* Read one more decimal position */ | |
94 | n[i] *= 10; | |
95 | n[i] += c - '0'; | |
96 | } | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
101 | static efi_status_t EFIAPI efi_cout_reset( | |
102 | struct efi_simple_text_output_protocol *this, | |
103 | char extended_verification) | |
104 | { | |
105 | EFI_ENTRY("%p, %d", this, extended_verification); | |
106 | return EFI_EXIT(EFI_UNSUPPORTED); | |
107 | } | |
108 | ||
c1311ad4 AG |
109 | static efi_status_t EFIAPI efi_cout_output_string( |
110 | struct efi_simple_text_output_protocol *this, | |
3a45bc7f | 111 | const efi_string_t string) |
c1311ad4 | 112 | { |
3a45bc7f RC |
113 | struct simple_text_output_mode *con = &efi_con_mode; |
114 | struct cout_mode *mode = &efi_cout_modes[con->mode]; | |
c1311ad4 AG |
115 | |
116 | EFI_ENTRY("%p, %p", this, string); | |
3a45bc7f RC |
117 | |
118 | unsigned int n16 = utf16_strlen(string); | |
119 | char buf[MAX_UTF8_PER_UTF16 * n16 + 1]; | |
120 | char *p; | |
121 | ||
122 | *utf16_to_utf8((u8 *)buf, string, n16) = '\0'; | |
123 | ||
124 | fputs(stdout, buf); | |
125 | ||
126 | for (p = buf; *p; p++) { | |
127 | switch (*p) { | |
128 | case '\r': /* carriage-return */ | |
129 | con->cursor_column = 0; | |
130 | break; | |
131 | case '\n': /* newline */ | |
132 | con->cursor_column = 0; | |
133 | con->cursor_row++; | |
134 | break; | |
135 | case '\t': /* tab, assume 8 char align */ | |
136 | break; | |
137 | case '\b': /* backspace */ | |
138 | con->cursor_column = max(0, con->cursor_column - 1); | |
139 | break; | |
140 | default: | |
141 | con->cursor_column++; | |
142 | break; | |
143 | } | |
144 | if (con->cursor_column >= mode->columns) { | |
145 | con->cursor_column = 0; | |
146 | con->cursor_row++; | |
c1311ad4 | 147 | } |
3a45bc7f | 148 | con->cursor_row = min(con->cursor_row, (s32)mode->rows - 1); |
c1311ad4 AG |
149 | } |
150 | ||
151 | return EFI_EXIT(EFI_SUCCESS); | |
152 | } | |
153 | ||
154 | static efi_status_t EFIAPI efi_cout_test_string( | |
155 | struct efi_simple_text_output_protocol *this, | |
3a45bc7f | 156 | const efi_string_t string) |
c1311ad4 AG |
157 | { |
158 | EFI_ENTRY("%p, %p", this, string); | |
159 | return EFI_EXIT(EFI_SUCCESS); | |
160 | } | |
161 | ||
5be8b0a3 EV |
162 | static bool cout_mode_matches(struct cout_mode *mode, int rows, int cols) |
163 | { | |
164 | if (!mode->present) | |
165 | return false; | |
166 | ||
167 | return (mode->rows == rows) && (mode->columns == cols); | |
168 | } | |
169 | ||
71cc25c3 RC |
170 | static int query_console_serial(int *rows, int *cols) |
171 | { | |
172 | /* Ask the terminal about its size */ | |
173 | int n[3]; | |
174 | u64 timeout; | |
175 | ||
176 | /* Empty input buffer */ | |
177 | while (tstc()) | |
178 | getc(); | |
179 | ||
180 | printf(ESC"[18t"); | |
181 | ||
182 | /* Check if we have a terminal that understands */ | |
183 | timeout = timer_get_us() + 1000000; | |
184 | while (!tstc()) | |
185 | if (timer_get_us() > timeout) | |
186 | return -1; | |
187 | ||
188 | /* Read {depth,rows,cols} */ | |
189 | if (term_read_reply(n, 3, 't')) | |
190 | return -1; | |
191 | ||
192 | *cols = n[2]; | |
193 | *rows = n[1]; | |
194 | ||
195 | return 0; | |
196 | } | |
197 | ||
c1311ad4 AG |
198 | static efi_status_t EFIAPI efi_cout_query_mode( |
199 | struct efi_simple_text_output_protocol *this, | |
200 | unsigned long mode_number, unsigned long *columns, | |
201 | unsigned long *rows) | |
202 | { | |
203 | EFI_ENTRY("%p, %ld, %p, %p", this, mode_number, columns, rows); | |
204 | ||
205 | if (!console_size_queried) { | |
a18c5a83 | 206 | const char *stdout_name = env_get("stdout"); |
71cc25c3 | 207 | int rows, cols; |
c1311ad4 AG |
208 | |
209 | console_size_queried = true; | |
210 | ||
a18c5a83 RC |
211 | if (stdout_name && !strcmp(stdout_name, "vidconsole") && |
212 | IS_ENABLED(CONFIG_DM_VIDEO)) { | |
213 | struct stdio_dev *stdout_dev = | |
214 | stdio_get_by_name("vidconsole"); | |
215 | struct udevice *dev = stdout_dev->priv; | |
216 | struct vidconsole_priv *priv = | |
217 | dev_get_uclass_priv(dev); | |
218 | rows = priv->rows; | |
219 | cols = priv->cols; | |
220 | } else if (query_console_serial(&rows, &cols)) { | |
c1311ad4 | 221 | goto out; |
a18c5a83 | 222 | } |
5be8b0a3 EV |
223 | |
224 | /* Test if we can have Mode 1 */ | |
225 | if (cols >= 80 && rows >= 50) { | |
226 | efi_cout_modes[1].present = 1; | |
227 | efi_con_mode.max_mode = 2; | |
228 | } | |
229 | ||
230 | /* | |
231 | * Install our mode as mode 2 if it is different | |
232 | * than mode 0 or 1 and set it as the currently selected mode | |
233 | */ | |
234 | if (!cout_mode_matches(&efi_cout_modes[0], rows, cols) && | |
235 | !cout_mode_matches(&efi_cout_modes[1], rows, cols)) { | |
236 | efi_cout_modes[EFI_COUT_MODE_2].columns = cols; | |
237 | efi_cout_modes[EFI_COUT_MODE_2].rows = rows; | |
238 | efi_cout_modes[EFI_COUT_MODE_2].present = 1; | |
239 | efi_con_mode.max_mode = EFI_MAX_COUT_MODE; | |
240 | efi_con_mode.mode = EFI_COUT_MODE_2; | |
241 | } | |
c1311ad4 AG |
242 | } |
243 | ||
5be8b0a3 EV |
244 | if (mode_number >= efi_con_mode.max_mode) |
245 | return EFI_EXIT(EFI_UNSUPPORTED); | |
246 | ||
247 | if (efi_cout_modes[mode_number].present != 1) | |
248 | return EFI_EXIT(EFI_UNSUPPORTED); | |
249 | ||
c1311ad4 AG |
250 | out: |
251 | if (columns) | |
5be8b0a3 | 252 | *columns = efi_cout_modes[mode_number].columns; |
c1311ad4 | 253 | if (rows) |
5be8b0a3 | 254 | *rows = efi_cout_modes[mode_number].rows; |
c1311ad4 AG |
255 | |
256 | return EFI_EXIT(EFI_SUCCESS); | |
257 | } | |
258 | ||
259 | static efi_status_t EFIAPI efi_cout_set_mode( | |
260 | struct efi_simple_text_output_protocol *this, | |
261 | unsigned long mode_number) | |
262 | { | |
263 | EFI_ENTRY("%p, %ld", this, mode_number); | |
264 | ||
c1311ad4 | 265 | |
5be8b0a3 EV |
266 | if (mode_number > efi_con_mode.max_mode) |
267 | return EFI_EXIT(EFI_UNSUPPORTED); | |
268 | ||
269 | efi_con_mode.mode = mode_number; | |
270 | efi_con_mode.cursor_column = 0; | |
271 | efi_con_mode.cursor_row = 0; | |
272 | ||
273 | return EFI_EXIT(EFI_SUCCESS); | |
c1311ad4 AG |
274 | } |
275 | ||
2d5dc2a5 RC |
276 | static const struct { |
277 | unsigned int fg; | |
278 | unsigned int bg; | |
279 | } color[] = { | |
280 | { 30, 40 }, /* 0: black */ | |
281 | { 34, 44 }, /* 1: blue */ | |
282 | { 32, 42 }, /* 2: green */ | |
283 | { 36, 46 }, /* 3: cyan */ | |
284 | { 31, 41 }, /* 4: red */ | |
285 | { 35, 45 }, /* 5: magenta */ | |
286 | { 33, 43 }, /* 6: brown, map to yellow as edk2 does*/ | |
287 | { 37, 47 }, /* 7: light grey, map to white */ | |
288 | }; | |
289 | ||
290 | /* See EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.SetAttribute(). */ | |
c1311ad4 AG |
291 | static efi_status_t EFIAPI efi_cout_set_attribute( |
292 | struct efi_simple_text_output_protocol *this, | |
293 | unsigned long attribute) | |
294 | { | |
2d5dc2a5 RC |
295 | unsigned int bold = EFI_ATTR_BOLD(attribute); |
296 | unsigned int fg = EFI_ATTR_FG(attribute); | |
297 | unsigned int bg = EFI_ATTR_BG(attribute); | |
298 | ||
c1311ad4 AG |
299 | EFI_ENTRY("%p, %lx", this, attribute); |
300 | ||
2d5dc2a5 RC |
301 | if (attribute) |
302 | printf(ESC"[%u;%u;%um", bold, color[fg].fg, color[bg].bg); | |
303 | else | |
304 | printf(ESC"[0;37;40m"); | |
305 | ||
306 | return EFI_EXIT(EFI_SUCCESS); | |
c1311ad4 AG |
307 | } |
308 | ||
309 | static efi_status_t EFIAPI efi_cout_clear_screen( | |
310 | struct efi_simple_text_output_protocol *this) | |
311 | { | |
312 | EFI_ENTRY("%p", this); | |
313 | ||
314 | printf(ESC"[2J"); | |
315 | ||
316 | return EFI_EXIT(EFI_SUCCESS); | |
317 | } | |
318 | ||
319 | static efi_status_t EFIAPI efi_cout_set_cursor_position( | |
320 | struct efi_simple_text_output_protocol *this, | |
321 | unsigned long column, unsigned long row) | |
322 | { | |
323 | EFI_ENTRY("%p, %ld, %ld", this, column, row); | |
324 | ||
325 | printf(ESC"[%d;%df", (int)row, (int)column); | |
326 | efi_con_mode.cursor_column = column; | |
327 | efi_con_mode.cursor_row = row; | |
328 | ||
329 | return EFI_EXIT(EFI_SUCCESS); | |
330 | } | |
331 | ||
332 | static efi_status_t EFIAPI efi_cout_enable_cursor( | |
333 | struct efi_simple_text_output_protocol *this, | |
334 | bool enable) | |
335 | { | |
336 | EFI_ENTRY("%p, %d", this, enable); | |
337 | ||
338 | printf(ESC"[?25%c", enable ? 'h' : 'l'); | |
339 | ||
340 | return EFI_EXIT(EFI_SUCCESS); | |
341 | } | |
342 | ||
ebb4dd5b | 343 | struct efi_simple_text_output_protocol efi_con_out = { |
c1311ad4 AG |
344 | .reset = efi_cout_reset, |
345 | .output_string = efi_cout_output_string, | |
346 | .test_string = efi_cout_test_string, | |
347 | .query_mode = efi_cout_query_mode, | |
348 | .set_mode = efi_cout_set_mode, | |
349 | .set_attribute = efi_cout_set_attribute, | |
350 | .clear_screen = efi_cout_clear_screen, | |
351 | .set_cursor_position = efi_cout_set_cursor_position, | |
352 | .enable_cursor = efi_cout_enable_cursor, | |
353 | .mode = (void*)&efi_con_mode, | |
354 | }; | |
355 | ||
356 | static efi_status_t EFIAPI efi_cin_reset( | |
357 | struct efi_simple_input_interface *this, | |
358 | bool extended_verification) | |
359 | { | |
360 | EFI_ENTRY("%p, %d", this, extended_verification); | |
361 | return EFI_EXIT(EFI_UNSUPPORTED); | |
362 | } | |
363 | ||
364 | static efi_status_t EFIAPI efi_cin_read_key_stroke( | |
365 | struct efi_simple_input_interface *this, | |
366 | struct efi_input_key *key) | |
367 | { | |
368 | struct efi_input_key pressed_key = { | |
369 | .scan_code = 0, | |
370 | .unicode_char = 0, | |
371 | }; | |
372 | char ch; | |
373 | ||
374 | EFI_ENTRY("%p, %p", this, key); | |
375 | ||
376 | /* We don't do interrupts, so check for timers cooperatively */ | |
377 | efi_timer_check(); | |
378 | ||
379 | if (!tstc()) { | |
380 | /* No key pressed */ | |
381 | return EFI_EXIT(EFI_NOT_READY); | |
382 | } | |
383 | ||
384 | ch = getc(); | |
385 | if (ch == cESC) { | |
386 | /* Escape Sequence */ | |
387 | ch = getc(); | |
388 | switch (ch) { | |
389 | case cESC: /* ESC */ | |
390 | pressed_key.scan_code = 23; | |
391 | break; | |
392 | case 'O': /* F1 - F4 */ | |
393 | pressed_key.scan_code = getc() - 'P' + 11; | |
394 | break; | |
395 | case 'a'...'z': | |
396 | ch = ch - 'a'; | |
397 | break; | |
398 | case '[': | |
399 | ch = getc(); | |
400 | switch (ch) { | |
401 | case 'A'...'D': /* up, down right, left */ | |
402 | pressed_key.scan_code = ch - 'A' + 1; | |
403 | break; | |
404 | case 'F': /* End */ | |
405 | pressed_key.scan_code = 6; | |
406 | break; | |
407 | case 'H': /* Home */ | |
408 | pressed_key.scan_code = 5; | |
409 | break; | |
410 | case '1': /* F5 - F8 */ | |
411 | pressed_key.scan_code = getc() - '0' + 11; | |
412 | getc(); | |
413 | break; | |
414 | case '2': /* F9 - F12 */ | |
415 | pressed_key.scan_code = getc() - '0' + 19; | |
416 | getc(); | |
417 | break; | |
418 | case '3': /* DEL */ | |
419 | pressed_key.scan_code = 8; | |
420 | getc(); | |
421 | break; | |
422 | } | |
423 | break; | |
424 | } | |
425 | } else if (ch == 0x7f) { | |
426 | /* Backspace */ | |
427 | ch = 0x08; | |
428 | } | |
429 | pressed_key.unicode_char = ch; | |
430 | *key = pressed_key; | |
431 | ||
432 | return EFI_EXIT(EFI_SUCCESS); | |
433 | } | |
434 | ||
91be9a77 | 435 | struct efi_simple_input_interface efi_con_in = { |
c1311ad4 AG |
436 | .reset = efi_cin_reset, |
437 | .read_key_stroke = efi_cin_read_key_stroke, | |
438 | .wait_for_key = NULL, | |
439 | }; | |
91be9a77 HS |
440 | |
441 | static struct efi_event *console_timer_event; | |
442 | ||
ff925938 | 443 | static void EFIAPI efi_key_notify(struct efi_event *event, void *context) |
91be9a77 HS |
444 | { |
445 | } | |
446 | ||
9bc9664d HS |
447 | /* |
448 | * Notification function of the console timer event. | |
449 | * | |
450 | * event: console timer event | |
451 | * context: not used | |
452 | */ | |
ff925938 HS |
453 | static void EFIAPI efi_console_timer_notify(struct efi_event *event, |
454 | void *context) | |
91be9a77 HS |
455 | { |
456 | EFI_ENTRY("%p, %p", event, context); | |
9bc9664d HS |
457 | |
458 | /* Check if input is available */ | |
ca62a4f5 | 459 | if (tstc()) { |
9bc9664d | 460 | /* Queue the wait for key event */ |
e190e897 | 461 | efi_con_in.wait_for_key->is_signaled = true; |
9bc9664d HS |
462 | efi_signal_event(efi_con_in.wait_for_key, true); |
463 | } | |
91be9a77 HS |
464 | EFI_EXIT(EFI_SUCCESS); |
465 | } | |
466 | ||
467 | /* This gets called from do_bootefi_exec(). */ | |
468 | int efi_console_register(void) | |
469 | { | |
470 | efi_status_t r; | |
ebb4dd5b HS |
471 | struct efi_object *efi_console_output_obj; |
472 | struct efi_object *efi_console_input_obj; | |
a17e62cc | 473 | |
ebb4dd5b | 474 | /* Create handles */ |
2074f700 | 475 | r = efi_create_handle((efi_handle_t *)&efi_console_output_obj); |
ebb4dd5b HS |
476 | if (r != EFI_SUCCESS) |
477 | goto out_of_memory; | |
478 | r = efi_add_protocol(efi_console_output_obj->handle, | |
479 | &efi_guid_text_output_protocol, &efi_con_out); | |
480 | if (r != EFI_SUCCESS) | |
481 | goto out_of_memory; | |
2074f700 | 482 | r = efi_create_handle((efi_handle_t *)&efi_console_input_obj); |
ebb4dd5b HS |
483 | if (r != EFI_SUCCESS) |
484 | goto out_of_memory; | |
485 | r = efi_add_protocol(efi_console_input_obj->handle, | |
486 | &efi_guid_text_input_protocol, &efi_con_in); | |
487 | if (r != EFI_SUCCESS) | |
488 | goto out_of_memory; | |
a17e62cc | 489 | |
ebb4dd5b | 490 | /* Create console events */ |
91be9a77 HS |
491 | r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, |
492 | efi_key_notify, NULL, &efi_con_in.wait_for_key); | |
493 | if (r != EFI_SUCCESS) { | |
494 | printf("ERROR: Failed to register WaitForKey event\n"); | |
495 | return r; | |
496 | } | |
497 | r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, | |
498 | efi_console_timer_notify, NULL, | |
499 | &console_timer_event); | |
500 | if (r != EFI_SUCCESS) { | |
501 | printf("ERROR: Failed to register console event\n"); | |
502 | return r; | |
503 | } | |
504 | /* 5000 ns cycle is sufficient for 2 MBaud */ | |
505 | r = efi_set_timer(console_timer_event, EFI_TIMER_PERIODIC, 50); | |
506 | if (r != EFI_SUCCESS) | |
507 | printf("ERROR: Failed to set console timer\n"); | |
508 | return r; | |
ebb4dd5b HS |
509 | out_of_memory: |
510 | printf("ERROR: Out of meemory\n"); | |
511 | return r; | |
91be9a77 | 512 | } |