root/usr/src/uts/i86pc/boot/boot_console.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2012 Gary Mills
 * Copyright 2020 Joyent, Inc.
 * Copyright 2025 Oxide Computer Company
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Boot console support.  Most of the file is shared between dboot, and the
 * early kernel / fakebop.
 */

#include <sys/types.h>
#include <sys/systm.h>
#include <sys/archsystm.h>
#include <sys/framebuffer.h>
#include <sys/boot_console.h>
#include <sys/panic.h>
#include <sys/ctype.h>
#include <sys/ascii.h>
#include <sys/vgareg.h>
#if defined(__xpv)
#include <sys/hypervisor.h>
#endif /* __xpv */

#include "boot_console_impl.h"
#include "boot_serial.h"

#if defined(_BOOT)
#include <dboot/dboot_asm.h>
#include <dboot/dboot_xboot.h>
#else /* _BOOT */
#include <sys/bootconf.h>
#if defined(__xpv)
#include <sys/evtchn_impl.h>
#endif /* __xpv */
static char *defcons_buf;
static char *defcons_cur;
#endif /* _BOOT */

#if defined(__xpv)
extern void bcons_init_xen(char *);
extern void bcons_putchar_xen(int);
extern int bcons_getchar_xen(void);
extern int bcons_ischar_xen(void);
#endif /* __xpv */

fb_info_t fb_info;
static bcons_dev_t bcons_dev;                           /* Device callbacks */
static int console = CONS_SCREEN_TEXT;
static int diag = CONS_INVALID;
static int tty_num = 0;
static int tty_addr[] = {0x3f8, 0x2f8, 0x3e8, 0x2e8};
static char *boot_line;
static struct boot_env {
        char    *be_env;        /* ends with double ascii nul */
        size_t  be_size;        /* size of the environment, including nul */
} boot_env;

/*
 * Simple console terminal emulator for early boot.
 * We need this to support kmdb, all other console output is supposed
 * to be simple text output.
 */
typedef enum btem_state_type {
        A_STATE_START,
        A_STATE_ESC,
        A_STATE_CSI,
        A_STATE_CSI_QMARK,
        A_STATE_CSI_EQUAL
} btem_state_type_t;

#define BTEM_MAXPARAMS  5
typedef struct btem_state {
        btem_state_type_t btem_state;
        boolean_t btem_gotparam;
        int btem_curparam;
        int btem_paramval;
        int btem_params[BTEM_MAXPARAMS];
} btem_state_t;

static btem_state_t boot_tem;

static int serial_ischar(void);
static int serial_getchar(void);
static void serial_putchar(int);
static void serial_adjust_prop(void);

static void defcons_putchar(int);

#if !defined(_BOOT)
static boolean_t bootprop_set_tty_mode;
#endif

#if defined(__xpv)
static int console_hypervisor_redirect = B_FALSE;
static int console_hypervisor_device = CONS_INVALID;
static int console_hypervisor_tty_num = 0;

/* Obtain the hypervisor console type */
int
console_hypervisor_dev_type(int *tnum)
{
        if (tnum != NULL)
                *tnum = console_hypervisor_tty_num;
        return (console_hypervisor_device);
}
#endif /* __xpv */

static int port;

static void
serial_init(void)
{
        port = tty_addr[tty_num];

        outb(port + ISR, 0x20);
        if (inb(port + ISR) & 0x20) {
                /*
                 * 82510 chip is present
                 */
                outb(port + DAT+7, 0x04);       /* clear status */
                outb(port + ISR, 0x40);  /* set to bank 2 */
                outb(port + MCR, 0x08);  /* IMD */
                outb(port + DAT, 0x21);  /* FMD */
                outb(port + ISR, 0x00);  /* set to bank 0 */
        } else {
                /*
                 * set the UART in FIFO mode if it has FIFO buffers.
                 * use 16550 fifo reset sequence specified in NS
                 * application note. disable fifos until chip is
                 * initialized.
                 */
                outb(port + FIFOR, 0x00);               /* clear */
                outb(port + FIFOR, FIFO_ON);            /* enable */
                outb(port + FIFOR, FIFO_ON|FIFORXFLSH);  /* reset */
                outb(port + FIFOR,
                    FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80);
                if ((inb(port + ISR) & 0xc0) != 0xc0) {
                        /*
                         * no fifo buffers so disable fifos.
                         * this is true for 8250's
                         */
                        outb(port + FIFOR, 0x00);
                }
        }

        /* disable interrupts */
        outb(port + ICR, 0);

#if !defined(_BOOT)
        if (IN_XPV_PANIC())
                return;
#endif

        /* adjust setting based on tty properties */
        serial_adjust_prop();
}

/* Advance str pointer past white space */
#define EAT_WHITE_SPACE(str)    {                       \
        while ((*str != '\0') && ISSPACE(*str))         \
                str++;                                  \
}

/*
 * boot_line is set when we call here.  Search it for the argument name,
 * and if found, return a pointer to it.
 */
static char *
find_boot_line_prop(const char *name)
{
        char *ptr;
        char *ret = NULL;
        char end_char;
        size_t len;

        if (boot_line == NULL)
                return (NULL);

        len = strlen(name);

        /*
         * We have two nested loops here: the outer loop discards all options
         * except -B, and the inner loop parses the -B options looking for
         * the one we're interested in.
         */
        for (ptr = boot_line; *ptr != '\0'; ptr++) {
                EAT_WHITE_SPACE(ptr);

                if (*ptr == '-') {
                        ptr++;
                        while ((*ptr != '\0') && (*ptr != 'B') &&
                            !ISSPACE(*ptr))
                                ptr++;
                        if (*ptr == '\0')
                                goto out;
                        else if (*ptr != 'B')
                                continue;
                } else {
                        while ((*ptr != '\0') && !ISSPACE(*ptr))
                                ptr++;
                        if (*ptr == '\0')
                                goto out;
                        continue;
                }

                do {
                        ptr++;
                        EAT_WHITE_SPACE(ptr);

                        if ((strncmp(ptr, name, len) == 0) &&
                            (ptr[len] == '=')) {
                                ptr += len + 1;
                                if ((*ptr == '\'') || (*ptr == '"')) {
                                        ret = ptr + 1;
                                        end_char = *ptr;
                                        ptr++;
                                } else {
                                        ret = ptr;
                                        end_char = ',';
                                }
                                goto consume_property;
                        }

                        /*
                         * We have a property, and it's not the one we're
                         * interested in.  Skip the property name.  A name
                         * can end with '=', a comma, or white space.
                         */
                        while ((*ptr != '\0') && (*ptr != '=') &&
                            (*ptr != ',') && (!ISSPACE(*ptr)))
                                ptr++;

                        /*
                         * We only want to go through the rest of the inner
                         * loop if we have a comma.  If we have a property
                         * name without a value, either continue or break.
                         */
                        if (*ptr == '\0')
                                goto out;
                        else if (*ptr == ',')
                                continue;
                        else if (ISSPACE(*ptr))
                                break;
                        ptr++;

                        /*
                         * Is the property quoted?
                         */
                        if ((*ptr == '\'') || (*ptr == '"')) {
                                end_char = *ptr;
                                ptr++;
                        } else {
                                /*
                                 * Not quoted, so the string ends at a comma
                                 * or at white space.  Deal with white space
                                 * later.
                                 */
                                end_char = ',';
                        }

                        /*
                         * Now, we can ignore any characters until we find
                         * end_char.
                         */
consume_property:
                        for (; (*ptr != '\0') && (*ptr != end_char); ptr++) {
                                if ((end_char == ',') && ISSPACE(*ptr))
                                        break;
                        }
                        if (*ptr && (*ptr != ',') && !ISSPACE(*ptr))
                                ptr++;
                } while (*ptr == ',');
        }
out:
        return (ret);
}

/*
 * Find prop from boot env module. The data in module is list of C strings
 * name=value, the list is terminated by double nul.
 */
static const char *
find_boot_env_prop(const char *name)
{
        char *ptr;
        size_t len;
        uintptr_t size;

        if (boot_env.be_env == NULL)
                return (NULL);

        ptr = boot_env.be_env;
        len = strlen(name);

        /*
         * Make sure we have at least len + 2 bytes in the environment.
         * We are looking for name=value\0 constructs, and the environment
         * itself is terminated by '\0'.
         */
        if (boot_env.be_size < len + 2)
                return (NULL);

        do {
                if ((strncmp(ptr, name, len) == 0) && (ptr[len] == '=')) {
                        ptr += len + 1;
                        return (ptr);
                }
                /* find the first '\0' */
                while (*ptr != '\0') {
                        ptr++;
                        size = (uintptr_t)ptr - (uintptr_t)boot_env.be_env;
                        if (size > boot_env.be_size)
                                return (NULL);
                }
                ptr++;

                /* If the remainder is shorter than name + 2, get out. */
                size = (uintptr_t)ptr - (uintptr_t)boot_env.be_env;
                if (boot_env.be_size - size < len + 2)
                        return (NULL);
        } while (*ptr != '\0');
        return (NULL);
}

/*
 * Get prop value from either command line or boot environment.
 * We always check kernel command line first, as this will keep the
 * functionality and will allow user to override the values in environment.
 */
const char *
find_boot_prop(const char *name)
{
        const char *value = find_boot_line_prop(name);

        if (value == NULL)
                value = find_boot_env_prop(name);
        return (value);
}

#define MATCHES(p, pat) \
        (strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0)

#define SKIP(p, c)                              \
        while (*(p) != 0 && *p != (c))          \
                ++(p);                          \
        if (*(p) == (c))                        \
                ++(p);

/*
 * find a tty mode property either from cmdline or from boot properties
 */
static const char *
get_mode_value(char *name)
{
        /*
         * when specified on boot line it looks like "name" "="....
         */
        if (boot_line != NULL) {
                return (find_boot_prop(name));
        }

#if defined(_BOOT)
        return (NULL);
#else
        /*
         * if we're running in the full kernel we check the bootenv.rc settings
         */
        {
                static char propval[20];

                propval[0] = 0;
                if (do_bsys_getproplen(NULL, name) <= 0)
                        return (NULL);
                (void) do_bsys_getprop(NULL, name, propval);
                return (propval);
        }
#endif
}

/*
 * adjust serial port based on properties
 * These come either from the cmdline or from boot properties.
 */
static void
serial_adjust_prop(void)
{
        char propname[20];
        const char *propval;
        const char *p;
        ulong_t baud;
        uchar_t lcr = 0;
        uchar_t mcr = DTR | RTS;

        (void) strcpy(propname, "ttyX-mode");
        propname[3] = 'a' + tty_num;
        propval = get_mode_value(propname);
#if !defined(_BOOT)
        if (propval != NULL)
                bootprop_set_tty_mode = B_TRUE;
#endif
        if (propval == NULL)
                propval = "9600,8,n,1,-";

        /* property is of the form: "9600,8,n,1,-" */
        p = propval;
        if (MATCHES(p, "110,"))
                baud = ASY110;
        else if (MATCHES(p, "150,"))
                baud = ASY150;
        else if (MATCHES(p, "300,"))
                baud = ASY300;
        else if (MATCHES(p, "600,"))
                baud = ASY600;
        else if (MATCHES(p, "1200,"))
                baud = ASY1200;
        else if (MATCHES(p, "2400,"))
                baud = ASY2400;
        else if (MATCHES(p, "4800,"))
                baud = ASY4800;
        else if (MATCHES(p, "19200,"))
                baud = ASY19200;
        else if (MATCHES(p, "38400,"))
                baud = ASY38400;
        else if (MATCHES(p, "57600,"))
                baud = ASY57600;
        else if (MATCHES(p, "115200,"))
                baud = ASY115200;
        else {
                baud = ASY9600;
                SKIP(p, ',');
        }
        outb(port + LCR, DLAB);
        outb(port + DAT + DLL, baud & 0xff);
        outb(port + DAT + DLH, (baud >> 8) & 0xff);

        switch (*p) {
        case '5':
                lcr |= BITS5;
                ++p;
                break;
        case '6':
                lcr |= BITS6;
                ++p;
                break;
        case '7':
                lcr |= BITS7;
                ++p;
                break;
        case '8':
                ++p;
                /* FALLTHROUGH */
        default:
                lcr |= BITS8;
                break;
        }

        SKIP(p, ',');

        switch (*p) {
        case 'n':
                lcr |= PARITY_NONE;
                ++p;
                break;
        case 'o':
                lcr |= PARITY_ODD;
                ++p;
                break;
        case 'e':
                ++p;
                /* FALLTHROUGH */
        default:
                lcr |= PARITY_EVEN;
                break;
        }


        SKIP(p, ',');

        switch (*p) {
        case '1':
                /* STOP1 is 0 */
                ++p;
                break;
        default:
                lcr |= STOP2;
                break;
        }
        /* set parity bits */
        outb(port + LCR, lcr);

        (void) strcpy(propname, "ttyX-rts-dtr-off");
        propname[3] = 'a' + tty_num;
        propval = get_mode_value(propname);
        if (propval == NULL)
                propval = "false";
        if (propval[0] != 'f' && propval[0] != 'F')
                mcr = 0;
        /* set modem control bits */
        outb(port + MCR, mcr | OUT2);
}

/* Obtain the console type */
int
boot_console_type(int *tnum)
{
        if (tnum != NULL)
                *tnum = tty_num;
        return (console);
}

/*
 * A structure to map console names to values.
 */
typedef struct {
        char *name;
        int value;
} console_value_t;

console_value_t console_devices[] = {
        { "ttya", CONS_TTY },   /* 0 */
        { "ttyb", CONS_TTY },   /* 1 */
        { "ttyc", CONS_TTY },   /* 2 */
        { "ttyd", CONS_TTY },   /* 3 */
        { "text", CONS_SCREEN_TEXT },
        { "graphics", CONS_SCREEN_GRAPHICS },
#if defined(__xpv)
        { "hypervisor", CONS_HYPERVISOR },
#endif
#if !defined(_BOOT)
        { "usb-serial", CONS_USBSER },
#endif
        { NULL, CONS_INVALID }
};

static void
bcons_init_env(struct xboot_info *xbi)
{
        uint32_t i;
        struct boot_modules *modules;

        modules = (struct boot_modules *)(uintptr_t)xbi->bi_modules;
        for (i = 0; i < xbi->bi_module_cnt; i++) {
                if (modules[i].bm_type == BMT_ENV)
                        break;
        }
        if (i == xbi->bi_module_cnt)
                return;

        boot_env.be_env = (char *)(uintptr_t)modules[i].bm_addr;
        boot_env.be_size = modules[i].bm_size;
}

int
boot_fb(struct xboot_info *xbi, int console)
{
        if (xbi_fb_init(xbi, &bcons_dev) == B_FALSE)
                return (console);

        /*
         * The framebuffer address is not set; fall back to the serial console.
         */
        if (fb_info.paddr == 0)
                return (CONS_TTY);

#if defined(_BOOT)
        /*
         * If the firmware placed the framebuffer mapping above the 32-bit
         * boundary, we cannot use it from dboot.
         */
        if (fb_info.paddr >= UINTPTR_MAX)
                return (CONS_TTY);
#endif

        fb_info.terminal.x = VGA_TEXT_COLS;
        fb_info.terminal.y = VGA_TEXT_ROWS;
        boot_fb_init(CONS_FRAMEBUFFER);

        if (console == CONS_SCREEN_TEXT)
                return (CONS_FRAMEBUFFER);
        return (console);
}

/*
 * TODO.
 * quick and dirty local atoi. Perhaps should build with strtol, but
 * dboot & early boot mix does overcomplicate things much.
 * Stolen from libc anyhow.
 */
static int
atoi(const char *p)
{
        int n, c, neg = 0;
        unsigned char *up = (unsigned char *)p;

        if (!isdigit(c = *up)) {
                while (isspace(c))
                        c = *++up;
                switch (c) {
                case '-':
                        neg++;
                        /* FALLTHROUGH */
                case '+':
                        c = *++up;
                }
                if (!isdigit(c))
                        return (0);
        }
        for (n = '0' - c; isdigit(c = *++up);) {
                n *= 10; /* two steps to avoid unnecessary overflow */
                n += '0' - c; /* accum neg to avoid surprises at MAX */
        }
        return (neg ? n : -n);
}

static void
bcons_init_fb(void)
{
        const char *propval;
        int intval;

        /* initialize with explicit default values */
        fb_info.fg_color = CONS_COLOR;
        fb_info.bg_color = 0;
        fb_info.inverse = B_FALSE;
        fb_info.inverse_screen = B_FALSE;

        /* color values are 0 - 255 */
        propval = find_boot_prop("tem.fg_color");
        if (propval != NULL) {
                intval = atoi(propval);
                if (intval >= 0 && intval <= 255)
                        fb_info.fg_color = intval;
        }

        /* color values are 0 - 255 */
        propval = find_boot_prop("tem.bg_color");
        if (propval != NULL && ISDIGIT(*propval)) {
                intval = atoi(propval);
                if (intval >= 0 && intval <= 255)
                        fb_info.bg_color = intval;
        }

        /* get inverses. allow 0, 1, true, false */
        propval = find_boot_prop("tem.inverse");
        if (propval != NULL) {
                if (*propval == '1' || MATCHES(propval, "true"))
                        fb_info.inverse = B_TRUE;
        }

        propval = find_boot_prop("tem.inverse-screen");
        if (propval != NULL) {
                if (*propval == '1' || MATCHES(propval, "true"))
                        fb_info.inverse_screen = B_TRUE;
        }

#if defined(_BOOT)
        /*
         * Load cursor position from bootloader only in dboot,
         * dboot will pass cursor position to kernel via xboot info.
         */
        propval = find_boot_prop("tem.cursor.row");
        if (propval != NULL) {
                intval = atoi(propval);
                if (intval >= 0 && intval <= 0xFFFF)
                        fb_info.cursor.pos.y = intval;
        }

        propval = find_boot_prop("tem.cursor.col");
        if (propval != NULL) {
                intval = atoi(propval);
                if (intval >= 0 && intval <= 0xFFFF)
                        fb_info.cursor.pos.x = intval;
        }
#endif
}

/*
 * Go through the known console device names trying to match the string we were
 * given.  The string on the command line must end with a comma or white space.
 *
 * For convenience, we provide the caller with an integer index for the CONS_TTY
 * case.
 */
static int
lookup_console_device(const char *cons_str, int *indexp)
{
        int n, cons;
        size_t len, cons_len;
        console_value_t *consolep;

        cons = CONS_INVALID;
        if (cons_str != NULL) {

                cons_len = strlen(cons_str);
                for (n = 0; console_devices[n].name != NULL; n++) {
                        consolep = &console_devices[n];
                        len = strlen(consolep->name);
                        if ((len <= cons_len) && ((cons_str[len] == '\0') ||
                            (cons_str[len] == ',') || (cons_str[len] == '\'') ||
                            (cons_str[len] == '"') || ISSPACE(cons_str[len])) &&
                            (strncmp(cons_str, consolep->name, len) == 0)) {
                                cons = consolep->value;
                                if (cons == CONS_TTY)
                                        *indexp = n;
                                break;
                        }
                }
        }
        return (cons);
}

void
bcons_init(struct xboot_info *xbi)
{
        const char *cons_str;
#if !defined(_BOOT)
        static char console_text[] = "text";
        extern int post_fastreboot;
#endif

        if (xbi == NULL) {
                /* This is very early dboot console, set up ttya. */
                console = CONS_TTY;
                serial_init();
                return;
        }

        /* Set up data to fetch properties from commad line and boot env. */
        boot_line = (char *)(uintptr_t)xbi->bi_cmdline;
        bcons_init_env(xbi);
        console = CONS_INVALID;

        /* set up initial fb_info */
        bcons_init_fb();

#if defined(__xpv)
        bcons_init_xen(boot_line);
#endif /* __xpv */

        /*
         * First check for diag-device.
         */
        cons_str = find_boot_prop("diag-device");
        if (cons_str != NULL)
                diag = lookup_console_device(cons_str, &tty_num);

        cons_str = find_boot_prop("console");
        if (cons_str == NULL)
                cons_str = find_boot_prop("output-device");

#if !defined(_BOOT)
        if (post_fastreboot && strcmp(cons_str, "graphics") == 0)
                cons_str = console_text;
#endif

        if (cons_str != NULL)
                console = lookup_console_device(cons_str, &tty_num);

#if defined(__xpv)
        /*
         * domU's always use the hypervisor regardless of what
         * the console variable may be set to.
         */
        if (!DOMAIN_IS_INITDOMAIN(xen_info)) {
                console = CONS_HYPERVISOR;
                console_hypervisor_redirect = B_TRUE;
        }
#endif /* __xpv */

        if (console == CONS_INVALID)
                console = CONS_SCREEN_TEXT;

#if defined(__xpv)
        if (DOMAIN_IS_INITDOMAIN(xen_info)) {
                switch (HYPERVISOR_console_io(CONSOLEIO_get_device, 0, NULL)) {
                        case XEN_CONSOLE_COM1:
                        case XEN_CONSOLE_COM2:
                                console_hypervisor_device = CONS_TTY;
                                console_hypervisor_tty_num = tty_num;
                                break;
                        case XEN_CONSOLE_VGA:
                                /*
                                 * Currently xen doesn't really support
                                 * keyboard/display console devices.
                                 * What this setting means is that
                                 * "vga=keep" has been enabled, which is
                                 * more of a xen debugging tool that a
                                 * true console mode.  Hence, we're going
                                 * to ignore this xen "console" setting.
                                 */
                                /*FALLTHROUGH*/
                        default:
                                console_hypervisor_device = CONS_INVALID;
                }
        }

        /*
         * if the hypervisor is using the currently selected serial
         * port then default to using the hypervisor as the console
         * device.
         */
        if (console == console_hypervisor_device) {
                console = CONS_HYPERVISOR;
                console_hypervisor_redirect = B_TRUE;
        }
#endif /* __xpv */

        /* make sure the FB is set up if present */
        console = boot_fb(xbi, console);
        switch (console) {
        case CONS_TTY:
                serial_init();
                break;

        case CONS_HYPERVISOR:
                break;

#if !defined(_BOOT)
        case CONS_USBSER:
                /*
                 * We can't do anything with the usb serial
                 * until we have memory management.
                 */
                break;
#endif
        case CONS_SCREEN_GRAPHICS:
                kb_init();
                break;
        case CONS_SCREEN_TEXT:
                boot_vga_init(&bcons_dev);
                /* Fall through */
        default:
                kb_init();
                break;
        }

        /*
         * Initialize diag device unless already done.
         */
        switch (diag) {
        case CONS_TTY:
                if (console != CONS_TTY)
                        serial_init();
                break;
        case CONS_SCREEN_GRAPHICS:
        case CONS_SCREEN_TEXT:
                if (console != CONS_SCREEN_GRAPHICS &&
                    console != CONS_SCREEN_TEXT)
                        kb_init();
                break;
        default:
                break;
        }
}

static void
serial_putchar(int c)
{
        int checks = 10000;

        while (((inb(port + LSR) & XHRE) == 0) && checks--)
                ;
        outb(port + DAT, (char)c);
}

static int
serial_getchar(void)
{
        uchar_t lsr;

        while (serial_ischar() == 0)
                ;

        lsr = inb(port + LSR);
        if (lsr & (SERIAL_BREAK | SERIAL_FRAME |
            SERIAL_PARITY | SERIAL_OVERRUN)) {
                if (lsr & SERIAL_OVERRUN) {
                        return (inb(port + DAT));
                } else {
                        /* Toss the garbage */
                        (void) inb(port + DAT);
                        return (0);
                }
        }
        return (inb(port + DAT));
}

static int
serial_ischar(void)
{
        return (inb(port + LSR) & RCA);
}

static void
btem_control(btem_state_t *btem, int c)
{
        int y, rows, cols;

        rows = fb_info.cursor.pos.y;
        cols = fb_info.cursor.pos.x;

        btem->btem_state = A_STATE_START;
        switch (c) {
        case A_BS:
                bcons_dev.bd_setpos(rows, cols - 1);
                break;

        case A_HT:
                cols += 8 - (cols % 8);
                if (cols >= fb_info.terminal.x)
                        cols = fb_info.terminal.x - 1;
                bcons_dev.bd_setpos(rows, cols);
                break;

        case A_CR:
                bcons_dev.bd_setpos(rows, 0);
                break;

        case A_FF:
                for (y = 0; y < fb_info.terminal.y; y++) {
                        bcons_dev.bd_setpos(y, 0);
                        bcons_dev.bd_eraseline();
                }
                bcons_dev.bd_setpos(0, 0);
                break;

        case A_ESC:
                btem->btem_state = A_STATE_ESC;
                break;

        default:
                bcons_dev.bd_putchar(c);
                break;
        }
}

/*
 * if parameters [0..count - 1] are not set, set them to the value
 * of newparam.
 */
static void
btem_setparam(btem_state_t *btem, int count, int newparam)
{
        int i;

        for (i = 0; i < count; i++) {
                if (btem->btem_params[i] == -1)
                        btem->btem_params[i] = newparam;
        }
}

static void
btem_chkparam(btem_state_t *btem, int c)
{
        int rows, cols;

        rows = fb_info.cursor.pos.y;
        cols = fb_info.cursor.pos.x;
        switch (c) {
        case '@':                       /* insert char */
                btem_setparam(btem, 1, 1);
                bcons_dev.bd_shift(btem->btem_params[0]);
                break;

        case 'A':                       /* cursor up */
                btem_setparam(btem, 1, 1);
                bcons_dev.bd_setpos(rows - btem->btem_params[0], cols);
                break;

        case 'B':                       /* cursor down */
                btem_setparam(btem, 1, 1);
                bcons_dev.bd_setpos(rows + btem->btem_params[0], cols);
                break;

        case 'C':                       /* cursor right */
                btem_setparam(btem, 1, 1);
                bcons_dev.bd_setpos(rows, cols + btem->btem_params[0]);
                break;

        case 'D':                       /* cursor left */
                btem_setparam(btem, 1, 1);
                bcons_dev.bd_setpos(rows, cols - btem->btem_params[0]);
                break;

        case 'K':
                bcons_dev.bd_eraseline();
                break;
        default:
                /* bcons_dev.bd_putchar(c); */
                break;
        }
        btem->btem_state = A_STATE_START;
}

static void
btem_chkparam_qmark(btem_state_t *btem, int c)
{
        /*
         * This code is intentionally NOP, we do process
         * \E[?25h and \E[?25l, but our cursor is always shown.
         */
        switch (c) {
        case 'h': /* DEC private mode set */
                btem_setparam(btem, 1, 1);
                switch (btem->btem_params[0]) {
                case 25: /* show cursor */
                        break;
                }
                break;
        case 'l':
                /* DEC private mode reset */
                btem_setparam(btem, 1, 1);
                switch (btem->btem_params[0]) {
                case 25: /* hide cursor */
                        break;
                }
                break;
        }
        btem->btem_state = A_STATE_START;
}

static void
btem_getparams(btem_state_t *btem, int c)
{
        if (isdigit(c)) {
                btem->btem_paramval = btem->btem_paramval * 10 + c - '0';
                btem->btem_gotparam = B_TRUE;
                return;
        }

        if (btem->btem_curparam < BTEM_MAXPARAMS) {
                if (btem->btem_gotparam == B_TRUE) {
                        btem->btem_params[btem->btem_curparam] =
                            btem->btem_paramval;
                }
                btem->btem_curparam++;
        }

        if (c == ';') {
                /* Restart parameter search */
                btem->btem_gotparam = B_FALSE;
                btem->btem_paramval = 0;
        } else {
                if (btem->btem_state == A_STATE_CSI_QMARK)
                        btem_chkparam_qmark(btem, c);
                else
                        btem_chkparam(btem, c);
        }
}

/* Simple boot terminal parser. */
static void
btem_parse(btem_state_t *btem, int c)
{
        int i;

        /* Normal state? */
        if (btem->btem_state == A_STATE_START) {
                if (c == A_CSI || c < ' ')
                        btem_control(btem, c);
                else
                        bcons_dev.bd_putchar(c);
                return;
        }

        /* In <ESC> sequence */
        if (btem->btem_state != A_STATE_ESC) {
                if (btem->btem_state != A_STATE_CSI) {
                        btem_getparams(btem, c);
                        return;
                }

                switch (c) {
                case '?':
                        btem->btem_state = A_STATE_CSI_QMARK;
                        return;
                default:
                        btem_getparams(btem, c);
                        return;
                }
        }

        /* Previous char was <ESC> */
        switch (c) {
        case '[':
                btem->btem_curparam = 0;
                btem->btem_paramval = 0;
                btem->btem_gotparam = B_FALSE;
                /* clear the parameters */
                for (i = 0; i < BTEM_MAXPARAMS; i++)
                        btem->btem_params[i] = -1;
                btem->btem_state = A_STATE_CSI;
                return;

        case 'Q':       /* <ESC>Q */
        case 'C':       /* <ESC>C */
                btem->btem_state = A_STATE_START;
                return;

        default:
                btem->btem_state = A_STATE_START;
                break;
        }

        if (c < ' ')
                btem_control(btem, c);
        else
                bcons_dev.bd_putchar(c);
}

static void
_doputchar(int device, int c)
{
        switch (device) {
        case CONS_TTY:
                serial_putchar(c);
                return;
        case CONS_SCREEN_TEXT:
        case CONS_FRAMEBUFFER:
                bcons_dev.bd_cursor(B_FALSE);
                btem_parse(&boot_tem, c);
                bcons_dev.bd_cursor(B_TRUE);
                return;
        case CONS_SCREEN_GRAPHICS:
#if !defined(_BOOT)
        case CONS_USBSER:
                defcons_putchar(c);
#endif /* _BOOT */
        default:
                return;
        }
}

void
bcons_putchar(int c)
{
#if defined(__xpv)
        if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
            console == CONS_HYPERVISOR) {
                bcons_putchar_xen(c);
                return;
        }
#endif /* __xpv */

        if (c == '\n') {
                _doputchar(console, '\r');
                if (diag != console)
                        _doputchar(diag, '\r');
        }
        _doputchar(console, c);
        if (diag != console)
                _doputchar(diag, c);
}

/*
 * kernel character input functions
 */
int
bcons_getchar(void)
{
#if defined(__xpv)
        if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
            console == CONS_HYPERVISOR)
                return (bcons_getchar_xen());
#endif /* __xpv */

        for (;;) {
                if (console == CONS_TTY || diag == CONS_TTY) {
                        if (serial_ischar())
                                return (serial_getchar());
                }
                if (console != CONS_INVALID || diag != CONS_INVALID) {
                        if (kb_ischar())
                                return (kb_getchar());
                }
        }
}

/*
 * Nothing below is used by dboot.
 */
#if !defined(_BOOT)

int
bcons_ischar(void)
{
        int c = 0;

#if defined(__xpv)
        if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
            console == CONS_HYPERVISOR)
                return (bcons_ischar_xen());
#endif /* __xpv */

        switch (console) {
        case CONS_TTY:
                c = serial_ischar();
                break;

        case CONS_INVALID:
                break;

        default:
                c = kb_ischar();
        }
        if (c != 0)
                return (c);

        switch (diag) {
        case CONS_TTY:
                c = serial_ischar();
                break;

        case CONS_INVALID:
                break;

        default:
                c = kb_ischar();
        }

        return (c);
}

/*
 * 2nd part of console initialization: we've now processed bootenv.rc; update
 * console settings as appropriate. This only really processes serial console
 * modifications.
 */
void
bcons_post_bootenvrc(char *inputdev, char *outputdev, char *consoledev)
{
        int cons = CONS_INVALID;
        int ttyn;
        char *devnames[] = { consoledev, outputdev, inputdev, NULL };
        int i;
        extern int post_fastreboot;

        ttyn = 0;
        if (post_fastreboot && console == CONS_SCREEN_GRAPHICS)
                console = CONS_SCREEN_TEXT;

        /*
         * USB serial and GRAPHICS console: we just collect data into a buffer.
         */
        if (console == CONS_USBSER || console == CONS_SCREEN_GRAPHICS) {
                extern void *defcons_init(size_t);
                defcons_buf = defcons_cur = defcons_init(MMU_PAGESIZE);
                return;
        }

        for (i = 0; devnames[i] != NULL; i++) {
                cons = lookup_console_device(devnames[i], &ttyn);
                if (cons != CONS_INVALID)
                        break;
        }

        if (cons == CONS_INVALID) {
                /*
                 * No console change, but let's see if bootenv.rc had a mode
                 * setting we should apply.
                 */
                if (console == CONS_TTY && !bootprop_set_tty_mode)
                        serial_init();
                return;
        }

#if defined(__xpv)
        /*
         * if the hypervisor is using the currently selected console device then
         * default to using the hypervisor as the console device.
         */
        if (cons == console_hypervisor_device) {
                cons = CONS_HYPERVISOR;
                console_hypervisor_redirect = B_TRUE;
        }
#endif /* __xpv */

        console = cons;

        if (console == CONS_TTY) {
                tty_num = ttyn;
                serial_init();
        }
}

#if defined(__xpv)
boolean_t
bcons_hypervisor_redirect(void)
{
        return (console_hypervisor_redirect);
}

void
bcons_device_change(int new_console)
{
        if (new_console < CONS_MIN || new_console > CONS_MAX)
                return;

        /*
         * If we are asked to switch the console to the hypervisor, that
         * really means to switch the console to whichever device the
         * hypervisor is/was using.
         */
        if (new_console == CONS_HYPERVISOR)
                new_console = console_hypervisor_device;

        console = new_console;

        if (new_console == CONS_TTY) {
                tty_num = console_hypervisor_tty_num;
                serial_init();
        }
}
#endif /* __xpv */

static void
defcons_putchar(int c)
{
        if (defcons_buf != NULL &&
            defcons_cur + 1 - defcons_buf < MMU_PAGESIZE) {
                *defcons_cur++ = c;
                *defcons_cur = 0;
        }
}

#endif /* _BOOT */