root/sys/arch/i386/stand/efiboot/eficons.c
/*      $NetBSD: eficons.c,v 1.18 2025/10/15 13:16:58 manu Exp $        */

/*-
 * Copyright (c) 2016 Kimihiro Nonaka <nonaka@netbsd.org>
 * 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;

#define EISA_COM_COUNT 4
static SERIAL_IO_INTERFACE *serios[12]; /* 4 EISA com slots, 8 others */
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 int efi_com_consdev2unit(int);
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 com_unit = -1;
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 unit, int speed, bool switchcons)
{
        int i;
        char *consname = NULL;

        btinfo_console.speed = default_comspeed;

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

                        /* switch to com for the test, regardless switchcons */
                        com_unit = 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:
        case CONSDEV_COM:
                consname = "com";
                btinfo_console.addr = ioport;
                if (btinfo_console.addr == 0)
                        btinfo_console.addr = getcomaddr(unit);
                if (speed != 0)
                        btinfo_console.speed = speed;

                if (switchcons) {
                        com_unit = unit;
                        efi_com_init(btinfo_console.addr, btinfo_console.speed);
                }
                break;

        case CONSDEV_COM0KBD:
        case CONSDEV_COM1KBD:
        case CONSDEV_COM2KBD:
        case CONSDEV_COM3KBD:
                consname = "com";
                btinfo_console.addr = getcomaddr(unit);

                if (!switchcons)
                        goto kbd;

                com_unit = unit;

                efi_cons_putc('0' + unit); /* XXX if unit>=10 ?  */
                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:
                goto nocom;
        case CONSDEV_UCOM:
                consname = "ucom";
                btinfo_console.addr = unit; /* Used for unit number */
                if (speed != 0)
                        btinfo_console.speed = speed;
                break;
        default:
nocom:
                consname = "pc";
                if (!switchcons)
                        break;
                internal_putchar = efi_cons_putc;
kbd:
                if (!switchcons)
                        break;
                if (unit != -1)
                        com_unit = unit;
                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;
        }

        if (consname != NULL)
                strlcpy(btinfo_console.devname, consname, 16);
}

int
cninit(void)
{
        int unit;
        efi_switch_video_to_text_mode();
        eficons_init_video();

        /* console output is now possible */

        efi_com_probe();

        unit = efi_com_consdev2unit(boot_params.bp_consdev);
        efi_consinit(boot_params.bp_consdev, boot_params.bp_consaddr, unit,
            boot_params.bp_conspeed, true);

        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);
        }
}

static int
efi_com_consdev2unit(int dev)
{
        int unit = -1;

        if (dev >= CONSDEV_COM0 && unit <= CONSDEV_COM3)
                unit = dev - CONSDEV_COM0;

        if (dev >= CONSDEV_COM0KBD && unit <= CONSDEV_COM3KBD)
                unit = dev - CONSDEV_COM0KBD;

        if (dev == CONSDEV_COM)
                unit = 0;

        if (dev == CONSDEV_UCOM)
                unit = 0;

        return unit;
}


/*
 * 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 unit = -1;
        int high_unit = EISA_COM_COUNT;

        /*
         * We could want to dynamically allocate serios[] to cope
         * with an aritrary count of serial ports, but we cannot yet
         * call alloc(), because efi_heap_init() was not yet called.
         * We could swap cninit() and efi_heap_init() order in
         * efi_main(), but then efi_heap_init() could not display
         * a failure message. Let us keep static it for now for the
         * sake of reliability.
         */
        for (i = 0; i < __arraycount(serios); i++)
                serios[i] = NULL;

        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;

                unit = -1;
                for (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)) {
                                        unit = dpp.Acpi->UID;
                                        break;
                                }
                        }
                }

                if (unit == -1)
                        unit = high_unit++;

                if (unit < 0 || __arraycount(serios) <= unit)
                        continue;

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

                serios[unit] = serio;
        }

        FreePool(handles);

}

static bool
efi_valid_com(int unit)
{
        return (unit >= 0 &&unit < __arraycount(serios) &&
            serios[unit] != NULL);
}

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(com_unit))
                return raw_com_init(addr, speed);

        serio = serios[com_unit];

        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", com_unit, (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(com_unit))
                panic("Invalid serial port: unit=%d", com_unit);

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

        serio = serios[com_unit];

        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(com_unit))
                return 0;

        serio = serios[com_unit];
        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(com_unit))
                panic("Invalid serial port: unit = %d", com_unit);

        if (serbuf_read != serbuf_write)
                return 1;

        serio = serios[com_unit];
        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;
}