Index: head/stand/efi/include/efilib.h =================================================================== --- head/stand/efi/include/efilib.h +++ head/stand/efi/include/efilib.h @@ -106,6 +106,7 @@ void efi_time_init(void); void efi_time_fini(void); +int parse_uefi_con_out(void); bool efi_cons_update_mode(void); EFI_STATUS efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE* Xsystab); Index: head/stand/efi/libefi/Makefile =================================================================== --- head/stand/efi/libefi/Makefile +++ head/stand/efi/libefi/Makefile @@ -61,4 +61,9 @@ # Handle FreeBSD specific %b and %D printf format specifiers CFLAGS+= ${FORMAT_EXTENSIONS} +# Do not use TERM_EMU on arm and arm64 as it doesn't behave well with serial console +.if ${MACHINE_CPUARCH} != "arm" && ${MACHINE_CPUARCH} != "aarch64" +CFLAGS+= -DTERM_EMU +.endif + .include Index: head/stand/efi/libefi/efi_console.c =================================================================== --- head/stand/efi/libefi/efi_console.c +++ head/stand/efi/libefi/efi_console.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "bootstrap.h" @@ -38,6 +39,26 @@ 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; @@ -447,6 +468,340 @@ 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) { @@ -461,35 +816,58 @@ rows = 24; } - if (buffer != NULL) { - if (tp.tp_row == rows && tp.tp_col == cols) - return (true); - free(buffer); - } else { - teken_init(&teken, &tf, NULL); - } + /* + * 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); + 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); + 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); + 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; + 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); @@ -522,10 +900,108 @@ 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)); Index: head/stand/efi/loader/main.c =================================================================== --- head/stand/efi/loader/main.c +++ head/stand/efi/loader/main.c @@ -688,7 +688,7 @@ * ACPI name space to map the UID for the serial port to a port. The * latter is especially hard. */ -static int +int parse_uefi_con_out(void) { int how, rv;