/*      $NetBSD: eficons.c,v 1.15 2025/06/19 15:00:17 manu Exp $        */

/*-
* Copyright (c) 2016 Kimihiro Nonaka <[email protected]>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include <sys/param.h>
#include <sys/bitops.h>
#include <sys/stdint.h>

#include <comio_direct.h>

#include "efiboot.h"

#include "bootinfo.h"
#include "vbe.h"

#ifndef DEFAULT_GOP_MODE
#define DEFAULT_GOP_MODE        "1024x768"
#endif
#define FALLBACK_GOP_MODE       0

extern struct x86_boot_params boot_params;

struct btinfo_console btinfo_console;

static EFI_GRAPHICS_OUTPUT_PROTOCOL *efi_gop;
static int efi_gop_mode = -1;
static CHAR16 keybuf[16];
static int keybuf_read = 0;
static int keybuf_write = 0;

static SERIAL_IO_INTERFACE *serios[4];
static int default_comspeed =
#if defined(CONSPEED)
   CONSPEED;
#else
   9600;
#endif
static u_char serbuf[16];
static int serbuf_read = 0;
static int serbuf_write = 0;

static int raw_com_addr = 0;

static void eficons_init_video(void);
static void efi_switch_video_to_text_mode(void);

static int efi_cons_getc(void);
static int efi_cons_putc(int);
static int efi_cons_iskey(int);
static int efi_cons_waitforinputevent(uint64_t);

static void efi_com_probe(void);
static bool efi_valid_com(int);
static int efi_com_init(int, int);
static int efi_com_getc(void);
static int efi_com_putc(int);
static int efi_com_status(int);
static int efi_com_waitforinputevent(uint64_t);

static int raw_com_init(int, int);
static int raw_com_getc(void);
static int raw_com_putc(int);
static int raw_com_status(int);
static int raw_com_waitforinputevent(uint64_t);

static int efi_find_gop_mode(char *);

static int iodev;
static int (*internal_getchar)(void) = efi_cons_getc;
static int (*internal_putchar)(int) = efi_cons_putc;
static int (*internal_iskey)(int) = efi_cons_iskey;
static int (*internal_waitforinputevent)(uint64_t) = efi_cons_waitforinputevent;

static int
getcomaddr(int idx)
{
       static const short comioport[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };

       if (idx < __arraycount(comioport))
               return comioport[idx];
       return 0;
}

/*
* XXX only pass console parameters to kernel.
*/
void
efi_consinit(int dev, int ioport, int speed)
{
       int i;

       btinfo_console.speed = default_comspeed;

       switch (dev) {
       case CONSDEV_AUTO:
               for (i = 0; i < __arraycount(serios); i++) {
                       iodev = CONSDEV_COM0 + i;
                       if (!efi_valid_com(iodev))
                               continue;
                       btinfo_console.addr = getcomaddr(i);

                       efi_cons_putc('0' + i);
                       efi_com_init(btinfo_console.addr, btinfo_console.speed);
                       /* check for:
                        *  1. successful output
                        *  2. optionally, keypress within 7s
                        */
                       if (efi_com_putc(':') &&
                           efi_com_putc('-') &&
                           efi_com_putc('(') &&
                           awaitkey(7, 0))
                               goto ok;
               }
               goto nocom;
ok:
               break;

       case CONSDEV_COM0:
       case CONSDEV_COM1:
       case CONSDEV_COM2:
       case CONSDEV_COM3:
               iodev = dev;
               btinfo_console.addr = ioport;
               if (btinfo_console.addr == 0)
                       btinfo_console.addr = getcomaddr(iodev - CONSDEV_COM0);
               if (speed != 0)
                       btinfo_console.speed = speed;
               efi_com_init(btinfo_console.addr, btinfo_console.speed);
               break;

       case CONSDEV_COM0KBD:
       case CONSDEV_COM1KBD:
       case CONSDEV_COM2KBD:
       case CONSDEV_COM3KBD:
               iodev = dev - CONSDEV_COM0KBD + CONSDEV_COM0;
               btinfo_console.addr = getcomaddr(iodev - CONSDEV_COM0);

               efi_cons_putc('0' + iodev - CONSDEV_COM0);
               efi_com_init(btinfo_console.addr, btinfo_console.speed);
               /* check for:
                *  1. successful output
                *  2. optionally, keypress within 7s
                */
               if (efi_com_putc(':') &&
                   efi_com_putc('-') &&
                   efi_com_putc('(') &&
                   awaitkey(7, 0))
                       goto kbd;
               /*FALLTHROUGH*/
       case CONSDEV_PC:
       default:
nocom:
               iodev = CONSDEV_PC;
               internal_putchar = efi_cons_putc;
kbd:
               internal_getchar = efi_cons_getc;
               internal_iskey = efi_cons_iskey;
               internal_waitforinputevent = efi_cons_waitforinputevent;
               memset(keybuf, 0, sizeof(keybuf));
               keybuf_read = keybuf_write = 0;
               break;
       }

       strlcpy(btinfo_console.devname, iodev == CONSDEV_PC ? "pc" : "com", 16);
}

int
cninit(void)
{

       efi_switch_video_to_text_mode();
       eficons_init_video();
       efi_com_probe();

       efi_consinit(boot_params.bp_consdev, boot_params.bp_consaddr,
           boot_params.bp_conspeed);

       return 0;
}

void
efi_cons_show(void)
{
       const bool pc_is_console = strcmp(btinfo_console.devname, "pc") == 0;
       const bool com_is_console = strcmp(btinfo_console.devname, "com") == 0;
       bool first = true;
       bool found = false;
       int i;

       if (efi_gop != NULL) {
               printf("pc");
               if (pc_is_console)
                       printf("*");
               first = false;
       }

       for (i = 0; i < __arraycount(serios); i++) {
               if (serios[i] != NULL) {
                       if (!first)
                               printf(" ");
                       first = false;

                       printf("com%d", i);
                       if (com_is_console &&
                           btinfo_console.addr == getcomaddr(i)) {
                               printf(",%d*", btinfo_console.speed);
                               found = true;
                       }
               }
       }
       if (!found && com_is_console) {
               if (!first)
                       printf(" ");
               first = false;

               printf("com,0x%x,%d*", btinfo_console.addr,
                   btinfo_console.speed);
       }

       printf("\n");
}

static int
efi_cons_getc(void)
{
       EFI_STATUS status;
       EFI_INPUT_KEY key;
       int c;

       if (keybuf_read != keybuf_write) {
               c = keybuf[keybuf_read];
               keybuf_read = (keybuf_read + 1) % __arraycount(keybuf);
               return c;
       }

       status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn,
           &key);
       while (status == EFI_NOT_READY) {
               WaitForSingleEvent(ST->ConIn->WaitForKey, 0);
               status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2,
                   ST->ConIn, &key);
       }
       return key.UnicodeChar;
}

static int
efi_cons_putc(int c)
{
       CHAR16 buf[2];

       buf[0] = c;
       buf[1] = 0;
       Output(buf);

       return 1;
}

/*ARGSUSED*/
static int
efi_cons_iskey(int intr)
{
       EFI_STATUS status;
       EFI_INPUT_KEY key;

       if (keybuf_read != keybuf_write)
               return 1;

       status = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn,
           &key);
       if (EFI_ERROR(status))
               return 0;

       keybuf[keybuf_write] = key.UnicodeChar;
       keybuf_write = (keybuf_write + 1) % __arraycount(keybuf);
       return 1;
}

static int
efi_cons_waitforinputevent(uint64_t timeout)
{
       EFI_STATUS status;

       status = WaitForSingleEvent(ST->ConIn->WaitForKey, timeout);
       if (!EFI_ERROR(status))
               return 0;
       if (status == EFI_TIMEOUT)
               return ETIMEDOUT;
       return EINVAL;
}

int
getchar(void)
{

       return internal_getchar();
}

void
putchar(int c)
{

       if (c == '\n')
               internal_putchar('\r');
       internal_putchar(c);
}

int
iskey(int intr)
{

       return internal_iskey(intr);
}

char
awaitkey(int timeout, int tell)
{
       char c = 0;

       for (;;) {
               char numbuf[32];
               int len;

               if (tell && timeout) {
                       len = snprintf(numbuf, sizeof(numbuf), "%d seconds. ",
                           timeout);
                       if (len > 0 && len < sizeof(numbuf)) {
                               char *p = numbuf;

                               printf("%s", numbuf);
                               while (*p)
                                       *p++ = '\b';
                       }
               }
               if (iskey(1)) {
                       /* flush input buffer */
                       while (iskey(0))
                               c = getchar();
                       if (c == 0)
                               c = -1;
                       if (tell && timeout)
                               printf("%s", numbuf);
                       break;
               }
               if (timeout--) {
                       /*
                        * UEFI spec 2.10 section 7.1.7
                        * EFI_BOOT_SERVICES.SetTimer() timeout in 100ns units
                        * but the implementation may be broken and faster:
                        * check date to make sure we are slow enough.
                        */
                       EFI_TIME t1, t2;

                       uefi_call_wrapper(RT->GetTime, 2, &t1, NULL);
                       do {
                               internal_waitforinputevent(10000000); /* 1s */
                               uefi_call_wrapper(RT->GetTime, 2, &t2, NULL);
                       } while (t1.Year == t2.Year &&
                                t1.Month == t2.Month &&
                                t1.Day == t2.Day &&
                                t1.Hour == t2.Hour &&
                                t1.Minute == t2.Minute &&
                                t1.Second == t2.Second);
               }
               else
                       break;
               if (tell)
                       printf("%s", numbuf);
       }

       if (tell)
               printf("0 seconds.     \n");

       return c;
}

void
clear_pc_screen(void)
{

       uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
}

static uint8_t
getdepth(const EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info)
{

       switch (info->PixelFormat) {
       case PixelBlueGreenRedReserved8BitPerColor:
       case PixelRedGreenBlueReserved8BitPerColor:
               return 32;

       case PixelBitMask:
               return fls32(info->PixelInformation.RedMask
                   | info->PixelInformation.GreenMask
                   | info->PixelInformation.BlueMask
                   | info->PixelInformation.ReservedMask);

       case PixelBltOnly:
       case PixelFormatMax:
               return 0;
       }
       return 0;
}

static void
setpixelformat(UINT32 mask, uint8_t *num, uint8_t *pos)
{
       uint8_t n, p;

       n = popcount32(mask);
       p = ffs32(mask);
       if (p > 0)
               p--;

       *num = n;
       *pos = p;
}

static void
bi_framebuffer(void)
{
       EFI_STATUS status;
       EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
       struct btinfo_framebuffer fb;
       INT32 bestmode;
       UINTN sz;

       if (efi_gop == NULL)
               goto nofb;

       if (efi_gop_mode >= 0) {
               bestmode = efi_gop_mode;
       } else {
               /* If a mode has not been selected, choose a default */
               bestmode = efi_find_gop_mode(DEFAULT_GOP_MODE);
               if (bestmode == -1)
                       bestmode = FALLBACK_GOP_MODE;
       }

       status = uefi_call_wrapper(efi_gop->SetMode, 2, efi_gop,
           bestmode);
       if (EFI_ERROR(status) || efi_gop->Mode->Mode != bestmode) {
               printf("GOP setmode failed: %" PRIxMAX "\n",
                   (uintmax_t)status);
               goto nofb;
       }

       status = uefi_call_wrapper(efi_gop->QueryMode, 4,
           efi_gop, bestmode, &sz, &info);
       if (EFI_ERROR(status)) {
               printf("GOP querymode failed: %" PRIxMAX "\n",
                   (uintmax_t)status);
               goto nofb;
       }

       memset(&fb, 0, sizeof(fb));
       fb.physaddr = efi_gop->Mode->FrameBufferBase;
       fb.flags = 0;
       fb.width = info->HorizontalResolution;
       fb.height = info->VerticalResolution;
       fb.depth = getdepth(info);
       fb.stride = info->PixelsPerScanLine * ((fb.depth + 7) / 8);
       fb.vbemode = 0; /* XXX */

       switch (info->PixelFormat) {
       case PixelBlueGreenRedReserved8BitPerColor:
               fb.rnum = 8;
               fb.gnum = 8;
               fb.bnum = 8;
               fb.rpos = 16;
               fb.gpos = 8;
               fb.bpos = 0;
               break;

       case PixelRedGreenBlueReserved8BitPerColor:
               fb.rnum = 8;
               fb.gnum = 8;
               fb.bnum = 8;
               fb.rpos = 0;
               fb.gpos = 8;
               fb.bpos = 16;
               break;

       case PixelBitMask:
               setpixelformat(info->PixelInformation.RedMask,
                   &fb.rnum, &fb.rpos);
               setpixelformat(info->PixelInformation.GreenMask,
                   &fb.gnum, &fb.gpos);
               setpixelformat(info->PixelInformation.BlueMask,
                   &fb.bnum, &fb.bpos);
               break;

       case PixelBltOnly:
       case PixelFormatMax:
               panic("Error: invalid pixel format (%d)", info->PixelFormat);
               break;
       }

       framebuffer_configure(&fb);
       return;

nofb:
       framebuffer_configure(NULL);
}

int
vbe_commit(void)
{

       bi_framebuffer();
       return 0;
}

static void
print_text_modes(void)
{
       EFI_STATUS status;
       UINTN cols, rows;
       INT32 i, curmode;

       curmode = ST->ConOut->Mode->Mode;
       for (i = 0; i < ST->ConOut->Mode->MaxMode; i++) {
               status = uefi_call_wrapper(ST->ConOut->QueryMode, 4,
                   ST->ConOut, i, &cols, &rows);
               if (EFI_ERROR(status))
                       continue;
               printf("%c%d: %" PRIxMAX "x%" PRIxMAX "\n",
                   i == curmode ? '*' : ' ', i, (uintmax_t)cols, (uintmax_t)rows);
       }
}

static int
efi_find_text_mode(char *arg)
{
       EFI_STATUS status;
       UINTN cols, rows;
       INT32 i;
       char mode[32];

       for (i = 0; i < ST->ConOut->Mode->MaxMode; i++) {
               status = uefi_call_wrapper(ST->ConOut->QueryMode, 4,
                   ST->ConOut, i, &cols, &rows);
               if (EFI_ERROR(status))
                       continue;
               snprintf(mode, sizeof(mode), "%" PRIuMAX "x%" PRIuMAX,
                   (uintmax_t)cols, (uintmax_t)rows);
               if (strcmp(arg, mode) == 0)
                       return i;
       }
       return -1;
}

void
command_text(char *arg)
{
       EFI_STATUS status;
       INT32 modenum;

       if (*arg == '\0' || strcmp(arg, "list") == 0) {
               print_text_modes();
               return;
       }

       if (strchr(arg, 'x') != NULL) {
               modenum = efi_find_text_mode(arg);
               if (modenum == -1) {
                       printf("mode %s not supported by firmware\n", arg);
                       return;
               }
       } else {
               modenum = strtoul(arg, NULL, 0);
       }

       status = uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, modenum);
       if (!EFI_ERROR(status))
               return;

       printf("invalid flag, must be 'list', a display mode, "
           "or a mode number\n");
}

static int
print_gop_modes(void)
{
       EFI_STATUS status;
       EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
       UINTN sz;
       UINT32 i;
       uint8_t depth;

       if (efi_gop == NULL)
               return 1;

       for (i = 0; i < efi_gop->Mode->MaxMode; i++) {
               status = uefi_call_wrapper(efi_gop->QueryMode, 4, efi_gop, i,
                   &sz, &info);
               if (EFI_ERROR(status) && status == EFI_NOT_STARTED) {
                       status = uefi_call_wrapper(efi_gop->SetMode, 2,
                           efi_gop, efi_gop->Mode->Mode);
                       status = uefi_call_wrapper(efi_gop->QueryMode, 4,
                           efi_gop, i, &sz, &info);
               }
               if (EFI_ERROR(status))
                       continue;

               printf("%c%d: %dx%d ",
                   memcmp(info, efi_gop->Mode->Info, sizeof(*info)) == 0 ?
                     '*' : ' ',
                     i, info->HorizontalResolution, info->VerticalResolution);
               switch (info->PixelFormat) {
               case PixelRedGreenBlueReserved8BitPerColor:
                       printf("RGBR");
                       break;
               case PixelBlueGreenRedReserved8BitPerColor:
                       printf("BGRR");
                       break;
               case PixelBitMask:
                       printf("R:%08x G:%08x B:%08x X:%08x",
                           info->PixelInformation.RedMask,
                           info->PixelInformation.GreenMask,
                           info->PixelInformation.BlueMask,
                           info->PixelInformation.ReservedMask);
                       break;
               case PixelBltOnly:
                       printf("(blt only)");
                       break;
               default:
                       printf("(Invalid pixel format)");
                       break;
               }
               printf(" pitch %d", info->PixelsPerScanLine);
               depth = getdepth(info);
               if (depth > 0)
                       printf(" bpp %d", depth);
               printf("\n");
       }

       return 0;
}

static int
efi_find_gop_mode(char *arg)
{
       EFI_STATUS status;
       EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
       UINTN sz;
       UINT32 i;
       char mode[32];
       uint8_t depth;

       for (i = 0; i < efi_gop->Mode->MaxMode; i++) {
               status = uefi_call_wrapper(efi_gop->QueryMode, 4, efi_gop, i,
                   &sz, &info);
               if (EFI_ERROR(status))
                       continue;

               depth = getdepth(info);
               if (depth == 0)
                       continue;

               snprintf(mode, sizeof(mode), "%lux%lux%u",
                   (long)info->HorizontalResolution,
                   (long)info->VerticalResolution,
                   depth);
               if (strcmp(arg, mode) == 0)
                       return i;

               snprintf(mode, sizeof(mode), "%lux%lu",
                   (long)info->HorizontalResolution,
                   (long)info->VerticalResolution);
               if (strcmp(arg, mode) == 0)
                       return i;
       }
       return -1;
}

void
command_gop(char *arg)
{
       EFI_STATUS status;
       INT32 modenum;

       if (efi_gop == NULL) {
               printf("GOP not supported by firmware\n");
               return;
       }

       if (*arg == '\0' || strcmp(arg, "list") == 0) {
               print_gop_modes();
               return;
       }

       if (strchr(arg, 'x') != NULL) {
               modenum = efi_find_gop_mode(arg);
               if (modenum == -1) {
                       printf("mode %s not supported by firmware\n", arg);
                       return;
               }
       } else {
               modenum = strtoul(arg, NULL, 0);
       }

       status = uefi_call_wrapper(efi_gop->SetMode, 2, efi_gop, modenum);
       if (!EFI_ERROR(status) && efi_gop->Mode->Mode == modenum) {
               efi_gop_mode = modenum;
               return;
       }

       printf("invalid flag, must be 'list', a display mode, "
           "or a mode number\n");
}

static void
eficons_init_video(void)
{
       EFI_STATUS status;
       UINTN cols, rows;
       INT32 i, best, mode80x25, mode100x31;

       /*
        * Setup text mode
        */
       uefi_call_wrapper(ST->ConOut->Reset, 2, ST->ConOut, TRUE);

       mode80x25 = mode100x31 = -1;
       for (i = 0; i < ST->ConOut->Mode->MaxMode; i++) {
               status = uefi_call_wrapper(ST->ConOut->QueryMode, 4,
                   ST->ConOut, i, &cols, &rows);
               if (EFI_ERROR(status))
                       continue;

               if (mode80x25 < 0 && cols == 80 && rows == 25)
                       mode80x25 = i;
               else if (mode100x31 < 0 && cols == 100 && rows == 31)
                       mode100x31 = i;
       }
       best = mode100x31 >= 0 ? mode100x31 : mode80x25 >= 0 ? mode80x25 : -1;
       if (best >= 0)
               uefi_call_wrapper(ST->ConOut->SetMode, 2, ST->ConOut, best);
       uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
       uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);

       LibLocateProtocol(&GraphicsOutputProtocol, (void **)&efi_gop);
}

/*
* for Apple EFI
*/
#define CONSOLE_CONTROL_PROTOCOL \
       {0xf42f7782, 0x12e, 0x4c12, {0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21}}
static EFI_GUID ConsoleControlProtocol = CONSOLE_CONTROL_PROTOCOL;

struct _EFI_CONSOLE_CONTROL_INTERFACE;
typedef struct _EFI_CONSOLE_CONTROL_INTERFACE EFI_CONSOLE_CONTROL_INTERFACE;
typedef enum { EfiConsoleControlScreenText } EFI_CONSOLE_CONTROL_SCREEN_MODE;
typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE) (
       IN EFI_CONSOLE_CONTROL_INTERFACE *This,
       IN EFI_CONSOLE_CONTROL_SCREEN_MODE Mode
);
struct _EFI_CONSOLE_CONTROL_INTERFACE {
       VOID *GetMode;
       EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode;
       VOID *LockStdIn;
};

static void
efi_switch_video_to_text_mode(void)
{
       EFI_STATUS status;
       EFI_CONSOLE_CONTROL_INTERFACE *cci;

       /* Set up the console, so printf works. */
       status = LibLocateProtocol(&ConsoleControlProtocol, (void **)&cci);
       if (!EFI_ERROR(status)) {
               uefi_call_wrapper(cci->SetMode, 2, cci,
                   EfiConsoleControlScreenText);
       }
}

/*
* serial port
*/
static void
efi_com_probe(void)
{
       EFI_STATUS status;
       UINTN i, nhandles;
       EFI_HANDLE *handles;
       EFI_DEVICE_PATH *dp, *dp0;
       EFI_DEV_PATH_PTR dpp;
       SERIAL_IO_INTERFACE *serio;
       int uid = -1;

       status = LibLocateHandle(ByProtocol, &SerialIoProtocol, NULL,
           &nhandles, &handles);
       if (EFI_ERROR(status))
               return;

       for (i = 0; i < nhandles; i++) {
               /*
                * Identify port number of the handle.  This assumes ACPI
                * UID 0-3 map to legacy COM[1-4] and they use the legacy
                * port address.
                */
               status = uefi_call_wrapper(BS->HandleProtocol, 3, handles[i],
                   &DevicePathProtocol, (void **)&dp0);
               if (EFI_ERROR(status))
                       continue;

               for (uid = -1, dp = dp0;
                    !IsDevicePathEnd(dp);
                    dp = NextDevicePathNode(dp)) {

                       if (DevicePathType(dp) == ACPI_DEVICE_PATH &&
                           DevicePathSubType(dp) == ACPI_DP) {
                               dpp = (EFI_DEV_PATH_PTR)dp;
                               if (dpp.Acpi->HID == EISA_PNP_ID(0x0501)) {
                                       uid = dpp.Acpi->UID;
                                       break;
                               }
                       }
               }
               if (uid < 0 || __arraycount(serios) <= uid)
                       continue;

               /* Prepare SERIAL_IO_INTERFACE */
               status = uefi_call_wrapper(BS->HandleProtocol, 3, handles[i],
                   &SerialIoProtocol, (void **)&serio);
               if (EFI_ERROR(status))
                       continue;

               serios[uid] = serio;
       }

       FreePool(handles);

}

static bool
efi_valid_com(int dev)
{
       int idx;

       switch (dev) {
       default:
       case CONSDEV_PC:
               return false;

       case CONSDEV_COM0:
       case CONSDEV_COM1:
       case CONSDEV_COM2:
       case CONSDEV_COM3:
               idx = dev - CONSDEV_COM0;
               break;
       }

       return idx < __arraycount(serios) &&
           serios[idx] != NULL &&
           getcomaddr(idx) != 0;
}

static int
efi_com_init(int addr, int speed)
{
       EFI_STATUS status;
       SERIAL_IO_INTERFACE *serio;

       if (speed <= 0)
               return 0;

       if (!efi_valid_com(iodev))
               return raw_com_init(addr, speed);

       serio = serios[iodev - CONSDEV_COM0];

       if (serio->Mode->BaudRate != btinfo_console.speed) {
               status = uefi_call_wrapper(serio->SetAttributes, 7, serio,
                   speed, serio->Mode->ReceiveFifoDepth,
                   serio->Mode->Timeout, serio->Mode->Parity,
                   serio->Mode->DataBits, serio->Mode->StopBits);
               if (EFI_ERROR(status)) {
                       printf("com%d: SetAttribute() failed with status=%" PRIxMAX
                           "\n", iodev - CONSDEV_COM0, (uintmax_t)status);
                       return 0;
               }
       }

       raw_com_addr = 0;
       default_comspeed = speed;
       internal_getchar = efi_com_getc;
       internal_putchar = efi_com_putc;
       internal_iskey = efi_com_status;
       internal_waitforinputevent = efi_com_waitforinputevent;
       memset(serbuf, 0, sizeof(serbuf));
       serbuf_read = serbuf_write = 0;

       return speed;
}

static int
efi_com_getc(void)
{
       EFI_STATUS status;
       SERIAL_IO_INTERFACE *serio;
       UINTN sz;
       u_char c;

       if (!efi_valid_com(iodev))
               panic("Invalid serial port: iodev=%d", iodev);

       if (serbuf_read != serbuf_write) {
               c = serbuf[serbuf_read];
               serbuf_read = (serbuf_read + 1) % __arraycount(serbuf);
               return c;
       }

       serio = serios[iodev - CONSDEV_COM0];

       for (;;) {
               sz = 1;
               status = uefi_call_wrapper(serio->Read, 3, serio, &sz, &c);
               if (!EFI_ERROR(status) && sz > 0)
                       break;
               if (status != EFI_TIMEOUT && EFI_ERROR(status))
                       panic("Error reading from serial status=%"PRIxMAX,
                           (uintmax_t)status);
       }
       return c;
}

static int
efi_com_putc(int c)
{
       EFI_STATUS status;
       SERIAL_IO_INTERFACE *serio;
       UINTN sz = 1;
       u_char buf;

       if (!efi_valid_com(iodev))
               return 0;

       serio = serios[iodev - CONSDEV_COM0];
       buf = c;
       status = uefi_call_wrapper(serio->Write, 3, serio, &sz, &buf);
       if (EFI_ERROR(status) || sz < 1)
               return 0;
       return 1;
}

/*ARGSUSED*/
static int
efi_com_status(int intr)
{
       EFI_STATUS status;
       SERIAL_IO_INTERFACE *serio;
       UINTN sz;
       u_char c;

       if (!efi_valid_com(iodev))
               panic("Invalid serial port: iodev=%d", iodev);

       if (serbuf_read != serbuf_write)
               return 1;

       serio = serios[iodev - CONSDEV_COM0];
       sz = 1;
       status = uefi_call_wrapper(serio->Read, 3, serio, &sz, &c);
       if (EFI_ERROR(status) || sz < 1)
               return 0;

       serbuf[serbuf_write] = c;
       serbuf_write = (serbuf_write + 1) % __arraycount(serbuf);
       return 1;
}

static void
efi_com_periodic_event(EFI_EVENT event, void *ctx)
{
       EFI_EVENT timer = ctx;

       if (efi_com_status(0)) {
               uefi_call_wrapper(BS->SetTimer, 3, event, TimerCancel, 0);
               uefi_call_wrapper(BS->SignalEvent, 1, timer);
       }
}

static int
efi_com_waitforinputevent(uint64_t timeout)
{
       EFI_STATUS status;
       EFI_EVENT timer, periodic;

       status = uefi_call_wrapper(BS->CreateEvent, 5, EVT_TIMER, 0, NULL, NULL,
           &timer);
       if (EFI_ERROR(status))
               return EINVAL;

       status = uefi_call_wrapper(BS->CreateEvent, 5,
           EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, efi_com_periodic_event,
           timer, &periodic);
       if (EFI_ERROR(status)) {
               uefi_call_wrapper(BS->CloseEvent, 1, timer);
               return EINVAL;
       }

       status = uefi_call_wrapper(BS->SetTimer, 3, periodic, TimerPeriodic,
           1000000);   /* 100ms */
       if (EFI_ERROR(status)) {
               uefi_call_wrapper(BS->CloseEvent, 1, periodic);
               uefi_call_wrapper(BS->CloseEvent, 1, timer);
               return EINVAL;
       }
       status = WaitForSingleEvent(&timer, timeout);
       uefi_call_wrapper(BS->SetTimer, 3, periodic, TimerCancel, 0);
       uefi_call_wrapper(BS->CloseEvent, 1, periodic);
       uefi_call_wrapper(BS->CloseEvent, 1, timer);
       if (!EFI_ERROR(status))
               return 0;
       if (status == EFI_TIMEOUT)
               return ETIMEDOUT;
       return EINVAL;
}

static int
raw_com_init(int addr, int speed)
{

       if (addr == 0 || speed <= 0)
               return 0;

       speed = cominit_d(addr, speed);

       raw_com_addr = addr;
       default_comspeed = speed;
       internal_getchar = raw_com_getc;
       internal_putchar = raw_com_putc;
       internal_iskey = raw_com_status;
       internal_waitforinputevent = raw_com_waitforinputevent;

       return speed;
}

static int
raw_com_getc(void)
{

       if (raw_com_addr == 0)
               panic("%s: Invalid serial port", __func__);
       return comgetc_d(raw_com_addr);
}

static int
raw_com_putc(int c)
{

       if (raw_com_addr == 0)
               panic("%s: Invalid serial port", __func__);
       return computc_d(c, raw_com_addr);
}

static int
raw_com_status(int intr)
{

       if (raw_com_addr == 0)
               panic("%s: Invalid serial port", __func__);
       return comstatus_d(raw_com_addr);
}

static int
raw_com_waitforinputevent(uint64_t timeout /* in 0.1 usec */)
{
       uint64_t ms;

       if (raw_com_addr == 0)
               panic("%s: Invalid serial port", __func__);

       for (ms = howmany(timeout, 10 * 1000); ms != 0; ms--) {
               if (raw_com_status(0))
                       return 0;
               uefi_call_wrapper(BS->Stall, 1, 1000);
       }
       return ETIMEDOUT;
}