xref: /freebsd/stand/efi/libefi/efi_console.c (revision 0bd5d367989b3d2de0e8d8ceaa2e31d3f0d96536)
1 /*-
2  * Copyright (c) 2000 Doug Rabson
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <efi.h>
31 #include <efilib.h>
32 
33 #include "bootstrap.h"
34 
35 static EFI_GUID simple_input_ex_guid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID;
36 static SIMPLE_TEXT_OUTPUT_INTERFACE	*conout;
37 static SIMPLE_INPUT_INTERFACE		*conin;
38 static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *coninex;
39 
40 #ifdef TERM_EMU
41 #define	DEFAULT_FGCOLOR	EFI_LIGHTGRAY
42 #define	DEFAULT_BGCOLOR	EFI_BLACK
43 
44 #define	MAXARGS	8
45 static int args[MAXARGS], argc;
46 static int fg_c, bg_c, curx, cury;
47 static int esc;
48 
49 void get_pos(int *x, int *y);
50 void curs_move(int *_x, int *_y, int x, int y);
51 static void CL(int);
52 void HO(void);
53 void end_term(void);
54 #endif
55 
56 #define	KEYBUFSZ 10
57 static unsigned keybuf[KEYBUFSZ];	/* keybuf for extended codes */
58 static int key_pending;
59 
60 static void efi_cons_probe(struct console *);
61 static int efi_cons_init(int);
62 void efi_cons_putchar(int);
63 int efi_cons_getchar(void);
64 void efi_cons_efiputchar(int);
65 int efi_cons_poll(void);
66 
67 struct console efi_console = {
68 	"efi",
69 	"EFI console",
70 	C_WIDEOUT,
71 	efi_cons_probe,
72 	efi_cons_init,
73 	efi_cons_putchar,
74 	efi_cons_getchar,
75 	efi_cons_poll
76 };
77 
78 #ifdef TERM_EMU
79 
80 /* Get cursor position. */
81 void
82 get_pos(int *x, int *y)
83 {
84 	*x = conout->Mode->CursorColumn;
85 	*y = conout->Mode->CursorRow;
86 }
87 
88 /* Move cursor to x rows and y cols (0-based). */
89 void
90 curs_move(int *_x, int *_y, int x, int y)
91 {
92 	conout->SetCursorPosition(conout, x, y);
93 	if (_x != NULL)
94 		*_x = conout->Mode->CursorColumn;
95 	if (_y != NULL)
96 		*_y = conout->Mode->CursorRow;
97 }
98 
99 /* Clear internal state of the terminal emulation code. */
100 void
101 end_term(void)
102 {
103 	esc = 0;
104 	argc = -1;
105 }
106 
107 #endif
108 
109 static void
110 efi_cons_probe(struct console *cp)
111 {
112 	conout = ST->ConOut;
113 	conin = ST->ConIn;
114 	cp->c_flags |= C_PRESENTIN | C_PRESENTOUT;
115 }
116 
117 static int
118 efi_cons_init(int arg)
119 {
120 	EFI_STATUS status;
121 
122 #ifdef TERM_EMU
123 	conout->SetAttribute(conout, EFI_TEXT_ATTR(DEFAULT_FGCOLOR,
124 	    DEFAULT_BGCOLOR));
125 	end_term();
126 	get_pos(&curx, &cury);
127 	curs_move(&curx, &cury, curx, cury);
128 	fg_c = DEFAULT_FGCOLOR;
129 	bg_c = DEFAULT_BGCOLOR;
130 #endif
131 	conout->EnableCursor(conout, TRUE);
132 	status = BS->OpenProtocol(ST->ConsoleInHandle, &simple_input_ex_guid,
133 	    (void **)&coninex, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
134 	if (status != EFI_SUCCESS)
135 		coninex = NULL;
136 	return (0);
137 }
138 
139 static void
140 efi_cons_rawputchar(int c)
141 {
142 	int i;
143 	UINTN x, y;
144 	conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
145 
146 	if (c == '\t') {
147 		int n;
148 
149 		n = 8 - ((conout->Mode->CursorColumn + 8) % 8);
150 		for (i = 0; i < n; i++)
151 			efi_cons_rawputchar(' ');
152 	} else {
153 #ifndef	TERM_EMU
154 		if (c == '\n')
155 			efi_cons_efiputchar('\r');
156 		efi_cons_efiputchar(c);
157 #else
158 		switch (c) {
159 		case '\r':
160 			curx = 0;
161 			efi_cons_efiputchar('\r');
162 			return;
163 		case '\n':
164 			efi_cons_efiputchar('\n');
165 			efi_cons_efiputchar('\r');
166 			cury++;
167 			if (cury >= y)
168 				cury--;
169 			curx = 0;
170 			return;
171 		case '\b':
172 			if (curx > 0) {
173 				efi_cons_efiputchar('\b');
174 				curx--;
175 			}
176 			return;
177 		default:
178 			efi_cons_efiputchar(c);
179 			curx++;
180 			if (curx > x-1) {
181 				curx = 0;
182 				cury++;
183 			}
184 			if (cury > y-1) {
185 				curx = 0;
186 				cury--;
187 			}
188 		}
189 #endif
190 	}
191 }
192 
193 #ifdef TERM_EMU
194 /* Gracefully exit ESC-sequence processing in case of misunderstanding. */
195 static void
196 bail_out(int c)
197 {
198 	char buf[16], *ch;
199 	int i;
200 
201 	if (esc) {
202 		efi_cons_rawputchar('\033');
203 		if (esc != '\033')
204 			efi_cons_rawputchar(esc);
205 		for (i = 0; i <= argc; ++i) {
206 			sprintf(buf, "%d", args[i]);
207 			ch = buf;
208 			while (*ch)
209 				efi_cons_rawputchar(*ch++);
210 		}
211 	}
212 	efi_cons_rawputchar(c);
213 	end_term();
214 }
215 
216 /* Clear display from current position to end of screen. */
217 static void
218 CD(void) {
219 	int i;
220 	UINTN x, y;
221 
222 	get_pos(&curx, &cury);
223 	if (curx == 0 && cury == 0) {
224 		conout->ClearScreen(conout);
225 		end_term();
226 		return;
227 	}
228 
229 	conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
230 	CL(0);  /* clear current line from cursor to end */
231 	for (i = cury + 1; i < y-1; i++) {
232 		curs_move(NULL, NULL, 0, i);
233 		CL(0);
234 	}
235 	curs_move(NULL, NULL, curx, cury);
236 	end_term();
237 }
238 
239 /*
240  * Absolute cursor move to args[0] rows and args[1] columns
241  * (the coordinates are 1-based).
242  */
243 static void
244 CM(void)
245 {
246 	if (args[0] > 0)
247 		args[0]--;
248 	if (args[1] > 0)
249 		args[1]--;
250 	curs_move(&curx, &cury, args[1], args[0]);
251 	end_term();
252 }
253 
254 /* Home cursor (left top corner), also called from mode command. */
255 void
256 HO(void)
257 {
258 	argc = 1;
259 	args[0] = args[1] = 1;
260 	CM();
261 }
262 
263 /* Clear line from current position to end of line */
264 static void
265 CL(int direction)
266 {
267 	int i, len;
268 	UINTN x, y;
269 	CHAR16 *line;
270 
271 	conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
272 	switch (direction) {
273 	case 0:         /* from cursor to end */
274 		len = x - curx + 1;
275 		break;
276 	case 1:         /* from beginning to cursor */
277 		len = curx;
278 		break;
279 	case 2:         /* entire line */
280 		len = x;
281 		break;
282 	default:	/* NOTREACHED */
283 		__unreachable();
284 	}
285 
286 	if (cury == y - 1)
287 		len--;
288 
289 	line = malloc(len * sizeof (CHAR16));
290 	if (line == NULL) {
291 		printf("out of memory\n");
292 		return;
293 	}
294 	for (i = 0; i < len; i++)
295 		line[i] = ' ';
296 	line[len-1] = 0;
297 
298 	if (direction != 0)
299 		curs_move(NULL, NULL, 0, cury);
300 
301 	conout->OutputString(conout, line);
302 	/* restore cursor position */
303 	curs_move(NULL, NULL, curx, cury);
304 	free(line);
305 	end_term();
306 }
307 
308 static void
309 get_arg(int c)
310 {
311 	if (argc < 0)
312 		argc = 0;
313 	args[argc] *= 10;
314 	args[argc] += c - '0';
315 }
316 
317 /* Emulate basic capabilities of cons25 terminal */
318 static void
319 efi_term_emu(int c)
320 {
321 	static int ansi_col[] = {
322 		0, 4, 2, 6, 1, 5, 3, 7
323 	};
324 	int t, i;
325 
326 	switch (esc) {
327 	case 0:
328 		switch (c) {
329 		case '\033':
330 			esc = c;
331 			break;
332 		default:
333 			efi_cons_rawputchar(c);
334 			break;
335 		}
336 		break;
337 	case '\033':
338 		switch (c) {
339 		case '[':
340 			esc = c;
341 			args[0] = 0;
342 			argc = -1;
343 			break;
344 		default:
345 			bail_out(c);
346 			break;
347 		}
348 		break;
349 	case '[':
350 		switch (c) {
351 		case ';':
352 			if (argc < 0)
353 				argc = 0;
354 			else if (argc + 1 >= MAXARGS)
355 				bail_out(c);
356 			else
357 				args[++argc] = 0;
358 			break;
359 		case 'H':               /* ho = \E[H */
360 			if (argc < 0)
361 				HO();
362 			else if (argc == 1)
363 				CM();
364 			else
365 				bail_out(c);
366 			break;
367 		case 'J':               /* cd = \E[J */
368 			if (argc < 0)
369 				CD();
370 			else
371 				bail_out(c);
372 			break;
373 		case 'm':
374 			if (argc < 0) {
375 				fg_c = DEFAULT_FGCOLOR;
376 				bg_c = DEFAULT_BGCOLOR;
377 			}
378 			for (i = 0; i <= argc; ++i) {
379 				switch (args[i]) {
380 				case 0:         /* back to normal */
381 					fg_c = DEFAULT_FGCOLOR;
382 					bg_c = DEFAULT_BGCOLOR;
383 					break;
384 				case 1:         /* bold */
385 					fg_c |= 0x8;
386 					break;
387 				case 4:         /* underline */
388 				case 5:         /* blink */
389 					bg_c |= 0x8;
390 					break;
391 				case 7:         /* reverse */
392 					t = fg_c;
393 					fg_c = bg_c;
394 					bg_c = t;
395 					break;
396 				case 22:	/* normal intensity */
397 					fg_c &= ~0x8;
398 					break;
399 				case 24:	/* not underline */
400 				case 25:	/* not blinking */
401 					bg_c &= ~0x8;
402 					break;
403 				case 30: case 31: case 32: case 33:
404 				case 34: case 35: case 36: case 37:
405 					fg_c = ansi_col[args[i] - 30];
406 					break;
407 				case 39:        /* normal */
408 					fg_c = DEFAULT_FGCOLOR;
409 					break;
410 				case 40: case 41: case 42: case 43:
411 				case 44: case 45: case 46: case 47:
412 					bg_c = ansi_col[args[i] - 40];
413 					break;
414 				case 49:        /* normal */
415 					bg_c = DEFAULT_BGCOLOR;
416 					break;
417 				}
418 			}
419 			conout->SetAttribute(conout, EFI_TEXT_ATTR(fg_c, bg_c));
420 			end_term();
421 			break;
422 		default:
423 			if (isdigit(c))
424 				get_arg(c);
425 			else
426 				bail_out(c);
427 			break;
428 		}
429 		break;
430 	default:
431 		bail_out(c);
432 		break;
433 	}
434 }
435 #else
436 void
437 HO(void)
438 {
439 }
440 #endif
441 
442 void
443 efi_cons_putchar(int c)
444 {
445 #ifdef TERM_EMU
446 	efi_term_emu(c);
447 #else
448 	efi_cons_rawputchar(c);
449 #endif
450 }
451 
452 static int
453 keybuf_getchar(void)
454 {
455 	int i, c = 0;
456 
457 	for (i = 0; i < KEYBUFSZ; i++) {
458 		if (keybuf[i] != 0) {
459 			c = keybuf[i];
460 			keybuf[i] = 0;
461 			break;
462 		}
463 	}
464 
465 	return (c);
466 }
467 
468 static bool
469 keybuf_ischar(void)
470 {
471 	int i;
472 
473 	for (i = 0; i < KEYBUFSZ; i++) {
474 		if (keybuf[i] != 0)
475 			return (true);
476 	}
477 	return (false);
478 }
479 
480 /*
481  * We are not reading input before keybuf is empty, so we are safe
482  * just to fill keybuf from the beginning.
483  */
484 static void
485 keybuf_inschar(EFI_INPUT_KEY *key)
486 {
487 
488 	switch (key->ScanCode) {
489 	case SCAN_UP: /* UP */
490 		keybuf[0] = 0x1b;	/* esc */
491 		keybuf[1] = '[';
492 		keybuf[2] = 'A';
493 		break;
494 	case SCAN_DOWN: /* DOWN */
495 		keybuf[0] = 0x1b;	/* esc */
496 		keybuf[1] = '[';
497 		keybuf[2] = 'B';
498 		break;
499 	case SCAN_RIGHT: /* RIGHT */
500 		keybuf[0] = 0x1b;	/* esc */
501 		keybuf[1] = '[';
502 		keybuf[2] = 'C';
503 		break;
504 	case SCAN_LEFT: /* LEFT */
505 		keybuf[0] = 0x1b;	/* esc */
506 		keybuf[1] = '[';
507 		keybuf[2] = 'D';
508 		break;
509 	case SCAN_DELETE:
510 		keybuf[0] = CHAR_BACKSPACE;
511 		break;
512 	case SCAN_ESC:
513 		keybuf[0] = 0x1b;	/* esc */
514 		break;
515 	default:
516 		keybuf[0] = key->UnicodeChar;
517 		break;
518 	}
519 }
520 
521 static bool
522 efi_readkey(void)
523 {
524 	EFI_STATUS status;
525 	EFI_INPUT_KEY key;
526 
527 	status = conin->ReadKeyStroke(conin, &key);
528 	if (status == EFI_SUCCESS) {
529 		keybuf_inschar(&key);
530 		return (true);
531 	}
532 	return (false);
533 }
534 
535 static bool
536 efi_readkey_ex(void)
537 {
538 	EFI_STATUS status;
539 	EFI_INPUT_KEY *kp;
540 	EFI_KEY_DATA  key_data;
541 	uint32_t kss;
542 
543 	status = coninex->ReadKeyStrokeEx(coninex, &key_data);
544 	if (status == EFI_SUCCESS) {
545 		kss = key_data.KeyState.KeyShiftState;
546 		kp = &key_data.Key;
547 		if (kss & EFI_SHIFT_STATE_VALID) {
548 
549 			/*
550 			 * quick mapping to control chars, replace with
551 			 * map lookup later.
552 			 */
553 			if (kss & EFI_RIGHT_CONTROL_PRESSED ||
554 			    kss & EFI_LEFT_CONTROL_PRESSED) {
555 				if (kp->UnicodeChar >= 'a' &&
556 				    kp->UnicodeChar <= 'z') {
557 					kp->UnicodeChar -= 'a';
558 					kp->UnicodeChar++;
559 				}
560 			}
561 		}
562 
563 		keybuf_inschar(kp);
564 		return (true);
565 	}
566 	return (false);
567 }
568 
569 int
570 efi_cons_getchar(void)
571 {
572 	int c;
573 
574 	if ((c = keybuf_getchar()) != 0)
575 		return (c);
576 
577 	key_pending = 0;
578 
579 	if (coninex == NULL) {
580 		if (efi_readkey())
581 			return (keybuf_getchar());
582 	} else {
583 		if (efi_readkey_ex())
584 			return (keybuf_getchar());
585 	}
586 
587 	return (-1);
588 }
589 
590 int
591 efi_cons_poll(void)
592 {
593 	EFI_STATUS status;
594 
595 	if (keybuf_ischar() || key_pending)
596 		return (1);
597 
598 	/*
599 	 * Some EFI implementation (u-boot for example) do not support
600 	 * WaitForKey().
601 	 * CheckEvent() can clear the signaled state.
602 	 */
603 	if (coninex != NULL) {
604 		if (coninex->WaitForKeyEx == NULL) {
605 			key_pending = efi_readkey_ex();
606 		} else {
607 			status = BS->CheckEvent(coninex->WaitForKeyEx);
608 			key_pending = status == EFI_SUCCESS;
609 		}
610 	} else {
611 		if (conin->WaitForKey == NULL) {
612 			key_pending = efi_readkey();
613 		} else {
614 			status = BS->CheckEvent(conin->WaitForKey);
615 			key_pending = status == EFI_SUCCESS;
616 		}
617 	}
618 
619 	return (key_pending);
620 }
621 
622 /* Plain direct access to EFI OutputString(). */
623 void
624 efi_cons_efiputchar(int c)
625 {
626 	CHAR16 buf[2];
627 
628 	/*
629 	 * translate box chars to unicode
630 	 */
631 	switch (c) {
632 	/* single frame */
633 	case 0xb3: buf[0] = BOXDRAW_VERTICAL; break;
634 	case 0xbf: buf[0] = BOXDRAW_DOWN_LEFT; break;
635 	case 0xc0: buf[0] = BOXDRAW_UP_RIGHT; break;
636 	case 0xc4: buf[0] = BOXDRAW_HORIZONTAL; break;
637 	case 0xda: buf[0] = BOXDRAW_DOWN_RIGHT; break;
638 	case 0xd9: buf[0] = BOXDRAW_UP_LEFT; break;
639 
640 	/* double frame */
641 	case 0xba: buf[0] = BOXDRAW_DOUBLE_VERTICAL; break;
642 	case 0xbb: buf[0] = BOXDRAW_DOUBLE_DOWN_LEFT; break;
643 	case 0xbc: buf[0] = BOXDRAW_DOUBLE_UP_LEFT; break;
644 	case 0xc8: buf[0] = BOXDRAW_DOUBLE_UP_RIGHT; break;
645 	case 0xc9: buf[0] = BOXDRAW_DOUBLE_DOWN_RIGHT; break;
646 	case 0xcd: buf[0] = BOXDRAW_DOUBLE_HORIZONTAL; break;
647 
648 	default:
649 		buf[0] = c;
650 	}
651         buf[1] = 0;     /* terminate string */
652 
653 	conout->OutputString(conout, buf);
654 }
655