1/*
2 * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19FILE_LICENCE ( GPL2_OR_LATER );
20
21#include <stddef.h>
22#include <assert.h>
23#include <gpxe/efi/efi.h>
24#include <gpxe/ansiesc.h>
25#include <console.h>
26
27#define ATTR_BOLD		0x08
28
29#define ATTR_FCOL_MASK		0x07
30#define ATTR_FCOL_BLACK		0x00
31#define ATTR_FCOL_BLUE		0x01
32#define ATTR_FCOL_GREEN		0x02
33#define ATTR_FCOL_CYAN		0x03
34#define ATTR_FCOL_RED		0x04
35#define ATTR_FCOL_MAGENTA	0x05
36#define ATTR_FCOL_YELLOW	0x06
37#define ATTR_FCOL_WHITE		0x07
38
39#define ATTR_BCOL_MASK		0x70
40#define ATTR_BCOL_BLACK		0x00
41#define ATTR_BCOL_BLUE		0x10
42#define ATTR_BCOL_GREEN		0x20
43#define ATTR_BCOL_CYAN		0x30
44#define ATTR_BCOL_RED		0x40
45#define ATTR_BCOL_MAGENTA	0x50
46#define ATTR_BCOL_YELLOW	0x60
47#define ATTR_BCOL_WHITE		0x70
48
49#define ATTR_DEFAULT		ATTR_FCOL_WHITE
50
51/** Current character attribute */
52static unsigned int efi_attr = ATTR_DEFAULT;
53
54/**
55 * Handle ANSI CUP (cursor position)
56 *
57 * @v count		Parameter count
58 * @v params[0]		Row (1 is top)
59 * @v params[1]		Column (1 is left)
60 */
61static void efi_handle_cup ( unsigned int count __unused, int params[] ) {
62	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut;
63	int cx = ( params[1] - 1 );
64	int cy = ( params[0] - 1 );
65
66	if ( cx < 0 )
67		cx = 0;
68	if ( cy < 0 )
69		cy = 0;
70
71	conout->SetCursorPosition ( conout, cx, cy );
72}
73
74/**
75 * Handle ANSI ED (erase in page)
76 *
77 * @v count		Parameter count
78 * @v params[0]		Region to erase
79 */
80static void efi_handle_ed ( unsigned int count __unused,
81			     int params[] __unused ) {
82	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut;
83
84	/* We assume that we always clear the whole screen */
85	assert ( params[0] == ANSIESC_ED_ALL );
86
87	conout->ClearScreen ( conout );
88}
89
90/**
91 * Handle ANSI SGR (set graphics rendition)
92 *
93 * @v count		Parameter count
94 * @v params		List of graphic rendition aspects
95 */
96static void efi_handle_sgr ( unsigned int count, int params[] ) {
97	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut;
98	static const uint8_t efi_attr_fcols[10] = {
99		ATTR_FCOL_BLACK, ATTR_FCOL_RED, ATTR_FCOL_GREEN,
100		ATTR_FCOL_YELLOW, ATTR_FCOL_BLUE, ATTR_FCOL_MAGENTA,
101		ATTR_FCOL_CYAN, ATTR_FCOL_WHITE,
102		ATTR_FCOL_WHITE, ATTR_FCOL_WHITE /* defaults */
103	};
104	static const uint8_t efi_attr_bcols[10] = {
105		ATTR_BCOL_BLACK, ATTR_BCOL_RED, ATTR_BCOL_GREEN,
106		ATTR_BCOL_YELLOW, ATTR_BCOL_BLUE, ATTR_BCOL_MAGENTA,
107		ATTR_BCOL_CYAN, ATTR_BCOL_WHITE,
108		ATTR_BCOL_BLACK, ATTR_BCOL_BLACK /* defaults */
109	};
110	unsigned int i;
111	int aspect;
112
113	for ( i = 0 ; i < count ; i++ ) {
114		aspect = params[i];
115		if ( aspect == 0 ) {
116			efi_attr = ATTR_DEFAULT;
117		} else if ( aspect == 1 ) {
118			efi_attr |= ATTR_BOLD;
119		} else if ( aspect == 22 ) {
120			efi_attr &= ~ATTR_BOLD;
121		} else if ( ( aspect >= 30 ) && ( aspect <= 39 ) ) {
122			efi_attr &= ~ATTR_FCOL_MASK;
123			efi_attr |= efi_attr_fcols[ aspect - 30 ];
124		} else if ( ( aspect >= 40 ) && ( aspect <= 49 ) ) {
125			efi_attr &= ~ATTR_BCOL_MASK;
126			efi_attr |= efi_attr_bcols[ aspect - 40 ];
127		}
128	}
129
130	conout->SetAttribute ( conout, efi_attr );
131}
132
133/** EFI console ANSI escape sequence handlers */
134static struct ansiesc_handler efi_ansiesc_handlers[] = {
135	{ ANSIESC_CUP, efi_handle_cup },
136	{ ANSIESC_ED, efi_handle_ed },
137	{ ANSIESC_SGR, efi_handle_sgr },
138	{ 0, NULL }
139};
140
141/** EFI console ANSI escape sequence context */
142static struct ansiesc_context efi_ansiesc_ctx = {
143	.handlers = efi_ansiesc_handlers,
144};
145
146/**
147 * Print a character to EFI console
148 *
149 * @v character		Character to be printed
150 */
151static void efi_putchar ( int character ) {
152	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut;
153	wchar_t wstr[] = { character, 0 };
154
155	/* Intercept ANSI escape sequences */
156	character = ansiesc_process ( &efi_ansiesc_ctx, character );
157	if ( character < 0 )
158		return;
159
160	conout->OutputString ( conout, wstr );
161}
162
163/**
164 * Pointer to current ANSI output sequence
165 *
166 * While we are in the middle of returning an ANSI sequence for a
167 * special key, this will point to the next character to return.  When
168 * not in the middle of such a sequence, this will point to a NUL
169 * (note: not "will be NULL").
170 */
171static const char *ansi_input = "";
172
173/** Mapping from EFI scan codes to ANSI escape sequences */
174static const char *ansi_sequences[] = {
175	[SCAN_UP] = "[A",
176	[SCAN_DOWN] = "[B",
177	[SCAN_RIGHT] = "[C",
178	[SCAN_LEFT] = "[D",
179	[SCAN_HOME] = "[H",
180	[SCAN_END] = "[F",
181	[SCAN_INSERT] = "[2~",
182	/* EFI translates an incoming backspace via the serial console
183	 * into a SCAN_DELETE.  There's not much we can do about this.
184	 */
185	[SCAN_DELETE] = "[3~",
186	[SCAN_PAGE_UP] = "[5~",
187	[SCAN_PAGE_DOWN] = "[6~",
188	/* EFI translates some (but not all) incoming escape sequences
189	 * via the serial console into equivalent scancodes.  When it
190	 * doesn't recognise a sequence, it helpfully(!) translates
191	 * the initial ESC and passes the remainder through verbatim.
192	 * Treating SCAN_ESC as equivalent to an empty escape sequence
193	 * works around this bug.
194	 */
195	[SCAN_ESC] = "",
196};
197
198/**
199 * Get ANSI escape sequence corresponding to EFI scancode
200 *
201 * @v scancode		EFI scancode
202 * @ret ansi_seq	ANSI escape sequence, if any, otherwise NULL
203 */
204static const char * scancode_to_ansi_seq ( unsigned int scancode ) {
205	if ( scancode < ( sizeof ( ansi_sequences ) /
206			  sizeof ( ansi_sequences[0] ) ) ) {
207		return ansi_sequences[scancode];
208	}
209	return NULL;
210}
211
212/**
213 * Get character from EFI console
214 *
215 * @ret character	Character read from console
216 */
217static int efi_getchar ( void ) {
218	EFI_SIMPLE_TEXT_INPUT_PROTOCOL *conin = efi_systab->ConIn;
219	const char *ansi_seq;
220	EFI_INPUT_KEY key;
221	EFI_STATUS efirc;
222
223	/* If we are mid-sequence, pass out the next byte */
224	if ( *ansi_input )
225		return *(ansi_input++);
226
227	/* Read key from real EFI console */
228	if ( ( efirc = conin->ReadKeyStroke ( conin, &key ) ) != 0 ) {
229		DBG ( "EFI could not read keystroke: %s\n",
230		      efi_strerror ( efirc ) );
231		return 0;
232	}
233	DBG2 ( "EFI read key stroke with unicode %04x scancode %04x\n",
234	       key.UnicodeChar, key.ScanCode );
235
236	/* If key has a Unicode representation, return it */
237	if ( key.UnicodeChar )
238		return key.UnicodeChar;
239
240	/* Otherwise, check for a special key that we know about */
241	if ( ( ansi_seq = scancode_to_ansi_seq ( key.ScanCode ) ) ) {
242		/* Start of escape sequence: return ESC (0x1b) */
243		ansi_input = ansi_seq;
244		return 0x1b;
245	}
246
247	return 0;
248}
249
250/**
251 * Check for character ready to read from EFI console
252 *
253 * @ret True		Character available to read
254 * @ret False		No character available to read
255 */
256static int efi_iskey ( void ) {
257	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
258	EFI_SIMPLE_TEXT_INPUT_PROTOCOL *conin = efi_systab->ConIn;
259	EFI_STATUS efirc;
260
261	/* If we are mid-sequence, we are always ready */
262	if ( *ansi_input )
263		return 1;
264
265	/* Check to see if the WaitForKey event has fired */
266	if ( ( efirc = bs->CheckEvent ( conin->WaitForKey ) ) == 0 )
267		return 1;
268
269	return 0;
270}
271
272struct console_driver efi_console __console_driver = {
273	.putchar = efi_putchar,
274	.getchar = efi_getchar,
275	.iskey = efi_iskey,
276};
277