freebsd-skq/stand/efi/libefi/efi_console.c
tsoome 615ed873f7 loader: fall back to term_emu on efi console with serial backend
In case of efi console having serial backend (video + serial or only serial),
we need to stick with old emulator till we can draw console.

Eventually we would need to get console terminal emulator to be removed
from serial console because the serial link already has the terminal.

However, we need to implement comconsole on all efi platforms first, then
we need the ability to draw console, so we do not have to use SimpleTextOutput
protocol (which will write both on video and serial in case of multiplexed
ComOut).

Differential Revision:	https://reviews.freebsd.org/D22161
2019-11-02 09:50:36 +00:00

1197 lines
24 KiB
C

/*-
* Copyright (c) 2000 Doug Rabson
* 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 AUTHOR 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 AUTHOR 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/cdefs.h>
__FBSDID("$FreeBSD$");
#include <efi.h>
#include <efilib.h>
#include <teken.h>
#include <sys/reboot.h>
#include "bootstrap.h"
static EFI_GUID simple_input_ex_guid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID;
static SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
static SIMPLE_INPUT_INTERFACE *conin;
static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *coninex;
static int mode; /* Does ConOut have serial console? */
static uint32_t utf8_left;
static uint32_t utf8_partial;
#ifdef TERM_EMU
#define DEFAULT_FGCOLOR EFI_LIGHTGRAY
#define DEFAULT_BGCOLOR EFI_BLACK
#define MAXARGS 8
static int args[MAXARGS], argc;
static int fg_c, bg_c, curx, cury;
static int esc;
void get_pos(int *x, int *y);
void curs_move(int *_x, int *_y, int x, int y);
static void CL(int);
void HO(void);
void end_term(void);
#endif
static tf_bell_t efi_cons_bell;
static tf_cursor_t efi_text_cursor;
static tf_putchar_t efi_text_putchar;
static tf_fill_t efi_text_fill;
static tf_copy_t efi_text_copy;
static tf_param_t efi_text_param;
static tf_respond_t efi_cons_respond;
static teken_funcs_t tf = {
.tf_bell = efi_cons_bell,
.tf_cursor = efi_text_cursor,
.tf_putchar = efi_text_putchar,
.tf_fill = efi_text_fill,
.tf_copy = efi_text_copy,
.tf_param = efi_text_param,
.tf_respond = efi_cons_respond,
};
teken_t teken;
teken_pos_t tp;
struct text_pixel {
teken_char_t c;
teken_attr_t a;
};
static struct text_pixel *buffer;
#define KEYBUFSZ 10
static unsigned keybuf[KEYBUFSZ]; /* keybuf for extended codes */
static int key_pending;
static const unsigned char teken_color_to_efi_color[16] = {
EFI_BLACK,
EFI_RED,
EFI_GREEN,
EFI_BROWN,
EFI_BLUE,
EFI_MAGENTA,
EFI_CYAN,
EFI_LIGHTGRAY,
EFI_DARKGRAY,
EFI_LIGHTRED,
EFI_LIGHTGREEN,
EFI_YELLOW,
EFI_LIGHTBLUE,
EFI_LIGHTMAGENTA,
EFI_LIGHTCYAN,
EFI_WHITE
};
static void efi_cons_probe(struct console *);
static int efi_cons_init(int);
void efi_cons_putchar(int);
int efi_cons_getchar(void);
void efi_cons_efiputchar(int);
int efi_cons_poll(void);
struct console efi_console = {
"efi",
"EFI console",
C_WIDEOUT,
efi_cons_probe,
efi_cons_init,
efi_cons_putchar,
efi_cons_getchar,
efi_cons_poll
};
/*
* Not implemented.
*/
static void
efi_cons_bell(void *s __unused)
{
}
static void
efi_text_cursor(void *s __unused, const teken_pos_t *p)
{
UINTN row, col;
(void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row);
if (p->tp_col == col)
col = p->tp_col - 1;
else
col = p->tp_col;
if (p->tp_row == row)
row = p->tp_row - 1;
else
row = p->tp_row;
conout->SetCursorPosition(conout, col, row);
}
static void
efi_text_printchar(const teken_pos_t *p, bool autoscroll)
{
UINTN a, attr;
struct text_pixel *px;
teken_color_t fg, bg, tmp;
px = buffer + p->tp_col + p->tp_row * tp.tp_col;
a = conout->Mode->Attribute;
fg = teken_256to16(px->a.ta_fgcolor);
bg = teken_256to16(px->a.ta_bgcolor);
if (px->a.ta_format & TF_BOLD)
fg |= TC_LIGHT;
if (px->a.ta_format & TF_BLINK)
bg |= TC_LIGHT;
if (px->a.ta_format & TF_REVERSE) {
tmp = fg;
fg = bg;
bg = tmp;
}
attr = EFI_TEXT_ATTR(teken_color_to_efi_color[fg],
teken_color_to_efi_color[bg] & 0x7);
conout->SetCursorPosition(conout, p->tp_col, p->tp_row);
/* to prvent autoscroll, skip print of lower right char */
if (!autoscroll &&
p->tp_row == tp.tp_row - 1 &&
p->tp_col == tp.tp_col - 1)
return;
(void) conout->SetAttribute(conout, attr);
efi_cons_efiputchar(px->c);
(void) conout->SetAttribute(conout, a);
}
static void
efi_text_putchar(void *s __unused, const teken_pos_t *p, teken_char_t c,
const teken_attr_t *a)
{
EFI_STATUS status;
int idx;
idx = p->tp_col + p->tp_row * tp.tp_col;
buffer[idx].c = c;
buffer[idx].a = *a;
efi_text_printchar(p, false);
}
static void
efi_text_fill(void *s, const teken_rect_t *r, teken_char_t c,
const teken_attr_t *a)
{
teken_pos_t p;
UINTN row, col;
(void) conout->QueryMode(conout, conout->Mode->Mode, &col, &row);
conout->EnableCursor(conout, FALSE);
for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row;
p.tp_row++)
for (p.tp_col = r->tr_begin.tp_col;
p.tp_col < r->tr_end.tp_col; p.tp_col++)
efi_text_putchar(s, &p, c, a);
conout->EnableCursor(conout, TRUE);
}
static bool
efi_same_pixel(struct text_pixel *px1, struct text_pixel *px2)
{
if (px1->c != px2->c)
return (false);
if (px1->a.ta_format != px2->a.ta_format)
return (false);
if (px1->a.ta_fgcolor != px2->a.ta_fgcolor)
return (false);
if (px1->a.ta_bgcolor != px2->a.ta_bgcolor)
return (false);
return (true);
}
static void
efi_text_copy(void *ptr __unused, const teken_rect_t *r, const teken_pos_t *p)
{
int srow, drow;
int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */
teken_pos_t d, s;
bool scroll = false;
/*
* Copying is a little tricky. We must make sure we do it in
* correct order, to make sure we don't overwrite our own data.
*/
nrow = r->tr_end.tp_row - r->tr_begin.tp_row;
ncol = r->tr_end.tp_col - r->tr_begin.tp_col;
/*
* Check if we do copy whole screen.
*/
if (p->tp_row == 0 && p->tp_col == 0 &&
nrow == tp.tp_row - 2 && ncol == tp.tp_col - 2)
scroll = true;
conout->EnableCursor(conout, FALSE);
if (p->tp_row < r->tr_begin.tp_row) {
/* Copy from bottom to top. */
for (y = 0; y < nrow; y++) {
d.tp_row = p->tp_row + y;
s.tp_row = r->tr_begin.tp_row + y;
drow = d.tp_row * tp.tp_col;
srow = s.tp_row * tp.tp_col;
for (x = 0; x < ncol; x++) {
d.tp_col = p->tp_col + x;
s.tp_col = r->tr_begin.tp_col + x;
if (!efi_same_pixel(
&buffer[d.tp_col + drow],
&buffer[s.tp_col + srow])) {
buffer[d.tp_col + drow] =
buffer[s.tp_col + srow];
if (!scroll)
efi_text_printchar(&d, false);
} else if (scroll) {
/*
* Draw last char and trigger
* scroll.
*/
if (y == nrow - 1 &&
x == ncol - 1) {
efi_text_printchar(&d, true);
}
}
}
}
} else {
/* Copy from top to bottom. */
if (p->tp_col < r->tr_begin.tp_col) {
/* Copy from right to left. */
for (y = nrow - 1; y >= 0; y--) {
d.tp_row = p->tp_row + y;
s.tp_row = r->tr_begin.tp_row + y;
drow = d.tp_row * tp.tp_col;
srow = s.tp_row * tp.tp_col;
for (x = 0; x < ncol; x++) {
d.tp_col = p->tp_col + x;
s.tp_col = r->tr_begin.tp_col + x;
if (!efi_same_pixel(
&buffer[d.tp_col + drow],
&buffer[s.tp_col + srow])) {
buffer[d.tp_col + drow] =
buffer[s.tp_col + srow];
efi_text_printchar(&d, false);
}
}
}
} else {
/* Copy from left to right. */
for (y = nrow - 1; y >= 0; y--) {
d.tp_row = p->tp_row + y;
s.tp_row = r->tr_begin.tp_row + y;
drow = d.tp_row * tp.tp_col;
srow = s.tp_row * tp.tp_col;
for (x = ncol - 1; x >= 0; x--) {
d.tp_col = p->tp_col + x;
s.tp_col = r->tr_begin.tp_col + x;
if (!efi_same_pixel(
&buffer[d.tp_col + drow],
&buffer[s.tp_col + srow])) {
buffer[d.tp_col + drow] =
buffer[s.tp_col + srow];
efi_text_printchar(&d, false);
}
}
}
}
}
conout->EnableCursor(conout, TRUE);
}
static void
efi_text_param(void *s __unused, int cmd, unsigned int value)
{
switch (cmd) {
case TP_SETLOCALCURSOR:
/*
* 0 means normal (usually block), 1 means hidden, and
* 2 means blinking (always block) for compatibility with
* syscons. We don't support any changes except hiding,
* so must map 2 to 0.
*/
value = (value == 1) ? 0 : 1;
/* FALLTHROUGH */
case TP_SHOWCURSOR:
if (value == 1)
conout->EnableCursor(conout, TRUE);
else
conout->EnableCursor(conout, FALSE);
break;
default:
/* Not yet implemented */
break;
}
}
/*
* Not implemented.
*/
static void
efi_cons_respond(void *s __unused, const void *buf __unused,
size_t len __unused)
{
}
static void
efi_cons_probe(struct console *cp)
{
cp->c_flags |= C_PRESENTIN | C_PRESENTOUT;
}
static bool
color_name_to_teken(const char *name, int *val)
{
if (strcasecmp(name, "black") == 0) {
*val = TC_BLACK;
return (true);
}
if (strcasecmp(name, "red") == 0) {
*val = TC_RED;
return (true);
}
if (strcasecmp(name, "green") == 0) {
*val = TC_GREEN;
return (true);
}
if (strcasecmp(name, "brown") == 0) {
*val = TC_BROWN;
return (true);
}
if (strcasecmp(name, "blue") == 0) {
*val = TC_BLUE;
return (true);
}
if (strcasecmp(name, "magenta") == 0) {
*val = TC_MAGENTA;
return (true);
}
if (strcasecmp(name, "cyan") == 0) {
*val = TC_CYAN;
return (true);
}
if (strcasecmp(name, "white") == 0) {
*val = TC_WHITE;
return (true);
}
return (false);
}
static int
efi_set_colors(struct env_var *ev, int flags, const void *value)
{
int val = 0;
char buf[2];
const void *evalue;
const teken_attr_t *ap;
teken_attr_t a;
if (value == NULL)
return (CMD_OK);
if (color_name_to_teken(value, &val)) {
snprintf(buf, sizeof (buf), "%d", val);
evalue = buf;
} else {
char *end;
errno = 0;
val = (int)strtol(value, &end, 0);
if (errno != 0 || *end != '\0') {
printf("Allowed values are either ansi color name or "
"number from range [0-7].\n");
return (CMD_OK);
}
evalue = value;
}
ap = teken_get_defattr(&teken);
a = *ap;
if (strcmp(ev->ev_name, "teken.fg_color") == 0) {
/* is it already set? */
if (ap->ta_fgcolor == val)
return (CMD_OK);
a.ta_fgcolor = val;
}
if (strcmp(ev->ev_name, "teken.bg_color") == 0) {
/* is it already set? */
if (ap->ta_bgcolor == val)
return (CMD_OK);
a.ta_bgcolor = val;
}
env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL);
teken_set_defattr(&teken, &a);
return (CMD_OK);
}
#ifdef TERM_EMU
/* Get cursor position. */
void
get_pos(int *x, int *y)
{
*x = conout->Mode->CursorColumn;
*y = conout->Mode->CursorRow;
}
/* Move cursor to x rows and y cols (0-based). */
void
curs_move(int *_x, int *_y, int x, int y)
{
conout->SetCursorPosition(conout, x, y);
if (_x != NULL)
*_x = conout->Mode->CursorColumn;
if (_y != NULL)
*_y = conout->Mode->CursorRow;
}
/* Clear internal state of the terminal emulation code. */
void
end_term(void)
{
esc = 0;
argc = -1;
}
#endif
static void
efi_cons_rawputchar(int c)
{
int i;
UINTN x, y;
conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
if (c == '\t') {
int n;
n = 8 - ((conout->Mode->CursorColumn + 8) % 8);
for (i = 0; i < n; i++)
efi_cons_rawputchar(' ');
} else {
#ifndef TERM_EMU
if (c == '\n')
efi_cons_efiputchar('\r');
efi_cons_efiputchar(c);
#else
switch (c) {
case '\r':
curx = 0;
efi_cons_efiputchar('\r');
return;
case '\n':
efi_cons_efiputchar('\n');
efi_cons_efiputchar('\r');
cury++;
if (cury >= y)
cury--;
curx = 0;
return;
case '\b':
if (curx > 0) {
efi_cons_efiputchar('\b');
curx--;
}
return;
default:
efi_cons_efiputchar(c);
curx++;
if (curx > x-1) {
curx = 0;
cury++;
}
if (cury > y-1) {
curx = 0;
cury--;
}
}
#endif
}
conout->EnableCursor(conout, TRUE);
}
#ifdef TERM_EMU
/* Gracefully exit ESC-sequence processing in case of misunderstanding. */
static void
bail_out(int c)
{
char buf[16], *ch;
int i;
if (esc) {
efi_cons_rawputchar('\033');
if (esc != '\033')
efi_cons_rawputchar(esc);
for (i = 0; i <= argc; ++i) {
sprintf(buf, "%d", args[i]);
ch = buf;
while (*ch)
efi_cons_rawputchar(*ch++);
}
}
efi_cons_rawputchar(c);
end_term();
}
/* Clear display from current position to end of screen. */
static void
CD(void)
{
int i;
UINTN x, y;
get_pos(&curx, &cury);
if (curx == 0 && cury == 0) {
conout->ClearScreen(conout);
end_term();
return;
}
conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
CL(0); /* clear current line from cursor to end */
for (i = cury + 1; i < y-1; i++) {
curs_move(NULL, NULL, 0, i);
CL(0);
}
curs_move(NULL, NULL, curx, cury);
end_term();
}
/*
* Absolute cursor move to args[0] rows and args[1] columns
* (the coordinates are 1-based).
*/
static void
CM(void)
{
if (args[0] > 0)
args[0]--;
if (args[1] > 0)
args[1]--;
curs_move(&curx, &cury, args[1], args[0]);
end_term();
}
/* Home cursor (left top corner), also called from mode command. */
void
HO(void)
{
argc = 1;
args[0] = args[1] = 1;
CM();
}
/* Clear line from current position to end of line */
static void
CL(int direction)
{
int i, len;
UINTN x, y;
CHAR16 *line;
conout->QueryMode(conout, conout->Mode->Mode, &x, &y);
switch (direction) {
case 0: /* from cursor to end */
len = x - curx + 1;
break;
case 1: /* from beginning to cursor */
len = curx;
break;
case 2: /* entire line */
len = x;
break;
default: /* NOTREACHED */
__unreachable();
}
if (cury == y - 1)
len--;
line = malloc(len * sizeof (CHAR16));
if (line == NULL) {
printf("out of memory\n");
return;
}
for (i = 0; i < len; i++)
line[i] = ' ';
line[len-1] = 0;
if (direction != 0)
curs_move(NULL, NULL, 0, cury);
conout->OutputString(conout, line);
/* restore cursor position */
curs_move(NULL, NULL, curx, cury);
free(line);
end_term();
}
static void
get_arg(int c)
{
if (argc < 0)
argc = 0;
args[argc] *= 10;
args[argc] += c - '0';
}
#endif
/* Emulate basic capabilities of cons25 terminal */
static void
efi_term_emu(int c)
{
#ifdef TERM_EMU
static int ansi_col[] = {
0, 4, 2, 6, 1, 5, 3, 7
};
int t, i;
EFI_STATUS status;
switch (esc) {
case 0:
switch (c) {
case '\033':
esc = c;
break;
default:
efi_cons_rawputchar(c);
break;
}
break;
case '\033':
switch (c) {
case '[':
esc = c;
args[0] = 0;
argc = -1;
break;
default:
bail_out(c);
break;
}
break;
case '[':
switch (c) {
case ';':
if (argc < 0)
argc = 0;
else if (argc + 1 >= MAXARGS)
bail_out(c);
else
args[++argc] = 0;
break;
case 'H': /* ho = \E[H */
if (argc < 0)
HO();
else if (argc == 1)
CM();
else
bail_out(c);
break;
case 'J': /* cd = \E[J */
if (argc < 0)
CD();
else
bail_out(c);
break;
case 'm':
if (argc < 0) {
fg_c = DEFAULT_FGCOLOR;
bg_c = DEFAULT_BGCOLOR;
}
for (i = 0; i <= argc; ++i) {
switch (args[i]) {
case 0: /* back to normal */
fg_c = DEFAULT_FGCOLOR;
bg_c = DEFAULT_BGCOLOR;
break;
case 1: /* bold */
fg_c |= 0x8;
break;
case 4: /* underline */
case 5: /* blink */
bg_c |= 0x8;
break;
case 7: /* reverse */
t = fg_c;
fg_c = bg_c;
bg_c = t;
break;
case 22: /* normal intensity */
fg_c &= ~0x8;
break;
case 24: /* not underline */
case 25: /* not blinking */
bg_c &= ~0x8;
break;
case 30: case 31: case 32: case 33:
case 34: case 35: case 36: case 37:
fg_c = ansi_col[args[i] - 30];
break;
case 39: /* normal */
fg_c = DEFAULT_FGCOLOR;
break;
case 40: case 41: case 42: case 43:
case 44: case 45: case 46: case 47:
bg_c = ansi_col[args[i] - 40];
break;
case 49: /* normal */
bg_c = DEFAULT_BGCOLOR;
break;
}
}
conout->SetAttribute(conout, EFI_TEXT_ATTR(fg_c, bg_c));
end_term();
break;
default:
if (isdigit(c))
get_arg(c);
else
bail_out(c);
break;
}
break;
default:
bail_out(c);
break;
}
#else
efi_cons_rawputchar(c);
#endif
}
bool
efi_cons_update_mode(void)
{
UINTN cols, rows;
const teken_attr_t *a;
EFI_STATUS status;
char env[8];
status = conout->QueryMode(conout, conout->Mode->Mode, &cols, &rows);
if (EFI_ERROR(status)) {
cols = 80;
rows = 24;
}
/*
* When we have serial port listed in ConOut, use pre-teken emulator,
* if built with.
* The problem is, we can not output text on efi and comconsole when
* efi also has comconsole bound. But then again, we need to have
* terminal emulator for efi text mode to support the menu.
* While teken is too expensive to be used on serial console, the
* pre-teken emulator is light enough to be used on serial console.
*/
mode = parse_uefi_con_out();
if ((mode & RB_SERIAL) == 0) {
if (buffer != NULL) {
if (tp.tp_row == rows && tp.tp_col == cols)
return (true);
free(buffer);
} else {
teken_init(&teken, &tf, NULL);
}
tp.tp_row = rows;
tp.tp_col = cols;
buffer = malloc(rows * cols * sizeof(*buffer));
if (buffer == NULL)
return (false);
teken_set_winsize(&teken, &tp);
a = teken_get_defattr(&teken);
snprintf(env, sizeof(env), "%d", a->ta_fgcolor);
env_setenv("teken.fg_color", EV_VOLATILE, env, efi_set_colors,
env_nounset);
snprintf(env, sizeof(env), "%d", a->ta_bgcolor);
env_setenv("teken.bg_color", EV_VOLATILE, env, efi_set_colors,
env_nounset);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
buffer[col + row * tp.tp_col].c = ' ';
buffer[col + row * tp.tp_col].a = *a;
}
}
} else {
#ifdef TERM_EMU
conout->SetAttribute(conout, EFI_TEXT_ATTR(DEFAULT_FGCOLOR,
DEFAULT_BGCOLOR));
end_term();
get_pos(&curx, &cury);
curs_move(&curx, &cury, curx, cury);
fg_c = DEFAULT_FGCOLOR;
bg_c = DEFAULT_BGCOLOR;
#endif
}
snprintf(env, sizeof (env), "%u", (unsigned)rows);
setenv("LINES", env, 1);
snprintf(env, sizeof (env), "%u", (unsigned)cols);
setenv("COLUMNS", env, 1);
return (true);
}
static int
efi_cons_init(int arg)
{
EFI_STATUS status;
if (conin != NULL)
return (0);
conout = ST->ConOut;
conin = ST->ConIn;
conout->EnableCursor(conout, TRUE);
status = BS->OpenProtocol(ST->ConsoleInHandle, &simple_input_ex_guid,
(void **)&coninex, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (status != EFI_SUCCESS)
coninex = NULL;
if (efi_cons_update_mode())
return (0);
return (1);
}
static void
input_partial(void)
{
unsigned i;
uint32_t c;
if (utf8_left == 0)
return;
for (i = 0; i < sizeof(utf8_partial); i++) {
c = (utf8_partial >> (24 - (i << 3))) & 0xff;
if (c != 0)
efi_term_emu(c);
}
utf8_left = 0;
utf8_partial = 0;
}
static void
input_byte(uint8_t c)
{
if ((c & 0x80) == 0x00) {
/* One-byte sequence. */
input_partial();
efi_term_emu(c);
return;
}
if ((c & 0xe0) == 0xc0) {
/* Two-byte sequence. */
input_partial();
utf8_left = 1;
utf8_partial = c;
return;
}
if ((c & 0xf0) == 0xe0) {
/* Three-byte sequence. */
input_partial();
utf8_left = 2;
utf8_partial = c;
return;
}
if ((c & 0xf8) == 0xf0) {
/* Four-byte sequence. */
input_partial();
utf8_left = 3;
utf8_partial = c;
return;
}
if ((c & 0xc0) == 0x80) {
/* Invalid state? */
if (utf8_left == 0) {
efi_term_emu(c);
return;
}
utf8_left--;
utf8_partial = (utf8_partial << 8) | c;
if (utf8_left == 0) {
uint32_t v, u;
uint8_t b;
v = 0;
u = utf8_partial;
b = (u >> 24) & 0xff;
if (b != 0) { /* Four-byte sequence */
v = b & 0x07;
b = (u >> 16) & 0xff;
v = (v << 6) | (b & 0x3f);
b = (u >> 8) & 0xff;
v = (v << 6) | (b & 0x3f);
b = u & 0xff;
v = (v << 6) | (b & 0x3f);
} else if ((b = (u >> 16) & 0xff) != 0) {
v = b & 0x0f; /* Three-byte sequence */
b = (u >> 8) & 0xff;
v = (v << 6) | (b & 0x3f);
b = u & 0xff;
v = (v << 6) | (b & 0x3f);
} else if ((b = (u >> 8) & 0xff) != 0) {
v = b & 0x1f; /* Two-byte sequence */
b = u & 0xff;
v = (v << 6) | (b & 0x3f);
}
/* Send unicode char directly to console. */
efi_cons_efiputchar(v);
utf8_partial = 0;
}
return;
}
/* Anything left is illegal in UTF-8 sequence. */
input_partial();
efi_term_emu(c);
}
void
efi_cons_putchar(int c)
{
unsigned char ch = c;
if ((mode & RB_SERIAL) != 0) {
input_byte(ch);
return;
}
if (buffer != NULL)
teken_input(&teken, &ch, sizeof (ch));
else
efi_cons_efiputchar(c);
}
static int
keybuf_getchar(void)
{
int i, c = 0;
for (i = 0; i < KEYBUFSZ; i++) {
if (keybuf[i] != 0) {
c = keybuf[i];
keybuf[i] = 0;
break;
}
}
return (c);
}
static bool
keybuf_ischar(void)
{
int i;
for (i = 0; i < KEYBUFSZ; i++) {
if (keybuf[i] != 0)
return (true);
}
return (false);
}
/*
* We are not reading input before keybuf is empty, so we are safe
* just to fill keybuf from the beginning.
*/
static void
keybuf_inschar(EFI_INPUT_KEY *key)
{
switch (key->ScanCode) {
case SCAN_UP: /* UP */
keybuf[0] = 0x1b; /* esc */
keybuf[1] = '[';
keybuf[2] = 'A';
break;
case SCAN_DOWN: /* DOWN */
keybuf[0] = 0x1b; /* esc */
keybuf[1] = '[';
keybuf[2] = 'B';
break;
case SCAN_RIGHT: /* RIGHT */
keybuf[0] = 0x1b; /* esc */
keybuf[1] = '[';
keybuf[2] = 'C';
break;
case SCAN_LEFT: /* LEFT */
keybuf[0] = 0x1b; /* esc */
keybuf[1] = '[';
keybuf[2] = 'D';
break;
case SCAN_DELETE:
keybuf[0] = CHAR_BACKSPACE;
break;
case SCAN_ESC:
keybuf[0] = 0x1b; /* esc */
break;
default:
keybuf[0] = key->UnicodeChar;
break;
}
}
static bool
efi_readkey(void)
{
EFI_STATUS status;
EFI_INPUT_KEY key;
status = conin->ReadKeyStroke(conin, &key);
if (status == EFI_SUCCESS) {
keybuf_inschar(&key);
return (true);
}
return (false);
}
static bool
efi_readkey_ex(void)
{
EFI_STATUS status;
EFI_INPUT_KEY *kp;
EFI_KEY_DATA key_data;
uint32_t kss;
status = coninex->ReadKeyStrokeEx(coninex, &key_data);
if (status == EFI_SUCCESS) {
kss = key_data.KeyState.KeyShiftState;
kp = &key_data.Key;
if (kss & EFI_SHIFT_STATE_VALID) {
/*
* quick mapping to control chars, replace with
* map lookup later.
*/
if (kss & EFI_RIGHT_CONTROL_PRESSED ||
kss & EFI_LEFT_CONTROL_PRESSED) {
if (kp->UnicodeChar >= 'a' &&
kp->UnicodeChar <= 'z') {
kp->UnicodeChar -= 'a';
kp->UnicodeChar++;
}
}
}
keybuf_inschar(kp);
return (true);
}
return (false);
}
int
efi_cons_getchar(void)
{
int c;
if ((c = keybuf_getchar()) != 0)
return (c);
key_pending = 0;
if (coninex == NULL) {
if (efi_readkey())
return (keybuf_getchar());
} else {
if (efi_readkey_ex())
return (keybuf_getchar());
}
return (-1);
}
int
efi_cons_poll(void)
{
EFI_STATUS status;
if (keybuf_ischar() || key_pending)
return (1);
/*
* Some EFI implementation (u-boot for example) do not support
* WaitForKey().
* CheckEvent() can clear the signaled state.
*/
if (coninex != NULL) {
if (coninex->WaitForKeyEx == NULL) {
key_pending = efi_readkey_ex();
} else {
status = BS->CheckEvent(coninex->WaitForKeyEx);
key_pending = status == EFI_SUCCESS;
}
} else {
if (conin->WaitForKey == NULL) {
key_pending = efi_readkey();
} else {
status = BS->CheckEvent(conin->WaitForKey);
key_pending = status == EFI_SUCCESS;
}
}
return (key_pending);
}
/* Plain direct access to EFI OutputString(). */
void
efi_cons_efiputchar(int c)
{
CHAR16 buf[2];
EFI_STATUS status;
buf[0] = c;
buf[1] = 0; /* terminate string */
status = conout->TestString(conout, buf);
if (EFI_ERROR(status))
buf[0] = '?';
conout->OutputString(conout, buf);
}