xref: /freebsd/stand/efi/libefi/efi_console.c (revision e9b148a3185f41e3a09e91ea75cae7828d908845)
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 #include <teken.h>
33 
34 #include "bootstrap.h"
35 
36 static EFI_GUID simple_input_ex_guid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID;
37 static SIMPLE_TEXT_OUTPUT_INTERFACE	*conout;
38 static SIMPLE_INPUT_INTERFACE		*conin;
39 static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *coninex;
40 
41 static tf_bell_t	efi_cons_bell;
42 static tf_cursor_t	efi_text_cursor;
43 static tf_putchar_t	efi_text_putchar;
44 static tf_fill_t	efi_text_fill;
45 static tf_copy_t	efi_text_copy;
46 static tf_param_t	efi_text_param;
47 static tf_respond_t	efi_cons_respond;
48 
49 static teken_funcs_t tf = {
50 	.tf_bell	= efi_cons_bell,
51 	.tf_cursor	= efi_text_cursor,
52 	.tf_putchar	= efi_text_putchar,
53 	.tf_fill	= efi_text_fill,
54 	.tf_copy	= efi_text_copy,
55 	.tf_param	= efi_text_param,
56 	.tf_respond	= efi_cons_respond,
57 };
58 
59 teken_t teken;
60 teken_pos_t tp;
61 
62 struct text_pixel {
63 	teken_char_t c;
64 	teken_attr_t a;
65 };
66 
67 static struct text_pixel *buffer;
68 
69 #define	KEYBUFSZ 10
70 static unsigned keybuf[KEYBUFSZ];	/* keybuf for extended codes */
71 static int key_pending;
72 
73 static const unsigned char teken_color_to_efi_color[16] = {
74 	EFI_BLACK,
75 	EFI_RED,
76 	EFI_GREEN,
77 	EFI_BROWN,
78 	EFI_BLUE,
79 	EFI_MAGENTA,
80 	EFI_CYAN,
81 	EFI_LIGHTGRAY,
82 	EFI_DARKGRAY,
83 	EFI_LIGHTRED,
84 	EFI_LIGHTGREEN,
85 	EFI_YELLOW,
86 	EFI_LIGHTBLUE,
87 	EFI_LIGHTMAGENTA,
88 	EFI_LIGHTCYAN,
89 	EFI_WHITE
90 };
91 
92 static void efi_cons_probe(struct console *);
93 static int efi_cons_init(int);
94 void efi_cons_putchar(int);
95 int efi_cons_getchar(void);
96 void efi_cons_efiputchar(int);
97 int efi_cons_poll(void);
98 
99 struct console efi_console = {
100 	"efi",
101 	"EFI console",
102 	C_WIDEOUT,
103 	efi_cons_probe,
104 	efi_cons_init,
105 	efi_cons_putchar,
106 	efi_cons_getchar,
107 	efi_cons_poll
108 };
109 
110 /*
111  * Not implemented.
112  */
113 static void
114 efi_cons_bell(void *s __unused)
115 {
116 }
117 
118 static void
119 efi_text_cursor(void *s __unused, const teken_pos_t *p)
120 {
121 	UINTN row, col;
122 
123 	(void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row);
124 
125 	if (p->tp_col == col)
126 		col = p->tp_col - 1;
127 	else
128 		col = p->tp_col;
129 
130 	if (p->tp_row == row)
131 		row = p->tp_row - 1;
132 	else
133 		row = p->tp_row;
134 
135 	conout->SetCursorPosition(conout, col, row);
136 }
137 
138 static void
139 efi_text_printchar(const teken_pos_t *p, bool autoscroll)
140 {
141 	UINTN a, attr;
142 	struct text_pixel *px;
143 	teken_color_t fg, bg, tmp;
144 
145 	px = buffer + p->tp_col + p->tp_row * tp.tp_col;
146 	a = conout->Mode->Attribute;
147 
148 	fg = teken_256to16(px->a.ta_fgcolor);
149 	bg = teken_256to16(px->a.ta_bgcolor);
150 	if (px->a.ta_format & TF_BOLD)
151 		fg |= TC_LIGHT;
152 	if (px->a.ta_format & TF_BLINK)
153 		bg |= TC_LIGHT;
154 
155 	if (px->a.ta_format & TF_REVERSE) {
156 		tmp = fg;
157 		fg = bg;
158 		bg = tmp;
159 	}
160 
161 	attr = EFI_TEXT_ATTR(teken_color_to_efi_color[fg],
162 	    teken_color_to_efi_color[bg] & 0x7);
163 
164 	conout->SetCursorPosition(conout, p->tp_col, p->tp_row);
165 
166 	/* to prvent autoscroll, skip print of lower right char */
167 	if (!autoscroll &&
168 	    p->tp_row == tp.tp_row - 1 &&
169 	    p->tp_col == tp.tp_col - 1)
170 		return;
171 
172 	(void) conout->SetAttribute(conout, attr);
173 	efi_cons_efiputchar(px->c);
174 	(void) conout->SetAttribute(conout, a);
175 }
176 
177 static void
178 efi_text_putchar(void *s __unused, const teken_pos_t *p, teken_char_t c,
179     const teken_attr_t *a)
180 {
181 	EFI_STATUS status;
182 	int idx;
183 
184 	idx = p->tp_col + p->tp_row * tp.tp_col;
185 	buffer[idx].c = c;
186 	buffer[idx].a = *a;
187 	efi_text_printchar(p, false);
188 }
189 
190 static void
191 efi_text_fill(void *s, const teken_rect_t *r, teken_char_t c,
192     const teken_attr_t *a)
193 {
194 	teken_pos_t p;
195 	UINTN row, col;
196 
197 	(void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row);
198 
199 	conout->EnableCursor(conout, FALSE);
200 	for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row;
201 	    p.tp_row++)
202 		for (p.tp_col = r->tr_begin.tp_col;
203 		    p.tp_col < r->tr_end.tp_col; p.tp_col++)
204 			efi_text_putchar(s, &p, c, a);
205 	conout->EnableCursor(conout, TRUE);
206 }
207 
208 static bool
209 efi_same_pixel(struct text_pixel *px1, struct text_pixel *px2)
210 {
211 	if (px1->c != px2->c)
212 		return (false);
213 
214 	if (px1->a.ta_format != px2->a.ta_format)
215 		return (false);
216 	if (px1->a.ta_fgcolor != px2->a.ta_fgcolor)
217 		return (false);
218 	if (px1->a.ta_bgcolor != px2->a.ta_bgcolor)
219 		return (false);
220 
221 	return (true);
222 }
223 
224 static void
225 efi_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p)
226 {
227 	int srow, drow;
228 	int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */
229 	teken_pos_t d, s;
230 	bool scroll = false;
231 
232 	/*
233 	 * Copying is a little tricky. We must make sure we do it in
234 	 * correct order, to make sure we don't overwrite our own data.
235 	 */
236 
237 	nrow = r->tr_end.tp_row - r->tr_begin.tp_row;
238 	ncol = r->tr_end.tp_col - r->tr_begin.tp_col;
239 
240 	/*
241 	 * Check if we do copy whole screen.
242 	 */
243 	if (p->tp_row == 0 && p->tp_col == 0 &&
244 	    nrow == tp.tp_row - 2 && ncol == tp.tp_col - 2)
245 		scroll = true;
246 
247 	conout->EnableCursor(conout, FALSE);
248 	if (p->tp_row < r->tr_begin.tp_row) {
249 		/* Copy from bottom to top. */
250 		for (y = 0; y < nrow; y++) {
251 			d.tp_row = p->tp_row + y;
252 			s.tp_row = r->tr_begin.tp_row + y;
253 			drow = d.tp_row * tp.tp_col;
254 			srow = s.tp_row * tp.tp_col;
255 			for (x = 0; x < ncol; x++) {
256 				d.tp_col = p->tp_col + x;
257 				s.tp_col = r->tr_begin.tp_col + x;
258 
259 				if (!efi_same_pixel(
260 				    &buffer[d.tp_col + drow],
261 				    &buffer[s.tp_col + srow])) {
262 					buffer[d.tp_col + drow] =
263 					    buffer[s.tp_col + srow];
264 					if (!scroll)
265 						efi_text_printchar(&d, false);
266 				} else if (scroll) {
267 					/*
268 					 * Draw last char and trigger
269 					 * scroll.
270 					 */
271 					if (y == nrow - 1 &&
272 					    x == ncol - 1) {
273 						efi_text_printchar(&d, true);
274 					}
275 				}
276 			}
277 		}
278 	} else {
279 		/* Copy from top to bottom. */
280 		if (p->tp_col < r->tr_begin.tp_col) {
281 			/* Copy from right to left. */
282 			for (y = nrow - 1; y >= 0; y--) {
283 				d.tp_row = p->tp_row + y;
284 				s.tp_row = r->tr_begin.tp_row + y;
285 				drow = d.tp_row * tp.tp_col;
286 				srow = s.tp_row * tp.tp_col;
287 				for (x = 0; x < ncol; x++) {
288 					d.tp_col = p->tp_col + x;
289 					s.tp_col = r->tr_begin.tp_col + x;
290 
291 					if (!efi_same_pixel(
292 					    &buffer[d.tp_col + drow],
293 					    &buffer[s.tp_col + srow])) {
294 						buffer[d.tp_col + drow] =
295 						    buffer[s.tp_col + srow];
296 						efi_text_printchar(&d, false);
297 					}
298 				}
299 			}
300 		} else {
301 			/* Copy from left to right. */
302 			for (y = nrow - 1; y >= 0; y--) {
303 				d.tp_row = p->tp_row + y;
304 				s.tp_row = r->tr_begin.tp_row + y;
305 				drow = d.tp_row * tp.tp_col;
306 				srow = s.tp_row * tp.tp_col;
307 				for (x = ncol - 1; x >= 0; x--) {
308 					d.tp_col = p->tp_col + x;
309 					s.tp_col = r->tr_begin.tp_col + x;
310 
311 					if (!efi_same_pixel(
312 					    &buffer[d.tp_col + drow],
313 					    &buffer[s.tp_col + srow])) {
314 						buffer[d.tp_col + drow] =
315 						    buffer[s.tp_col + srow];
316 						efi_text_printchar(&d, false);
317 					}
318 				}
319 			}
320 		}
321 	}
322 	conout->EnableCursor(conout, TRUE);
323 }
324 
325 static void
326 efi_text_param(void *s __unused, int cmd, unsigned int value)
327 {
328 	switch (cmd) {
329 	case TP_SETLOCALCURSOR:
330 		/*
331 		 * 0 means normal (usually block), 1 means hidden, and
332 		 * 2 means blinking (always block) for compatibility with
333 		 * syscons.  We don't support any changes except hiding,
334 		 * so must map 2 to 0.
335 		 */
336 		value = (value == 1) ? 0 : 1;
337 		/* FALLTHROUGH */
338 	case TP_SHOWCURSOR:
339 		if (value == 1)
340 			conout->EnableCursor(conout, TRUE);
341 		else
342 			conout->EnableCursor(conout, FALSE);
343 		break;
344 	default:
345 		/* Not yet implemented */
346 		break;
347 	}
348 }
349 
350 /*
351  * Not implemented.
352  */
353 static void
354 efi_cons_respond(void *s __unused, const void *buf __unused,
355     size_t len __unused)
356 {
357 }
358 
359 static void
360 efi_cons_probe(struct console *cp)
361 {
362 	cp->c_flags |= C_PRESENTIN | C_PRESENTOUT;
363 }
364 
365 static bool
366 color_name_to_teken(const char *name, int *val)
367 {
368 	if (strcasecmp(name, "black") == 0) {
369 		*val = TC_BLACK;
370 		return (true);
371 	}
372 	if (strcasecmp(name, "red") == 0) {
373 		*val = TC_RED;
374 		return (true);
375 	}
376 	if (strcasecmp(name, "green") == 0) {
377 		*val = TC_GREEN;
378 		return (true);
379 	}
380 	if (strcasecmp(name, "brown") == 0) {
381 		*val = TC_BROWN;
382 		return (true);
383 	}
384 	if (strcasecmp(name, "blue") == 0) {
385 		*val = TC_BLUE;
386 		return (true);
387 	}
388 	if (strcasecmp(name, "magenta") == 0) {
389 		*val = TC_MAGENTA;
390 		return (true);
391 	}
392 	if (strcasecmp(name, "cyan") == 0) {
393 		*val = TC_CYAN;
394 		return (true);
395 	}
396 	if (strcasecmp(name, "white") == 0) {
397 		*val = TC_WHITE;
398 		return (true);
399 	}
400 	return (false);
401 }
402 
403 static int
404 efi_set_colors(struct env_var *ev, int flags, const void *value)
405 {
406 	int val = 0;
407 	char buf[2];
408 	const void *evalue;
409 	const teken_attr_t *ap;
410 	teken_attr_t a;
411 
412 	if (value == NULL)
413 		return (CMD_OK);
414 
415 	if (color_name_to_teken(value, &val)) {
416 		snprintf(buf, sizeof (buf), "%d", val);
417 		evalue = buf;
418 	} else {
419 		char *end;
420 
421 		errno = 0;
422 		val = (int)strtol(value, &end, 0);
423 		if (errno != 0 || *end != '\0') {
424 			printf("Allowed values are either ansi color name or "
425 			    "number from range [0-7].\n");
426 			return (CMD_OK);
427 		}
428 		evalue = value;
429 	}
430 
431 	ap = teken_get_defattr(&teken);
432 	a = *ap;
433 	if (strcmp(ev->ev_name, "teken.fg_color") == 0) {
434 		/* is it already set? */
435 		if (ap->ta_fgcolor == val)
436 			return (CMD_OK);
437 		a.ta_fgcolor = val;
438 	}
439 	if (strcmp(ev->ev_name, "teken.bg_color") == 0) {
440 		/* is it already set? */
441 		if (ap->ta_bgcolor == val)
442 			return (CMD_OK);
443 		a.ta_bgcolor = val;
444 	}
445 	env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL);
446 	teken_set_defattr(&teken, &a);
447 	return (CMD_OK);
448 }
449 
450 bool
451 efi_cons_update_mode(void)
452 {
453 	UINTN cols, rows;
454 	const teken_attr_t *a;
455 	EFI_STATUS status;
456 	char env[8];
457 
458 	status = conout->QueryMode(conout, conout->Mode->Mode, &cols, &rows);
459 	if (EFI_ERROR(status)) {
460 		cols = 80;
461 		rows = 24;
462 	}
463 
464 	if (buffer != NULL) {
465 		if (tp.tp_row == rows && tp.tp_col == cols)
466 			return (true);
467 		free(buffer);
468 	} else {
469 		teken_init(&teken, &tf, NULL);
470 	}
471 
472 	tp.tp_row = rows;
473 	tp.tp_col = cols;
474 	buffer = malloc(rows * cols * sizeof(*buffer));
475 	if (buffer == NULL)
476 		return (false);
477 
478 	teken_set_winsize(&teken, &tp);
479 	a = teken_get_defattr(&teken);
480 
481 	snprintf(env, sizeof(env), "%d", a->ta_fgcolor);
482 	env_setenv("teken.fg_color", EV_VOLATILE, env, efi_set_colors,
483 	    env_nounset);
484 	snprintf(env, sizeof(env), "%d", a->ta_bgcolor);
485 	env_setenv("teken.bg_color", EV_VOLATILE, env, efi_set_colors,
486 	    env_nounset);
487 
488 	for (int row = 0; row < rows; row++)
489 		for (int col = 0; col < cols; col++) {
490 			buffer[col + row * tp.tp_col].c = ' ';
491 			buffer[col + row * tp.tp_col].a = *a;
492 		}
493 
494 	snprintf(env, sizeof (env), "%u", (unsigned)rows);
495 	setenv("LINES", env, 1);
496 	snprintf(env, sizeof (env), "%u", (unsigned)cols);
497 	setenv("COLUMNS", env, 1);
498 
499 	return (true);
500 }
501 
502 static int
503 efi_cons_init(int arg)
504 {
505 	EFI_STATUS status;
506 
507 	if (conin != NULL)
508 		return (0);
509 
510 	conout = ST->ConOut;
511 	conin = ST->ConIn;
512 
513 	conout->EnableCursor(conout, TRUE);
514 	status = BS->OpenProtocol(ST->ConsoleInHandle, &simple_input_ex_guid,
515 	    (void **)&coninex, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
516 	if (status != EFI_SUCCESS)
517 		coninex = NULL;
518 
519 	if (efi_cons_update_mode())
520 		return (0);
521 
522 	return (1);
523 }
524 
525 void
526 efi_cons_putchar(int c)
527 {
528 	unsigned char ch = c;
529 
530 	if (buffer != NULL)
531 		teken_input(&teken, &ch, sizeof (ch));
532 	else
533 		efi_cons_efiputchar(c);
534 }
535 
536 static int
537 keybuf_getchar(void)
538 {
539 	int i, c = 0;
540 
541 	for (i = 0; i < KEYBUFSZ; i++) {
542 		if (keybuf[i] != 0) {
543 			c = keybuf[i];
544 			keybuf[i] = 0;
545 			break;
546 		}
547 	}
548 
549 	return (c);
550 }
551 
552 static bool
553 keybuf_ischar(void)
554 {
555 	int i;
556 
557 	for (i = 0; i < KEYBUFSZ; i++) {
558 		if (keybuf[i] != 0)
559 			return (true);
560 	}
561 	return (false);
562 }
563 
564 /*
565  * We are not reading input before keybuf is empty, so we are safe
566  * just to fill keybuf from the beginning.
567  */
568 static void
569 keybuf_inschar(EFI_INPUT_KEY *key)
570 {
571 
572 	switch (key->ScanCode) {
573 	case SCAN_UP: /* UP */
574 		keybuf[0] = 0x1b;	/* esc */
575 		keybuf[1] = '[';
576 		keybuf[2] = 'A';
577 		break;
578 	case SCAN_DOWN: /* DOWN */
579 		keybuf[0] = 0x1b;	/* esc */
580 		keybuf[1] = '[';
581 		keybuf[2] = 'B';
582 		break;
583 	case SCAN_RIGHT: /* RIGHT */
584 		keybuf[0] = 0x1b;	/* esc */
585 		keybuf[1] = '[';
586 		keybuf[2] = 'C';
587 		break;
588 	case SCAN_LEFT: /* LEFT */
589 		keybuf[0] = 0x1b;	/* esc */
590 		keybuf[1] = '[';
591 		keybuf[2] = 'D';
592 		break;
593 	case SCAN_DELETE:
594 		keybuf[0] = CHAR_BACKSPACE;
595 		break;
596 	case SCAN_ESC:
597 		keybuf[0] = 0x1b;	/* esc */
598 		break;
599 	default:
600 		keybuf[0] = key->UnicodeChar;
601 		break;
602 	}
603 }
604 
605 static bool
606 efi_readkey(void)
607 {
608 	EFI_STATUS status;
609 	EFI_INPUT_KEY key;
610 
611 	status = conin->ReadKeyStroke(conin, &key);
612 	if (status == EFI_SUCCESS) {
613 		keybuf_inschar(&key);
614 		return (true);
615 	}
616 	return (false);
617 }
618 
619 static bool
620 efi_readkey_ex(void)
621 {
622 	EFI_STATUS status;
623 	EFI_INPUT_KEY *kp;
624 	EFI_KEY_DATA  key_data;
625 	uint32_t kss;
626 
627 	status = coninex->ReadKeyStrokeEx(coninex, &key_data);
628 	if (status == EFI_SUCCESS) {
629 		kss = key_data.KeyState.KeyShiftState;
630 		kp = &key_data.Key;
631 		if (kss & EFI_SHIFT_STATE_VALID) {
632 
633 			/*
634 			 * quick mapping to control chars, replace with
635 			 * map lookup later.
636 			 */
637 			if (kss & EFI_RIGHT_CONTROL_PRESSED ||
638 			    kss & EFI_LEFT_CONTROL_PRESSED) {
639 				if (kp->UnicodeChar >= 'a' &&
640 				    kp->UnicodeChar <= 'z') {
641 					kp->UnicodeChar -= 'a';
642 					kp->UnicodeChar++;
643 				}
644 			}
645 		}
646 
647 		keybuf_inschar(kp);
648 		return (true);
649 	}
650 	return (false);
651 }
652 
653 int
654 efi_cons_getchar(void)
655 {
656 	int c;
657 
658 	if ((c = keybuf_getchar()) != 0)
659 		return (c);
660 
661 	key_pending = 0;
662 
663 	if (coninex == NULL) {
664 		if (efi_readkey())
665 			return (keybuf_getchar());
666 	} else {
667 		if (efi_readkey_ex())
668 			return (keybuf_getchar());
669 	}
670 
671 	return (-1);
672 }
673 
674 int
675 efi_cons_poll(void)
676 {
677 	EFI_STATUS status;
678 
679 	if (keybuf_ischar() || key_pending)
680 		return (1);
681 
682 	/*
683 	 * Some EFI implementation (u-boot for example) do not support
684 	 * WaitForKey().
685 	 * CheckEvent() can clear the signaled state.
686 	 */
687 	if (coninex != NULL) {
688 		if (coninex->WaitForKeyEx == NULL) {
689 			key_pending = efi_readkey_ex();
690 		} else {
691 			status = BS->CheckEvent(coninex->WaitForKeyEx);
692 			key_pending = status == EFI_SUCCESS;
693 		}
694 	} else {
695 		if (conin->WaitForKey == NULL) {
696 			key_pending = efi_readkey();
697 		} else {
698 			status = BS->CheckEvent(conin->WaitForKey);
699 			key_pending = status == EFI_SUCCESS;
700 		}
701 	}
702 
703 	return (key_pending);
704 }
705 
706 /* Plain direct access to EFI OutputString(). */
707 void
708 efi_cons_efiputchar(int c)
709 {
710 	CHAR16 buf[2];
711 	EFI_STATUS status;
712 
713 	buf[0] = c;
714         buf[1] = 0;     /* terminate string */
715 
716 	status = conout->TestString(conout, buf);
717 	if (EFI_ERROR(status))
718 		buf[0] = '?';
719 	conout->OutputString(conout, buf);
720 }
721