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
35static EFI_GUID simple_input_ex_guid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID;
36static SIMPLE_TEXT_OUTPUT_INTERFACE	*conout;
37static SIMPLE_INPUT_INTERFACE		*conin;
38static 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
45static int args[MAXARGS], argc;
46static int fg_c, bg_c, curx, cury;
47static int esc;
48
49void get_pos(int *x, int *y);
50void curs_move(int *_x, int *_y, int x, int y);
51static void CL(int);
52void HO(void);
53void end_term(void);
54#endif
55
56#define	KEYBUFSZ 10
57static unsigned keybuf[KEYBUFSZ];	/* keybuf for extended codes */
58static int key_pending;
59
60static void efi_cons_probe(struct console *);
61static int efi_cons_init(int);
62void efi_cons_putchar(int);
63int efi_cons_getchar(void);
64void efi_cons_efiputchar(int);
65int efi_cons_poll(void);
66
67struct 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. */
81void
82get_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). */
89void
90curs_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. */
100void
101end_term(void)
102{
103	esc = 0;
104	argc = -1;
105}
106
107#endif
108
109static void
110efi_cons_probe(struct console *cp)
111{
112	conout = ST->ConOut;
113	conin = ST->ConIn;
114	cp->c_flags |= C_PRESENTIN | C_PRESENTOUT;
115}
116
117static int
118efi_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
139static void
140efi_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. */
195static void
196bail_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. */
217static void
218CD(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 */
243static void
244CM(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. */
255void
256HO(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 */
264static void
265CL(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
308static void
309get_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 */
318static void
319efi_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
436void
437HO(void)
438{
439}
440#endif
441
442void
443efi_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
452static int
453keybuf_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
468static bool
469keybuf_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 */
484static void
485keybuf_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
521static bool
522efi_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
535static bool
536efi_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		 * The shift state and/or toggle state may not be valid,
564		 * but we still can have ScanCode or UnicodeChar.
565		 */
566		if (kp->ScanCode == 0 && kp->UnicodeChar == 0)
567			return (false);
568		keybuf_inschar(kp);
569		return (true);
570	}
571	return (false);
572}
573
574int
575efi_cons_getchar(void)
576{
577	int c;
578
579	if ((c = keybuf_getchar()) != 0)
580		return (c);
581
582	key_pending = 0;
583
584	if (coninex == NULL) {
585		if (efi_readkey())
586			return (keybuf_getchar());
587	} else {
588		if (efi_readkey_ex())
589			return (keybuf_getchar());
590	}
591
592	return (-1);
593}
594
595int
596efi_cons_poll(void)
597{
598	EFI_STATUS status;
599
600	if (keybuf_ischar() || key_pending)
601		return (1);
602
603	/*
604	 * Some EFI implementation (u-boot for example) do not support
605	 * WaitForKey().
606	 * CheckEvent() can clear the signaled state.
607	 */
608	if (coninex != NULL) {
609		if (coninex->WaitForKeyEx == NULL) {
610			key_pending = efi_readkey_ex();
611		} else {
612			status = BS->CheckEvent(coninex->WaitForKeyEx);
613			key_pending = status == EFI_SUCCESS;
614		}
615	} else {
616		if (conin->WaitForKey == NULL) {
617			key_pending = efi_readkey();
618		} else {
619			status = BS->CheckEvent(conin->WaitForKey);
620			key_pending = status == EFI_SUCCESS;
621		}
622	}
623
624	return (key_pending);
625}
626
627/* Plain direct access to EFI OutputString(). */
628void
629efi_cons_efiputchar(int c)
630{
631	CHAR16 buf[2];
632
633	/*
634	 * translate box chars to unicode
635	 */
636	switch (c) {
637	/* single frame */
638	case 0xb3: buf[0] = BOXDRAW_VERTICAL; break;
639	case 0xbf: buf[0] = BOXDRAW_DOWN_LEFT; break;
640	case 0xc0: buf[0] = BOXDRAW_UP_RIGHT; break;
641	case 0xc4: buf[0] = BOXDRAW_HORIZONTAL; break;
642	case 0xda: buf[0] = BOXDRAW_DOWN_RIGHT; break;
643	case 0xd9: buf[0] = BOXDRAW_UP_LEFT; break;
644
645	/* double frame */
646	case 0xba: buf[0] = BOXDRAW_DOUBLE_VERTICAL; break;
647	case 0xbb: buf[0] = BOXDRAW_DOUBLE_DOWN_LEFT; break;
648	case 0xbc: buf[0] = BOXDRAW_DOUBLE_UP_LEFT; break;
649	case 0xc8: buf[0] = BOXDRAW_DOUBLE_UP_RIGHT; break;
650	case 0xc9: buf[0] = BOXDRAW_DOUBLE_DOWN_RIGHT; break;
651	case 0xcd: buf[0] = BOXDRAW_DOUBLE_HORIZONTAL; break;
652
653	default:
654		buf[0] = c;
655	}
656        buf[1] = 0;     /* terminate string */
657
658	conout->OutputString(conout, buf);
659}
660