freebsd-dev/stand/efi/libefi/efi_console.c
Toomas Soome a2e02d9d8e loader.efi: fix panic() after BS off
panic() is using multiple services - attempting to read
keyboard, accessing time functions and finally, exiting the loader.

Protect all the accessed listed above. Note, when BS are off,
we really can not just exit the loader, we only can reboot.

MFC after:	1 week
2022-06-19 17:46:35 +03:00

1431 lines
31 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 <sys/param.h>
#include <efi.h>
#include <efilib.h>
#include <teken.h>
#include <sys/reboot.h>
#include <machine/metadata.h>
#include <gfx_fb.h>
#include <framebuffer.h>
#include "bootstrap.h"
extern EFI_GUID gop_guid;
bool boot_services_active = true; /* boot services active first thing in main */
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 bool efi_started;
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
#define TEXT_ROWS 24
#define TEXT_COLS 80
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,
};
static teken_funcs_t tfx = {
.tf_bell = efi_cons_bell,
.tf_cursor = gfx_fb_cursor,
.tf_putchar = gfx_fb_putchar,
.tf_fill = gfx_fb_fill,
.tf_copy = gfx_fb_copy,
.tf_param = gfx_fb_param,
.tf_respond = efi_cons_respond,
};
#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);
static void cons_draw_frame(teken_attr_t *);
struct console efi_console = {
"efi",
"EFI console",
C_WIDEOUT,
efi_cons_probe,
efi_cons_init,
efi_cons_putchar,
efi_cons_getchar,
efi_cons_poll
};
/*
* This function is used to mark a rectangular image area so the scrolling
* will know we need to copy the data from there.
*/
void
term_image_display(teken_gfx_t *state, const teken_rect_t *r)
{
teken_pos_t p;
int idx;
if (screen_buffer == NULL)
return;
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++) {
idx = p.tp_col + p.tp_row * state->tg_tp.tp_col;
if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
return;
screen_buffer[idx].a.ta_format |= TF_IMAGE;
}
}
}
/*
* Not implemented.
*/
static void
efi_cons_bell(void *s __unused)
{
}
static void
efi_text_cursor(void *arg, const teken_pos_t *p)
{
teken_gfx_t *state = arg;
UINTN col, row;
if (!boot_services_active)
return;
row = p->tp_row;
if (p->tp_row >= state->tg_tp.tp_row)
row = state->tg_tp.tp_row - 1;
col = p->tp_col;
if (p->tp_col >= state->tg_tp.tp_col)
col = state->tg_tp.tp_col - 1;
conout->SetCursorPosition(conout, col, row);
}
static void
efi_text_printchar(teken_gfx_t *state, const teken_pos_t *p, bool autoscroll)
{
UINTN a, attr;
struct text_pixel *px;
teken_color_t fg, bg, tmp;
px = screen_buffer + p->tp_col + p->tp_row * state->tg_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 prevent autoscroll, skip print of lower right char */
if (!autoscroll &&
p->tp_row == state->tg_tp.tp_row - 1 &&
p->tp_col == state->tg_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, const teken_pos_t *p, teken_char_t c,
const teken_attr_t *a)
{
teken_gfx_t *state = s;
EFI_STATUS status;
int idx;
if (!boot_services_active)
return;
idx = p->tp_col + p->tp_row * state->tg_tp.tp_col;
if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row)
return;
screen_buffer[idx].c = c;
screen_buffer[idx].a = *a;
efi_text_printchar(s, p, false);
}
static void
efi_text_fill(void *arg, const teken_rect_t *r, teken_char_t c,
const teken_attr_t *a)
{
teken_gfx_t *state = arg;
teken_pos_t p;
if (!boot_services_active)
return;
if (state->tg_cursor_visible)
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(state, &p, c, a);
if (state->tg_cursor_visible)
conout->EnableCursor(conout, TRUE);
}
static void
efi_text_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s,
teken_pos_t *d, bool scroll)
{
unsigned soffset, doffset;
teken_pos_t sp, dp;
int x;
soffset = s->tp_col + s->tp_row * state->tg_tp.tp_col;
doffset = d->tp_col + d->tp_row * state->tg_tp.tp_col;
sp = *s;
dp = *d;
for (x = 0; x < ncol; x++) {
sp.tp_col = s->tp_col + x;
dp.tp_col = d->tp_col + x;
if (!is_same_pixel(&screen_buffer[soffset + x],
&screen_buffer[doffset + x])) {
screen_buffer[doffset + x] =
screen_buffer[soffset + x];
if (!scroll)
efi_text_printchar(state, &dp, false);
} else if (scroll) {
/* Draw last char and trigger scroll. */
if (dp.tp_col + 1 == state->tg_tp.tp_col &&
dp.tp_row + 1 == state->tg_tp.tp_row) {
efi_text_printchar(state, &dp, true);
}
}
}
}
static void
efi_text_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p)
{
teken_gfx_t *state = arg;
unsigned doffset, soffset;
teken_pos_t d, s;
int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */
bool scroll = false;
if (!boot_services_active)
return;
/*
* 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 == state->tg_tp.tp_row - 2 && ncol == state->tg_tp.tp_col - 2)
scroll = true;
soffset = r->tr_begin.tp_col + r->tr_begin.tp_row * state->tg_tp.tp_col;
doffset = p->tp_col + p->tp_row * state->tg_tp.tp_col;
/* remove the cursor */
if (state->tg_cursor_visible)
conout->EnableCursor(conout, FALSE);
/*
* Copy line by line.
*/
if (doffset <= soffset) {
s = r->tr_begin;
d = *p;
for (y = 0; y < nrow; y++) {
s.tp_row = r->tr_begin.tp_row + y;
d.tp_row = p->tp_row + y;
efi_text_copy_line(state, ncol, &s, &d, scroll);
}
} else {
for (y = nrow - 1; y >= 0; y--) {
s.tp_row = r->tr_begin.tp_row + y;
d.tp_row = p->tp_row + y;
efi_text_copy_line(state, ncol, &s, &d, false);
}
}
/* display the cursor */
if (state->tg_cursor_visible)
conout->EnableCursor(conout, TRUE);
}
static void
efi_text_param(void *arg, int cmd, unsigned int value)
{
teken_gfx_t *state = arg;
if (!boot_services_active)
return;
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 != 0) {
conout->EnableCursor(conout, TRUE);
state->tg_cursor_visible = true;
} else {
conout->EnableCursor(conout, FALSE);
state->tg_cursor_visible = 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)
{
}
/*
* Set up conin/conout/coninex to make sure we have input ready.
*/
static void
efi_cons_probe(struct console *cp)
{
EFI_STATUS status;
conout = ST->ConOut;
conin = ST->ConIn;
/*
* Call SetMode to work around buggy firmware.
*/
status = conout->SetMode(conout, conout->Mode->Mode);
if (coninex == NULL) {
status = BS->OpenProtocol(ST->ConsoleInHandle,
&simple_input_ex_guid, (void **)&coninex,
IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (status != EFI_SUCCESS)
coninex = NULL;
}
cp->c_flags |= C_PRESENTIN | C_PRESENTOUT;
}
static bool
color_name_to_teken(const char *name, int *val)
{
int light = 0;
if (strncasecmp(name, "light", 5) == 0) {
name += 5;
light = TC_LIGHT;
} else if (strncasecmp(name, "bright", 6) == 0) {
name += 6;
light = TC_LIGHT;
}
if (strcasecmp(name, "black") == 0) {
*val = TC_BLACK | light;
return (true);
}
if (strcasecmp(name, "red") == 0) {
*val = TC_RED | light;
return (true);
}
if (strcasecmp(name, "green") == 0) {
*val = TC_GREEN | light;
return (true);
}
if (strcasecmp(name, "yellow") == 0 || strcasecmp(name, "brown") == 0) {
*val = TC_YELLOW | light;
return (true);
}
if (strcasecmp(name, "blue") == 0) {
*val = TC_BLUE | light;
return (true);
}
if (strcasecmp(name, "magenta") == 0) {
*val = TC_MAGENTA | light;
return (true);
}
if (strcasecmp(name, "cyan") == 0) {
*val = TC_CYAN | light;
return (true);
}
if (strcasecmp(name, "white") == 0) {
*val = TC_WHITE | light;
return (true);
}
return (false);
}
static int
efi_set_colors(struct env_var *ev, int flags, const void *value)
{
int val = 0;
char buf[3];
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;
long lval;
errno = 0;
lval = strtol(value, &end, 0);
if (errno != 0 || *end != '\0' || lval < 0 || lval > 15) {
printf("Allowed values are either ansi color name or "
"number from range [0-15].\n");
return (CMD_OK);
}
val = (int)lval;
evalue = value;
}
ap = teken_get_defattr(&gfx_state.tg_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;
}
/* Improve visibility */
if (a.ta_bgcolor == TC_WHITE)
a.ta_bgcolor |= TC_LIGHT;
teken_set_defattr(&gfx_state.tg_teken, &a);
cons_draw_frame(&a);
env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL);
teken_input(&gfx_state.tg_teken, "\e[2J", 4);
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)
{
if (!boot_services_active)
return;
#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
}
static int
env_screen_nounset(struct env_var *ev __unused)
{
if (gfx_state.tg_fb_type == FB_TEXT)
return (0);
return (EPERM);
}
static void
cons_draw_frame(teken_attr_t *a)
{
teken_attr_t attr = *a;
teken_color_t fg = a->ta_fgcolor;
attr.ta_fgcolor = attr.ta_bgcolor;
teken_set_defattr(&gfx_state.tg_teken, &attr);
gfx_fb_drawrect(0, 0, gfx_state.tg_fb.fb_width,
gfx_state.tg_origin.tp_row, 1);
gfx_fb_drawrect(0,
gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1,
gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, 1);
gfx_fb_drawrect(0, gfx_state.tg_origin.tp_row,
gfx_state.tg_origin.tp_col,
gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1, 1);
gfx_fb_drawrect(
gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col - 1,
gfx_state.tg_origin.tp_row, gfx_state.tg_fb.fb_width,
gfx_state.tg_fb.fb_height, 1);
attr.ta_fgcolor = fg;
teken_set_defattr(&gfx_state.tg_teken, &attr);
}
bool
cons_update_mode(bool use_gfx_mode)
{
UINTN cols, rows;
const teken_attr_t *a;
teken_attr_t attr;
EFI_STATUS status;
char env[10], *ptr;
if (!efi_started)
return (false);
/*
* Despite the use_gfx_mode, we want to make sure we call
* efi_find_framebuffer(). This will populate the fb data,
* which will be passed to kernel.
*/
if (efi_find_framebuffer(&gfx_state) == 0 && use_gfx_mode) {
int roff, goff, boff;
roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1;
goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1;
boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1;
(void) generate_cons_palette(cmap, COLOR_FORMAT_RGB,
gfx_state.tg_fb.fb_mask_red >> roff, roff,
gfx_state.tg_fb.fb_mask_green >> goff, goff,
gfx_state.tg_fb.fb_mask_blue >> boff, boff);
} else {
/*
* Either text mode was asked by user or we failed to
* find frame buffer.
*/
gfx_state.tg_fb_type = FB_TEXT;
}
status = conout->QueryMode(conout, conout->Mode->Mode, &cols, &rows);
if (EFI_ERROR(status) || cols * rows == 0) {
cols = TEXT_COLS;
rows = TEXT_ROWS;
}
/*
* 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.
*
* When doing multiple consoles (both serial and video),
* also just use the old emulator. RB_MULTIPLE also implies
* we're using a serial console.
*/
mode = parse_uefi_con_out();
if ((mode & (RB_SERIAL | RB_MULTIPLE)) == 0) {
conout->EnableCursor(conout, FALSE);
gfx_state.tg_cursor_visible = false;
if (gfx_state.tg_fb_type == FB_TEXT) {
gfx_state.tg_functions = &tf;
/* ensure the following are not set for text mode */
unsetenv("screen.height");
unsetenv("screen.width");
unsetenv("screen.depth");
} else {
uint32_t fb_height, fb_width;
fb_height = gfx_state.tg_fb.fb_height;
fb_width = gfx_state.tg_fb.fb_width;
/*
* setup_font() can adjust terminal size.
* We can see two kind of bad happening.
* We either can get too small console font - requested
* terminal size is large, display resolution is
* large, and we get very small font.
* Or, we can get too large font - requested
* terminal size is small and this will cause large
* font to be selected.
* Now, the setup_font() is updated to consider
* display density and this should give us mostly
* acceptable font. However, the catch is, not all
* display devices will give us display density.
* Still, we do hope, external monitors do - this is
* where the display size will matter the most.
* And for laptop screens, we should still get good
* results by requesting 80x25 terminal.
*/
gfx_state.tg_tp.tp_row = 25;
gfx_state.tg_tp.tp_col = 80;
setup_font(&gfx_state, fb_height, fb_width);
rows = gfx_state.tg_tp.tp_row;
cols = gfx_state.tg_tp.tp_col;
/* Point of origin in pixels. */
gfx_state.tg_origin.tp_row = (fb_height -
(rows * gfx_state.tg_font.vf_height)) / 2;
gfx_state.tg_origin.tp_col = (fb_width -
(cols * gfx_state.tg_font.vf_width)) / 2;
/* UEFI gop has depth 32. */
gfx_state.tg_glyph_size = gfx_state.tg_font.vf_height *
gfx_state.tg_font.vf_width * 4;
free(gfx_state.tg_glyph);
gfx_state.tg_glyph = malloc(gfx_state.tg_glyph_size);
if (gfx_state.tg_glyph == NULL)
return (false);
gfx_state.tg_functions = &tfx;
snprintf(env, sizeof (env), "%d", fb_height);
env_setenv("screen.height", EV_VOLATILE | EV_NOHOOK,
env, env_noset, env_screen_nounset);
snprintf(env, sizeof (env), "%d", fb_width);
env_setenv("screen.width", EV_VOLATILE | EV_NOHOOK,
env, env_noset, env_screen_nounset);
snprintf(env, sizeof (env), "%d",
gfx_state.tg_fb.fb_bpp);
env_setenv("screen.depth", EV_VOLATILE | EV_NOHOOK,
env, env_noset, env_screen_nounset);
}
/* Record our terminal screen size. */
gfx_state.tg_tp.tp_row = rows;
gfx_state.tg_tp.tp_col = cols;
teken_init(&gfx_state.tg_teken, gfx_state.tg_functions,
&gfx_state);
free(screen_buffer);
screen_buffer = malloc(rows * cols * sizeof(*screen_buffer));
if (screen_buffer != NULL) {
teken_set_winsize(&gfx_state.tg_teken,
&gfx_state.tg_tp);
a = teken_get_defattr(&gfx_state.tg_teken);
attr = *a;
/*
* On first run, we set up the efi_set_colors()
* callback. If the env is already set, we
* pick up fg and bg color values from the environment.
*/
ptr = getenv("teken.fg_color");
if (ptr != NULL) {
attr.ta_fgcolor = strtol(ptr, NULL, 10);
ptr = getenv("teken.bg_color");
attr.ta_bgcolor = strtol(ptr, NULL, 10);
teken_set_defattr(&gfx_state.tg_teken, &attr);
} else {
snprintf(env, sizeof(env), "%d",
attr.ta_fgcolor);
env_setenv("teken.fg_color", EV_VOLATILE, env,
efi_set_colors, env_nounset);
snprintf(env, sizeof(env), "%d",
attr.ta_bgcolor);
env_setenv("teken.bg_color", EV_VOLATILE, env,
efi_set_colors, env_nounset);
}
}
}
if (screen_buffer == NULL) {
conout->EnableCursor(conout, TRUE);
#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
} else {
/* Improve visibility */
if (attr.ta_bgcolor == TC_WHITE)
attr.ta_bgcolor |= TC_LIGHT;
teken_set_defattr(&gfx_state.tg_teken, &attr);
/* Draw frame around terminal area. */
cons_draw_frame(&attr);
/*
* Erase display, this will also fill our screen
* buffer.
*/
teken_input(&gfx_state.tg_teken, "\e[2J", 4);
gfx_state.tg_functions->tf_param(&gfx_state,
TP_SHOWCURSOR, 1);
}
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 (efi_started)
return (0);
efi_started = true;
gfx_framework_init();
if (cons_update_mode(gfx_state.tg_fb_type != FB_TEXT))
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;
/*
* Don't use Teken when we're doing pure serial, or a multiple console
* with video "primary" because that's also serial.
*/
if ((mode & (RB_SERIAL | RB_MULTIPLE)) != 0 || screen_buffer == NULL) {
input_byte(ch);
return;
}
teken_input(&gfx_state.tg_teken, &ch, sizeof (ch));
}
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++;
}
}
}
/*
* The shift state and/or toggle state may not be valid,
* but we still can have ScanCode or UnicodeChar.
*/
if (kp->ScanCode == 0 && kp->UnicodeChar == 0)
return (false);
keybuf_inschar(kp);
return (true);
}
return (false);
}
int
efi_cons_getchar(void)
{
int c;
if ((c = keybuf_getchar()) != 0)
return (c);
if (!boot_services_active)
return (-1);
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);
if (!boot_services_active)
return (0);
/*
* 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);
}