xref: /openbmc/u-boot/lib/efi_loader/efi_console.c (revision d9b88d25)
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>
10 #include <efi_loader.h>
11 
12 static bool console_size_queried;
13 
14 #define EFI_COUT_MODE_2 2
15 #define EFI_MAX_COUT_MODE 3
16 
17 struct cout_mode {
18 	unsigned long columns;
19 	unsigned long rows;
20 	int present;
21 };
22 
23 static struct cout_mode efi_cout_modes[] = {
24 	/* EFI Mode 0 is 80x25 and always present */
25 	{
26 		.columns = 80,
27 		.rows = 25,
28 		.present = 1,
29 	},
30 	/* EFI Mode 1 is always 80x50 */
31 	{
32 		.columns = 80,
33 		.rows = 50,
34 		.present = 0,
35 	},
36 	/* Value are unknown until we query the console */
37 	{
38 		.columns = 0,
39 		.rows = 0,
40 		.present = 0,
41 	},
42 };
43 
44 const efi_guid_t efi_guid_console_control = CONSOLE_CONTROL_GUID;
45 
46 #define cESC '\x1b'
47 #define ESC "\x1b"
48 
49 static efi_status_t EFIAPI efi_cin_get_mode(
50 			struct efi_console_control_protocol *this,
51 			int *mode, char *uga_exists, char *std_in_locked)
52 {
53 	EFI_ENTRY("%p, %p, %p, %p", this, mode, uga_exists, std_in_locked);
54 
55 	if (mode)
56 		*mode = EFI_CONSOLE_MODE_TEXT;
57 	if (uga_exists)
58 		*uga_exists = 0;
59 	if (std_in_locked)
60 		*std_in_locked = 0;
61 
62 	return EFI_EXIT(EFI_SUCCESS);
63 }
64 
65 static efi_status_t EFIAPI efi_cin_set_mode(
66 			struct efi_console_control_protocol *this, int mode)
67 {
68 	EFI_ENTRY("%p, %d", this, mode);
69 	return EFI_EXIT(EFI_UNSUPPORTED);
70 }
71 
72 static efi_status_t EFIAPI efi_cin_lock_std_in(
73 			struct efi_console_control_protocol *this,
74 			uint16_t *password)
75 {
76 	EFI_ENTRY("%p, %p", this, password);
77 	return EFI_EXIT(EFI_UNSUPPORTED);
78 }
79 
80 const struct efi_console_control_protocol efi_console_control = {
81 	.get_mode = efi_cin_get_mode,
82 	.set_mode = efi_cin_set_mode,
83 	.lock_std_in = efi_cin_lock_std_in,
84 };
85 
86 /* Default to mode 0 */
87 static struct simple_text_output_mode efi_con_mode = {
88 	.max_mode = 1,
89 	.mode = 0,
90 	.attribute = 0,
91 	.cursor_column = 0,
92 	.cursor_row = 0,
93 	.cursor_visible = 1,
94 };
95 
96 static int term_read_reply(int *n, int maxnum, char end_char)
97 {
98 	char c;
99 	int i = 0;
100 
101 	c = getc();
102 	if (c != cESC)
103 		return -1;
104 	c = getc();
105 	if (c != '[')
106 		return -1;
107 
108 	n[0] = 0;
109 	while (1) {
110 		c = getc();
111 		if (c == ';') {
112 			i++;
113 			if (i >= maxnum)
114 				return -1;
115 			n[i] = 0;
116 			continue;
117 		} else if (c == end_char) {
118 			break;
119 		} else if (c > '9' || c < '0') {
120 			return -1;
121 		}
122 
123 		/* Read one more decimal position */
124 		n[i] *= 10;
125 		n[i] += c - '0';
126 	}
127 
128 	return 0;
129 }
130 
131 static efi_status_t EFIAPI efi_cout_reset(
132 			struct efi_simple_text_output_protocol *this,
133 			char extended_verification)
134 {
135 	EFI_ENTRY("%p, %d", this, extended_verification);
136 	return EFI_EXIT(EFI_UNSUPPORTED);
137 }
138 
139 static void print_unicode_in_utf8(u16 c)
140 {
141 	char utf8[4] = { 0 };
142 	char *b = utf8;
143 
144 	if (c < 0x80) {
145 		*(b++) = c;
146 	} else if (c < 0x800) {
147 		*(b++) = 192 + c / 64;
148 		*(b++) = 128 + c % 64;
149 	} else {
150 		*(b++) = 224 + c / 4096;
151 		*(b++) = 128 + c / 64 % 64;
152 		*(b++) = 128 + c % 64;
153 	}
154 
155 	puts(utf8);
156 }
157 
158 static efi_status_t EFIAPI efi_cout_output_string(
159 			struct efi_simple_text_output_protocol *this,
160 			const unsigned short *string)
161 {
162 	struct cout_mode *mode;
163 	u16 ch;
164 
165 	mode = &efi_cout_modes[efi_con_mode.mode];
166 	EFI_ENTRY("%p, %p", this, string);
167 	for (;(ch = *string); string++) {
168 		print_unicode_in_utf8(ch);
169 		efi_con_mode.cursor_column++;
170 		if (ch == '\n') {
171 			efi_con_mode.cursor_column = 1;
172 			efi_con_mode.cursor_row++;
173 		} else if (efi_con_mode.cursor_column > mode->columns) {
174 			efi_con_mode.cursor_column = 1;
175 			efi_con_mode.cursor_row++;
176 		}
177 		if (efi_con_mode.cursor_row > mode->rows)
178 			efi_con_mode.cursor_row = mode->rows;
179 	}
180 
181 	return EFI_EXIT(EFI_SUCCESS);
182 }
183 
184 static efi_status_t EFIAPI efi_cout_test_string(
185 			struct efi_simple_text_output_protocol *this,
186 			const unsigned short *string)
187 {
188 	EFI_ENTRY("%p, %p", this, string);
189 	return EFI_EXIT(EFI_SUCCESS);
190 }
191 
192 static bool cout_mode_matches(struct cout_mode *mode, int rows, int cols)
193 {
194 	if (!mode->present)
195 		return false;
196 
197 	return (mode->rows == rows) && (mode->columns == cols);
198 }
199 
200 static efi_status_t EFIAPI efi_cout_query_mode(
201 			struct efi_simple_text_output_protocol *this,
202 			unsigned long mode_number, unsigned long *columns,
203 			unsigned long *rows)
204 {
205 	EFI_ENTRY("%p, %ld, %p, %p", this, mode_number, columns, rows);
206 
207 	if (!console_size_queried) {
208 		/* Ask the terminal about its size */
209 		int n[3];
210 		int cols;
211 		int rows;
212 		u64 timeout;
213 
214 		console_size_queried = true;
215 
216 		/* Empty input buffer */
217 		while (tstc())
218 			getc();
219 
220 		printf(ESC"[18t");
221 
222 		/* Check if we have a terminal that understands */
223 		timeout = timer_get_us() + 1000000;
224 		while (!tstc())
225 			if (timer_get_us() > timeout)
226 				goto out;
227 
228 		/* Read {depth,rows,cols} */
229 		if (term_read_reply(n, 3, 't')) {
230 			goto out;
231 		}
232 
233 		cols = n[2];
234 		rows = n[1];
235 
236 		/* Test if we can have Mode 1 */
237 		if (cols >= 80 && rows >= 50) {
238 			efi_cout_modes[1].present = 1;
239 			efi_con_mode.max_mode = 2;
240 		}
241 
242 		/*
243 		 * Install our mode as mode 2 if it is different
244 		 * than mode 0 or 1 and set it  as the currently selected mode
245 		 */
246 		if (!cout_mode_matches(&efi_cout_modes[0], rows, cols) &&
247 		    !cout_mode_matches(&efi_cout_modes[1], rows, cols)) {
248 			efi_cout_modes[EFI_COUT_MODE_2].columns = cols;
249 			efi_cout_modes[EFI_COUT_MODE_2].rows = rows;
250 			efi_cout_modes[EFI_COUT_MODE_2].present = 1;
251 			efi_con_mode.max_mode = EFI_MAX_COUT_MODE;
252 			efi_con_mode.mode = EFI_COUT_MODE_2;
253 		}
254 	}
255 
256 	if (mode_number >= efi_con_mode.max_mode)
257 		return EFI_EXIT(EFI_UNSUPPORTED);
258 
259 	if (efi_cout_modes[mode_number].present != 1)
260 		return EFI_EXIT(EFI_UNSUPPORTED);
261 
262 out:
263 	if (columns)
264 		*columns = efi_cout_modes[mode_number].columns;
265 	if (rows)
266 		*rows = efi_cout_modes[mode_number].rows;
267 
268 	return EFI_EXIT(EFI_SUCCESS);
269 }
270 
271 static efi_status_t EFIAPI efi_cout_set_mode(
272 			struct efi_simple_text_output_protocol *this,
273 			unsigned long mode_number)
274 {
275 	EFI_ENTRY("%p, %ld", this, mode_number);
276 
277 
278 	if (mode_number > efi_con_mode.max_mode)
279 		return EFI_EXIT(EFI_UNSUPPORTED);
280 
281 	efi_con_mode.mode = mode_number;
282 	efi_con_mode.cursor_column = 0;
283 	efi_con_mode.cursor_row = 0;
284 
285 	return EFI_EXIT(EFI_SUCCESS);
286 }
287 
288 static efi_status_t EFIAPI efi_cout_set_attribute(
289 			struct efi_simple_text_output_protocol *this,
290 			unsigned long attribute)
291 {
292 	EFI_ENTRY("%p, %lx", this, attribute);
293 
294 	/* Just ignore attributes (colors) for now */
295 	return EFI_EXIT(EFI_UNSUPPORTED);
296 }
297 
298 static efi_status_t EFIAPI efi_cout_clear_screen(
299 			struct efi_simple_text_output_protocol *this)
300 {
301 	EFI_ENTRY("%p", this);
302 
303 	printf(ESC"[2J");
304 
305 	return EFI_EXIT(EFI_SUCCESS);
306 }
307 
308 static efi_status_t EFIAPI efi_cout_set_cursor_position(
309 			struct efi_simple_text_output_protocol *this,
310 			unsigned long column, unsigned long row)
311 {
312 	EFI_ENTRY("%p, %ld, %ld", this, column, row);
313 
314 	printf(ESC"[%d;%df", (int)row, (int)column);
315 	efi_con_mode.cursor_column = column;
316 	efi_con_mode.cursor_row = row;
317 
318 	return EFI_EXIT(EFI_SUCCESS);
319 }
320 
321 static efi_status_t EFIAPI efi_cout_enable_cursor(
322 			struct efi_simple_text_output_protocol *this,
323 			bool enable)
324 {
325 	EFI_ENTRY("%p, %d", this, enable);
326 
327 	printf(ESC"[?25%c", enable ? 'h' : 'l');
328 
329 	return EFI_EXIT(EFI_SUCCESS);
330 }
331 
332 const struct efi_simple_text_output_protocol efi_con_out = {
333 	.reset = efi_cout_reset,
334 	.output_string = efi_cout_output_string,
335 	.test_string = efi_cout_test_string,
336 	.query_mode = efi_cout_query_mode,
337 	.set_mode = efi_cout_set_mode,
338 	.set_attribute = efi_cout_set_attribute,
339 	.clear_screen = efi_cout_clear_screen,
340 	.set_cursor_position = efi_cout_set_cursor_position,
341 	.enable_cursor = efi_cout_enable_cursor,
342 	.mode = (void*)&efi_con_mode,
343 };
344 
345 static efi_status_t EFIAPI efi_cin_reset(
346 			struct efi_simple_input_interface *this,
347 			bool extended_verification)
348 {
349 	EFI_ENTRY("%p, %d", this, extended_verification);
350 	return EFI_EXIT(EFI_UNSUPPORTED);
351 }
352 
353 static efi_status_t EFIAPI efi_cin_read_key_stroke(
354 			struct efi_simple_input_interface *this,
355 			struct efi_input_key *key)
356 {
357 	struct efi_input_key pressed_key = {
358 		.scan_code = 0,
359 		.unicode_char = 0,
360 	};
361 	char ch;
362 
363 	EFI_ENTRY("%p, %p", this, key);
364 
365 	/* We don't do interrupts, so check for timers cooperatively */
366 	efi_timer_check();
367 
368 	if (!tstc()) {
369 		/* No key pressed */
370 		return EFI_EXIT(EFI_NOT_READY);
371 	}
372 
373 	ch = getc();
374 	if (ch == cESC) {
375 		/* Escape Sequence */
376 		ch = getc();
377 		switch (ch) {
378 		case cESC: /* ESC */
379 			pressed_key.scan_code = 23;
380 			break;
381 		case 'O': /* F1 - F4 */
382 			pressed_key.scan_code = getc() - 'P' + 11;
383 			break;
384 		case 'a'...'z':
385 			ch = ch - 'a';
386 			break;
387 		case '[':
388 			ch = getc();
389 			switch (ch) {
390 			case 'A'...'D': /* up, down right, left */
391 				pressed_key.scan_code = ch - 'A' + 1;
392 				break;
393 			case 'F': /* End */
394 				pressed_key.scan_code = 6;
395 				break;
396 			case 'H': /* Home */
397 				pressed_key.scan_code = 5;
398 				break;
399 			case '1': /* F5 - F8 */
400 				pressed_key.scan_code = getc() - '0' + 11;
401 				getc();
402 				break;
403 			case '2': /* F9 - F12 */
404 				pressed_key.scan_code = getc() - '0' + 19;
405 				getc();
406 				break;
407 			case '3': /* DEL */
408 				pressed_key.scan_code = 8;
409 				getc();
410 				break;
411 			}
412 			break;
413 		}
414 	} else if (ch == 0x7f) {
415 		/* Backspace */
416 		ch = 0x08;
417 	}
418 	pressed_key.unicode_char = ch;
419 	*key = pressed_key;
420 
421 	return EFI_EXIT(EFI_SUCCESS);
422 }
423 
424 const struct efi_simple_input_interface efi_con_in = {
425 	.reset = efi_cin_reset,
426 	.read_key_stroke = efi_cin_read_key_stroke,
427 	.wait_for_key = NULL,
428 };
429