1/*	$NetBSD: eficons.c,v 1.14 2023/09/14 03:05:15 rin Exp $	*/
2
3/*-
4 * Copyright (c) 2016 Kimihiro Nonaka <nonaka@netbsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/param.h>
30#include <sys/bitops.h>
31#include <sys/stdint.h>
32
33#include <comio_direct.h>
34
35#include "efiboot.h"
36
37#include "bootinfo.h"
38#include "vbe.h"
39
40#ifndef DEFAULT_GOP_MODE
41#define DEFAULT_GOP_MODE	"1024x768"
42#endif
43#define FALLBACK_GOP_MODE	0
44
45extern struct x86_boot_params boot_params;
46
47struct btinfo_console btinfo_console;
48
49static EFI_GRAPHICS_OUTPUT_PROTOCOL *efi_gop;
50static int efi_gop_mode = -1;
51static CHAR16 keybuf[16];
52static int keybuf_read = 0;
53static int keybuf_write = 0;
54
55static SERIAL_IO_INTERFACE *serios[4];
56static int default_comspeed =
57#if defined(CONSPEED)
58    CONSPEED;
59#else
60    9600;
61#endif
62static u_char serbuf[16];
63static int serbuf_read = 0;
64static int serbuf_write = 0;
65
66static int raw_com_addr = 0;
67
68static void eficons_init_video(void);
69static void efi_switch_video_to_text_mode(void);
70
71static int efi_cons_getc(void);
72static int efi_cons_putc(int);
73static int efi_cons_iskey(int);
74static int efi_cons_waitforinputevent(uint64_t);
75
76static void efi_com_probe(void);
77static bool efi_valid_com(int);
78static int efi_com_init(int, int);
79static int efi_com_getc(void);
80static int efi_com_putc(int);
81static int efi_com_status(int);
82static int efi_com_waitforinputevent(uint64_t);
83
84static int raw_com_init(int, int);
85static int raw_com_getc(void);
86static int raw_com_putc(int);
87static int raw_com_status(int);
88static int raw_com_waitforinputevent(uint64_t);
89
90static int efi_find_gop_mode(char *);
91
92static int iodev;
93static int (*internal_getchar)(void) = efi_cons_getc;
94static int (*internal_putchar)(int) = efi_cons_putc;
95static int (*internal_iskey)(int) = efi_cons_iskey;
96static int (*internal_waitforinputevent)(uint64_t) = efi_cons_waitforinputevent;
97
98static int
99getcomaddr(int idx)
100{
101	static const short comioport[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
102
103	if (idx < __arraycount(comioport))
104		return comioport[idx];
105	return 0;
106}
107
108/*
109 * XXX only pass console parameters to kernel.
110 */
111void
112efi_consinit(int dev, int ioport, int speed)
113{
114	int i;
115
116	btinfo_console.speed = default_comspeed;
117
118	switch (dev) {
119	case CONSDEV_AUTO:
120		for (i = 0; i < __arraycount(serios); i++) {
121			iodev = CONSDEV_COM0 + i;
122			if (!efi_valid_com(iodev))
123				continue;
124			btinfo_console.addr = getcomaddr(i);
125
126			efi_cons_putc('0' + i);
127			efi_com_init(btinfo_console.addr, btinfo_console.speed);
128			/* check for:
129			 *  1. successful output
130			 *  2. optionally, keypress within 7s
131			 */
132			if (efi_com_putc(':') &&
133			    efi_com_putc('-') &&
134			    efi_com_putc('(') &&
135			    awaitkey(7, 0))
136				goto ok;
137		}
138		goto nocom;
139ok:
140		break;
141
142	case CONSDEV_COM0:
143	case CONSDEV_COM1:
144	case CONSDEV_COM2:
145	case CONSDEV_COM3:
146		iodev = dev;
147		btinfo_console.addr = ioport;
148		if (btinfo_console.addr == 0)
149			btinfo_console.addr = getcomaddr(iodev - CONSDEV_COM0);
150		if (speed != 0)
151			btinfo_console.speed = speed;
152		efi_com_init(btinfo_console.addr, btinfo_console.speed);
153		break;
154
155	case CONSDEV_COM0KBD:
156	case CONSDEV_COM1KBD:
157	case CONSDEV_COM2KBD:
158	case CONSDEV_COM3KBD:
159		iodev = dev - CONSDEV_COM0KBD + CONSDEV_COM0;
160		btinfo_console.addr = getcomaddr(iodev - CONSDEV_COM0);
161
162		efi_cons_putc('0' + iodev - CONSDEV_COM0);
163		efi_com_init(btinfo_console.addr, btinfo_console.speed);
164		/* check for:
165		 *  1. successful output
166		 *  2. optionally, keypress within 7s
167		 */
168		if (efi_com_putc(':') &&
169		    efi_com_putc('-') &&
170		    efi_com_putc('(') &&
171		    awaitkey(7, 0))
172			goto kbd;
173		/*FALLTHROUGH*/
174	case CONSDEV_PC:
175	default:
176nocom:
177		iodev = CONSDEV_PC;
178		internal_putchar = efi_cons_putc;
179kbd:
180		internal_getchar = efi_cons_getc;
181		internal_iskey = efi_cons_iskey;
182		internal_waitforinputevent = efi_cons_waitforinputevent;
183		memset(keybuf, 0, sizeof(keybuf));
184		keybuf_read = keybuf_write = 0;
185		break;
186	}
187
188	strlcpy(btinfo_console.devname, iodev == CONSDEV_PC ? "pc" : "com", 16);
189}
190
191int
192cninit(void)
193{
194
195	efi_switch_video_to_text_mode();
196	eficons_init_video();
197	efi_com_probe();
198
199	efi_consinit(boot_params.bp_consdev, boot_params.bp_consaddr,
200	    boot_params.bp_conspeed);
201
202	return 0;
203}
204
205void
206efi_cons_show(void)
207{
208	const bool pc_is_console = strcmp(btinfo_console.devname, "pc") == 0;
209	const bool com_is_console = strcmp(btinfo_console.devname, "com") == 0;
210	bool first = true;
211	bool found = false;
212	int i;
213
214	if (efi_gop != NULL) {
215		printf("pc");
216		if (pc_is_console)
217			printf("*");
218		first = false;
219	}
220
221	for (i = 0; i < __arraycount(serios); i++) {
222		if (serios[i] != NULL) {
223			if (!first)
224				printf(" ");
225			first = false;
226
227			printf("com%d", i);
228			if (com_is_console &&
229			    btinfo_console.addr == getcomaddr(i)) {
230				printf(",%d*", btinfo_console.speed);
231				found = true;
232			}
233		}
234	}
235	if (!found && com_is_console) {
236		if (!first)
237			printf(" ");
238		first = false;
239
240		printf("com,0x%x,%d*", btinfo_console.addr,
241		    btinfo_console.speed);
242	}
243
244	printf("\n");
245}
246
247static int
248efi_cons_getc(void)
249{
250	EFI_STATUS status;
251	EFI_INPUT_KEY key;
252	int c;
253
254	if (keybuf_read != keybuf_write) {
255		c = keybuf[keybuf_read];
256		keybuf_read = (keybuf_read + 1) % __arraycount(keybuf);
257		return c;
258	}
259
260	status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn,
261	    &key);
262	while (status == EFI_NOT_READY) {
263		WaitForSingleEvent(ST->ConIn->WaitForKey, 0);
264		status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2,
265		    ST->ConIn, &key);
266	}
267	return key.UnicodeChar;
268}
269
270static int
271efi_cons_putc(int c)
272{
273	CHAR16 buf[2];
274
275	buf[0] = c;
276	buf[1] = 0;
277	Output(buf);
278
279	return 1;
280}
281
282/*ARGSUSED*/
283static int
284efi_cons_iskey(int intr)
285{
286	EFI_STATUS status;
287	EFI_INPUT_KEY key;
288
289	if (keybuf_read != keybuf_write)
290		return 1;
291
292	status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn,
293	    &key);
294	if (EFI_ERROR(status))
295		return 0;
296
297	keybuf[keybuf_write] = key.UnicodeChar;
298	keybuf_write = (keybuf_write + 1) % __arraycount(keybuf);
299	return 1;
300}
301
302static int
303efi_cons_waitforinputevent(uint64_t timeout)
304{
305	EFI_STATUS status;
306
307	status = WaitForSingleEvent(ST->ConIn->WaitForKey, timeout);
308	if (!EFI_ERROR(status))
309		return 0;
310	if (status == EFI_TIMEOUT)
311		return ETIMEDOUT;
312	return EINVAL;
313}
314
315int
316getchar(void)
317{
318
319	return internal_getchar();
320}
321
322void
323putchar(int c)
324{
325
326	if (c == '\n')
327		internal_putchar('\r');
328	internal_putchar(c);
329}
330
331int
332iskey(int intr)
333{
334
335	return internal_iskey(intr);
336}
337
338char
339awaitkey(int timeout, int tell)
340{
341	char c = 0;
342
343	for (;;) {
344		char numbuf[32];
345		int len;
346
347		if (tell && timeout) {
348			len = snprintf(numbuf, sizeof(numbuf), "%d seconds. ",
349			    timeout);
350			if (len > 0 && len < sizeof(numbuf)) {
351				char *p = numbuf;
352
353				printf("%s", numbuf);
354				while (*p)
355					*p++ = '\b';
356			}
357		}
358		if (iskey(1)) {
359			/* flush input buffer */
360			while (iskey(0))
361				c = getchar();
362			if (c == 0)
363				c = -1;
364			if (tell && timeout)
365				printf("%s", numbuf);
366			break;
367		}
368		if (timeout--)
369			internal_waitforinputevent(10000000);
370		else
371			break;
372		if (tell)
373			printf("%s", numbuf);
374	}
375
376	if (tell)
377		printf("0 seconds.     \n");
378
379	return c;
380}
381
382void
383clear_pc_screen(void)
384{
385
386	uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
387}
388
389static uint8_t
390getdepth(const EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info)
391{
392
393	switch (info->PixelFormat) {
394	case PixelBlueGreenRedReserved8BitPerColor:
395	case PixelRedGreenBlueReserved8BitPerColor:
396		return 32;
397
398	case PixelBitMask:
399		return fls32(info->PixelInformation.RedMask
400		    | info->PixelInformation.GreenMask
401		    | info->PixelInformation.BlueMask
402		    | info->PixelInformation.ReservedMask);
403
404	case PixelBltOnly:
405	case PixelFormatMax:
406		return 0;
407	}
408	return 0;
409}
410
411static void
412setpixelformat(UINT32 mask, uint8_t *num, uint8_t *pos)
413{
414	uint8_t n, p;
415
416	n = popcount32(mask);
417	p = ffs32(mask);
418	if (p > 0)
419		p--;
420
421	*num = n;
422	*pos = p;
423}
424
425static void
426bi_framebuffer(void)
427{
428	EFI_STATUS status;
429	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
430	struct btinfo_framebuffer fb;
431	INT32 bestmode;
432	UINTN sz;
433
434	if (efi_gop == NULL)
435		goto nofb;
436
437	if (efi_gop_mode >= 0) {
438		bestmode = efi_gop_mode;
439	} else {
440		/* If a mode has not been selected, choose a default */
441		bestmode = efi_find_gop_mode(DEFAULT_GOP_MODE);
442		if (bestmode == -1)
443			bestmode = FALLBACK_GOP_MODE;
444	}
445
446	status = uefi_call_wrapper(efi_gop->SetMode, 2, efi_gop,
447	    bestmode);
448	if (EFI_ERROR(status) || efi_gop->Mode->Mode != bestmode) {
449		printf("GOP setmode failed: %" PRIxMAX "\n",
450		    (uintmax_t)status);
451		goto nofb;
452	}
453
454	status = uefi_call_wrapper(efi_gop->QueryMode, 4,
455	    efi_gop, bestmode, &sz, &info);
456	if (EFI_ERROR(status)) {
457		printf("GOP querymode failed: %" PRIxMAX "\n",
458		    (uintmax_t)status);
459		goto nofb;
460	}
461
462	memset(&fb, 0, sizeof(fb));
463	fb.physaddr = efi_gop->Mode->FrameBufferBase;
464	fb.flags = 0;
465	fb.width = info->HorizontalResolution;
466	fb.height = info->VerticalResolution;
467	fb.depth = getdepth(info);
468	fb.stride = info->PixelsPerScanLine * ((fb.depth + 7) / 8);
469	fb.vbemode = 0;	/* XXX */
470
471	switch (info->PixelFormat) {
472	case PixelBlueGreenRedReserved8BitPerColor:
473		fb.rnum = 8;
474		fb.gnum = 8;
475		fb.bnum = 8;
476		fb.rpos = 16;
477		fb.gpos = 8;
478		fb.bpos = 0;
479		break;
480
481	case PixelRedGreenBlueReserved8BitPerColor:
482		fb.rnum = 8;
483		fb.gnum = 8;
484		fb.bnum = 8;
485		fb.rpos = 0;
486		fb.gpos = 8;
487		fb.bpos = 16;
488		break;
489
490	case PixelBitMask:
491		setpixelformat(info->PixelInformation.RedMask,
492		    &fb.rnum, &fb.rpos);
493		setpixelformat(info->PixelInformation.GreenMask,
494		    &fb.gnum, &fb.gpos);
495		setpixelformat(info->PixelInformation.BlueMask,
496		    &fb.bnum, &fb.bpos);
497		break;
498
499	case PixelBltOnly:
500	case PixelFormatMax:
501		panic("Error: invalid pixel format (%d)", info->PixelFormat);
502		break;
503	}
504
505	framebuffer_configure(&fb);
506	return;
507
508nofb:
509	framebuffer_configure(NULL);
510}
511
512int
513vbe_commit(void)
514{
515
516	bi_framebuffer();
517	return 0;
518}
519
520static void
521print_text_modes(void)
522{
523	EFI_STATUS status;
524	UINTN cols, rows;
525	INT32 i, curmode;
526
527	curmode = ST->ConOut->Mode->Mode;
528	for (i = 0; i < ST->ConOut->Mode->MaxMode; i++) {
529		status = uefi_call_wrapper(ST->ConOut->QueryMode, 4,
530		    ST->ConOut, i, &cols, &rows);
531		if (EFI_ERROR(status))
532			continue;
533		printf("%c%d: %" PRIxMAX "x%" PRIxMAX "\n",
534		    i == curmode ? '*' : ' ', i, (uintmax_t)cols, (uintmax_t)rows);
535	}
536}
537
538static int
539efi_find_text_mode(char *arg)
540{
541	EFI_STATUS status;
542	UINTN cols, rows;
543	INT32 i;
544	char mode[32];
545
546	for (i = 0; i < ST->ConOut->Mode->MaxMode; i++) {
547		status = uefi_call_wrapper(ST->ConOut->QueryMode, 4,
548		    ST->ConOut, i, &cols, &rows);
549		if (EFI_ERROR(status))
550			continue;
551		snprintf(mode, sizeof(mode), "%" PRIuMAX "x%" PRIuMAX,
552		    (uintmax_t)cols, (uintmax_t)rows);
553		if (strcmp(arg, mode) == 0)
554			return i;
555	}
556	return -1;
557}
558
559void
560command_text(char *arg)
561{
562	EFI_STATUS status;
563	INT32 modenum;
564
565	if (*arg == '\0' || strcmp(arg, "list") == 0) {
566		print_text_modes();
567		return;
568	}
569
570	if (strchr(arg, 'x') != NULL) {
571		modenum = efi_find_text_mode(arg);
572		if (modenum == -1) {
573			printf("mode %s not supported by firmware\n", arg);
574			return;
575		}
576	} else {
577		modenum = strtoul(arg, NULL, 0);
578	}
579
580	status = uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, modenum);
581	if (!EFI_ERROR(status))
582		return;
583
584	printf("invalid flag, must be 'list', a display mode, "
585	    "or a mode number\n");
586}
587
588static int
589print_gop_modes(void)
590{
591	EFI_STATUS status;
592	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
593	UINTN sz;
594	UINT32 i;
595	uint8_t depth;
596
597	if (efi_gop == NULL)
598		return 1;
599
600	for (i = 0; i < efi_gop->Mode->MaxMode; i++) {
601		status = uefi_call_wrapper(efi_gop->QueryMode, 4, efi_gop, i,
602		    &sz, &info);
603		if (EFI_ERROR(status) && status == EFI_NOT_STARTED) {
604			status = uefi_call_wrapper(efi_gop->SetMode, 2,
605			    efi_gop, efi_gop->Mode->Mode);
606			status = uefi_call_wrapper(efi_gop->QueryMode, 4,
607			    efi_gop, i, &sz, &info);
608		}
609		if (EFI_ERROR(status))
610			continue;
611
612		printf("%c%d: %dx%d ",
613		    memcmp(info, efi_gop->Mode->Info, sizeof(*info)) == 0 ?
614		      '*' : ' ',
615		      i, info->HorizontalResolution, info->VerticalResolution);
616		switch (info->PixelFormat) {
617		case PixelRedGreenBlueReserved8BitPerColor:
618			printf("RGBR");
619			break;
620		case PixelBlueGreenRedReserved8BitPerColor:
621			printf("BGRR");
622			break;
623		case PixelBitMask:
624			printf("R:%08x G:%08x B:%08x X:%08x",
625			    info->PixelInformation.RedMask,
626			    info->PixelInformation.GreenMask,
627			    info->PixelInformation.BlueMask,
628			    info->PixelInformation.ReservedMask);
629			break;
630		case PixelBltOnly:
631			printf("(blt only)");
632			break;
633		default:
634			printf("(Invalid pixel format)");
635			break;
636		}
637		printf(" pitch %d", info->PixelsPerScanLine);
638		depth = getdepth(info);
639		if (depth > 0)
640			printf(" bpp %d", depth);
641		printf("\n");
642	}
643
644	return 0;
645}
646
647static int
648efi_find_gop_mode(char *arg)
649{
650	EFI_STATUS status;
651	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
652	UINTN sz;
653	UINT32 i;
654	char mode[32];
655	uint8_t depth;
656
657	for (i = 0; i < efi_gop->Mode->MaxMode; i++) {
658		status = uefi_call_wrapper(efi_gop->QueryMode, 4, efi_gop, i,
659		    &sz, &info);
660		if (EFI_ERROR(status))
661			continue;
662
663		depth = getdepth(info);
664		if (depth == 0)
665			continue;
666
667		snprintf(mode, sizeof(mode), "%lux%lux%u",
668		    (long)info->HorizontalResolution,
669		    (long)info->VerticalResolution,
670		    depth);
671		if (strcmp(arg, mode) == 0)
672			return i;
673
674		snprintf(mode, sizeof(mode), "%lux%lu",
675		    (long)info->HorizontalResolution,
676		    (long)info->VerticalResolution);
677		if (strcmp(arg, mode) == 0)
678			return i;
679	}
680	return -1;
681}
682
683void
684command_gop(char *arg)
685{
686	EFI_STATUS status;
687	INT32 modenum;
688
689	if (efi_gop == NULL) {
690		printf("GOP not supported by firmware\n");
691		return;
692	}
693
694	if (*arg == '\0' || strcmp(arg, "list") == 0) {
695		print_gop_modes();
696		return;
697	}
698
699	if (strchr(arg, 'x') != NULL) {
700		modenum = efi_find_gop_mode(arg);
701		if (modenum == -1) {
702			printf("mode %s not supported by firmware\n", arg);
703			return;
704		}
705	} else {
706		modenum = strtoul(arg, NULL, 0);
707	}
708
709	status = uefi_call_wrapper(efi_gop->SetMode, 2, efi_gop, modenum);
710	if (!EFI_ERROR(status) && efi_gop->Mode->Mode == modenum) {
711		efi_gop_mode = modenum;
712		return;
713	}
714
715	printf("invalid flag, must be 'list', a display mode, "
716	    "or a mode number\n");
717}
718
719static void
720eficons_init_video(void)
721{
722	EFI_STATUS status;
723	UINTN cols, rows;
724	INT32 i, best, mode80x25, mode100x31;
725
726	/*
727	 * Setup text mode
728	 */
729	uefi_call_wrapper(ST->ConOut->Reset, 2, ST->ConOut, TRUE);
730
731	mode80x25 = mode100x31 = -1;
732	for (i = 0; i < ST->ConOut->Mode->MaxMode; i++) {
733		status = uefi_call_wrapper(ST->ConOut->QueryMode, 4,
734		    ST->ConOut, i, &cols, &rows);
735		if (EFI_ERROR(status))
736			continue;
737
738		if (mode80x25 < 0 && cols == 80 && rows == 25)
739			mode80x25 = i;
740		else if (mode100x31 < 0 && cols == 100 && rows == 31)
741			mode100x31 = i;
742	}
743	best = mode100x31 >= 0 ? mode100x31 : mode80x25 >= 0 ? mode80x25 : -1;
744	if (best >= 0)
745		uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, best);
746	uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
747	uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
748
749	LibLocateProtocol(&GraphicsOutputProtocol, (void **)&efi_gop);
750}
751
752/*
753 * for Apple EFI
754 */
755#define	CONSOLE_CONTROL_PROTOCOL \
756	{0xf42f7782, 0x12e, 0x4c12, {0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21}}
757static EFI_GUID ConsoleControlProtocol = CONSOLE_CONTROL_PROTOCOL;
758
759struct _EFI_CONSOLE_CONTROL_INTERFACE;
760typedef struct _EFI_CONSOLE_CONTROL_INTERFACE EFI_CONSOLE_CONTROL_INTERFACE;
761typedef enum { EfiConsoleControlScreenText } EFI_CONSOLE_CONTROL_SCREEN_MODE;
762typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE) (
763	IN EFI_CONSOLE_CONTROL_INTERFACE *This,
764	IN EFI_CONSOLE_CONTROL_SCREEN_MODE Mode
765);
766struct _EFI_CONSOLE_CONTROL_INTERFACE {
767	VOID *GetMode;
768	EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode;
769	VOID *LockStdIn;
770};
771
772static void
773efi_switch_video_to_text_mode(void)
774{
775	EFI_STATUS status;
776	EFI_CONSOLE_CONTROL_INTERFACE *cci;
777
778	/* Set up the console, so printf works. */
779	status = LibLocateProtocol(&ConsoleControlProtocol, (void **)&cci);
780	if (!EFI_ERROR(status)) {
781		uefi_call_wrapper(cci->SetMode, 2, cci,
782		    EfiConsoleControlScreenText);
783	}
784}
785
786/*
787 * serial port
788 */
789static void
790efi_com_probe(void)
791{
792	EFI_STATUS status;
793	UINTN i, nhandles;
794	EFI_HANDLE *handles;
795	EFI_DEVICE_PATH	*dp, *dp0;
796	EFI_DEV_PATH_PTR dpp;
797	SERIAL_IO_INTERFACE *serio;
798	int uid = -1;
799
800	status = LibLocateHandle(ByProtocol, &SerialIoProtocol, NULL,
801	    &nhandles, &handles);
802	if (EFI_ERROR(status))
803		return;
804
805	for (i = 0; i < nhandles; i++) {
806		/*
807		 * Identify port number of the handle.  This assumes ACPI
808		 * UID 0-3 map to legacy COM[1-4] and they use the legacy
809		 * port address.
810		 */
811		status = uefi_call_wrapper(BS->HandleProtocol, 3, handles[i],
812		    &DevicePathProtocol, (void **)&dp0);
813		if (EFI_ERROR(status))
814			continue;
815
816		for (uid = -1, dp = dp0;
817		     !IsDevicePathEnd(dp);
818		     dp = NextDevicePathNode(dp)) {
819
820			if (DevicePathType(dp) == ACPI_DEVICE_PATH &&
821			    DevicePathSubType(dp) == ACPI_DP) {
822				dpp = (EFI_DEV_PATH_PTR)dp;
823				if (dpp.Acpi->HID == EISA_PNP_ID(0x0501)) {
824					uid = dpp.Acpi->UID;
825					break;
826				}
827			}
828		}
829		if (uid < 0 || __arraycount(serios) <= uid)
830			continue;
831
832		/* Prepare SERIAL_IO_INTERFACE */
833		status = uefi_call_wrapper(BS->HandleProtocol, 3, handles[i],
834		    &SerialIoProtocol, (void **)&serio);
835		if (EFI_ERROR(status))
836			continue;
837
838		serios[uid] = serio;
839	}
840
841	FreePool(handles);
842
843}
844
845static bool
846efi_valid_com(int dev)
847{
848	int idx;
849
850	switch (dev) {
851	default:
852	case CONSDEV_PC:
853		return false;
854
855	case CONSDEV_COM0:
856	case CONSDEV_COM1:
857	case CONSDEV_COM2:
858	case CONSDEV_COM3:
859		idx = dev - CONSDEV_COM0;
860		break;
861	}
862
863	return idx < __arraycount(serios) &&
864	    serios[idx] != NULL &&
865	    getcomaddr(idx) != 0;
866}
867
868static int
869efi_com_init(int addr, int speed)
870{
871	EFI_STATUS status;
872	SERIAL_IO_INTERFACE *serio;
873
874	if (speed <= 0)
875		return 0;
876
877	if (!efi_valid_com(iodev))
878		return raw_com_init(addr, speed);
879
880	serio = serios[iodev - CONSDEV_COM0];
881
882	if (serio->Mode->BaudRate != btinfo_console.speed) {
883		status = uefi_call_wrapper(serio->SetAttributes, 7, serio,
884		    speed, serio->Mode->ReceiveFifoDepth,
885		    serio->Mode->Timeout, serio->Mode->Parity,
886		    serio->Mode->DataBits, serio->Mode->StopBits);
887		if (EFI_ERROR(status)) {
888			printf("com%d: SetAttribute() failed with status=%" PRIxMAX
889			    "\n", iodev - CONSDEV_COM0, (uintmax_t)status);
890			return 0;
891		}
892	}
893
894	raw_com_addr = 0;
895	default_comspeed = speed;
896	internal_getchar = efi_com_getc;
897	internal_putchar = efi_com_putc;
898	internal_iskey = efi_com_status;
899	internal_waitforinputevent = efi_com_waitforinputevent;
900	memset(serbuf, 0, sizeof(serbuf));
901	serbuf_read = serbuf_write = 0;
902
903	return speed;
904}
905
906static int
907efi_com_getc(void)
908{
909	EFI_STATUS status;
910	SERIAL_IO_INTERFACE *serio;
911	UINTN sz;
912	u_char c;
913
914	if (!efi_valid_com(iodev))
915		panic("Invalid serial port: iodev=%d", iodev);
916
917	if (serbuf_read != serbuf_write) {
918		c = serbuf[serbuf_read];
919		serbuf_read = (serbuf_read + 1) % __arraycount(serbuf);
920		return c;
921	}
922
923	serio = serios[iodev - CONSDEV_COM0];
924
925	for (;;) {
926		sz = 1;
927		status = uefi_call_wrapper(serio->Read, 3, serio, &sz, &c);
928		if (!EFI_ERROR(status) && sz > 0)
929			break;
930		if (status != EFI_TIMEOUT && EFI_ERROR(status))
931			panic("Error reading from serial status=%"PRIxMAX,
932			    (uintmax_t)status);
933	}
934	return c;
935}
936
937static int
938efi_com_putc(int c)
939{
940	EFI_STATUS status;
941	SERIAL_IO_INTERFACE *serio;
942	UINTN sz = 1;
943	u_char buf;
944
945	if (!efi_valid_com(iodev))
946		return 0;
947
948	serio = serios[iodev - CONSDEV_COM0];
949	buf = c;
950	status = uefi_call_wrapper(serio->Write, 3, serio, &sz, &buf);
951	if (EFI_ERROR(status) || sz < 1)
952		return 0;
953	return 1;
954}
955
956/*ARGSUSED*/
957static int
958efi_com_status(int intr)
959{
960	EFI_STATUS status;
961	SERIAL_IO_INTERFACE *serio;
962	UINTN sz;
963	u_char c;
964
965	if (!efi_valid_com(iodev))
966		panic("Invalid serial port: iodev=%d", iodev);
967
968	if (serbuf_read != serbuf_write)
969		return 1;
970
971	serio = serios[iodev - CONSDEV_COM0];
972	sz = 1;
973	status = uefi_call_wrapper(serio->Read, 3, serio, &sz, &c);
974	if (EFI_ERROR(status) || sz < 1)
975		return 0;
976
977	serbuf[serbuf_write] = c;
978	serbuf_write = (serbuf_write + 1) % __arraycount(serbuf);
979	return 1;
980}
981
982static void
983efi_com_periodic_event(EFI_EVENT event, void *ctx)
984{
985	EFI_EVENT timer = ctx;
986
987	if (efi_com_status(0)) {
988		uefi_call_wrapper(BS->SetTimer, 3, event, TimerCancel, 0);
989		uefi_call_wrapper(BS->SignalEvent, 1, timer);
990	}
991}
992
993static int
994efi_com_waitforinputevent(uint64_t timeout)
995{
996	EFI_STATUS status;
997	EFI_EVENT timer, periodic;
998
999	status = uefi_call_wrapper(BS->CreateEvent, 5, EVT_TIMER, 0, NULL, NULL,
1000	    &timer);
1001	if (EFI_ERROR(status))
1002		return EINVAL;
1003
1004        status = uefi_call_wrapper(BS->CreateEvent, 5,
1005	    EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, efi_com_periodic_event,
1006	    timer, &periodic);
1007	if (EFI_ERROR(status)) {
1008		uefi_call_wrapper(BS->CloseEvent, 1, timer);
1009		return EINVAL;
1010	}
1011
1012	status = uefi_call_wrapper(BS->SetTimer, 3, periodic, TimerPeriodic,
1013	    1000000);	/* 100ms */
1014	if (EFI_ERROR(status)) {
1015		uefi_call_wrapper(BS->CloseEvent, 1, periodic);
1016		uefi_call_wrapper(BS->CloseEvent, 1, timer);
1017		return EINVAL;
1018	}
1019	status = WaitForSingleEvent(&timer, timeout);
1020	uefi_call_wrapper(BS->SetTimer, 3, periodic, TimerCancel, 0);
1021	uefi_call_wrapper(BS->CloseEvent, 1, periodic);
1022	uefi_call_wrapper(BS->CloseEvent, 1, timer);
1023	if (!EFI_ERROR(status))
1024		return 0;
1025	if (status == EFI_TIMEOUT)
1026		return ETIMEDOUT;
1027	return EINVAL;
1028}
1029
1030static int
1031raw_com_init(int addr, int speed)
1032{
1033
1034	if (addr == 0 || speed <= 0)
1035		return 0;
1036
1037	speed = cominit_d(addr, speed);
1038
1039	raw_com_addr = addr;
1040	default_comspeed = speed;
1041	internal_getchar = raw_com_getc;
1042	internal_putchar = raw_com_putc;
1043	internal_iskey = raw_com_status;
1044	internal_waitforinputevent = raw_com_waitforinputevent;
1045
1046	return speed;
1047}
1048
1049static int
1050raw_com_getc(void)
1051{
1052
1053	if (raw_com_addr == 0)
1054		panic("%s: Invalid serial port", __func__);
1055	return comgetc_d(raw_com_addr);
1056}
1057
1058static int
1059raw_com_putc(int c)
1060{
1061
1062	if (raw_com_addr == 0)
1063		panic("%s: Invalid serial port", __func__);
1064	return computc_d(c, raw_com_addr);
1065}
1066
1067static int
1068raw_com_status(int intr)
1069{
1070
1071	if (raw_com_addr == 0)
1072		panic("%s: Invalid serial port", __func__);
1073	return comstatus_d(raw_com_addr);
1074}
1075
1076static int
1077raw_com_waitforinputevent(uint64_t timeout /* in 0.1 usec */)
1078{
1079	uint64_t ms;
1080
1081	if (raw_com_addr == 0)
1082		panic("%s: Invalid serial port", __func__);
1083
1084	for (ms = howmany(timeout, 10 * 1000); ms != 0; ms--) {
1085		if (raw_com_status(0))
1086			return 0;
1087		uefi_call_wrapper(BS->Stall, 1, 1000);
1088	}
1089	return ETIMEDOUT;
1090}
1091