From 041d1ea37802bf7178a31a53f96c26efa6b8fb7b Mon Sep 17 00:00:00 2001 From: James Date: Fri, 16 Nov 2012 10:41:01 +0000 Subject: fish --- grub-core/term/at_keyboard.c | 638 ++++++++++++++++++ grub-core/term/efi/console.c | 291 +++++++++ grub-core/term/gfxterm.c | 1220 +++++++++++++++++++++++++++++++++++ grub-core/term/i386/pc/console.c | 68 ++ grub-core/term/i386/pc/vga_text.c | 175 +++++ grub-core/term/i386/vga_common.c | 48 ++ grub-core/term/ieee1275/ofconsole.c | 249 +++++++ grub-core/term/ns8250.c | 278 ++++++++ grub-core/term/serial.c | 372 +++++++++++ grub-core/term/terminfo.c | 652 +++++++++++++++++++ grub-core/term/tparm.c | 761 ++++++++++++++++++++++ grub-core/term/usb_keyboard.c | 472 ++++++++++++++ 12 files changed, 5224 insertions(+) create mode 100644 grub-core/term/at_keyboard.c create mode 100644 grub-core/term/efi/console.c create mode 100644 grub-core/term/gfxterm.c create mode 100644 grub-core/term/i386/pc/console.c create mode 100644 grub-core/term/i386/pc/vga_text.c create mode 100644 grub-core/term/i386/vga_common.c create mode 100644 grub-core/term/ieee1275/ofconsole.c create mode 100644 grub-core/term/ns8250.c create mode 100644 grub-core/term/serial.c create mode 100644 grub-core/term/terminfo.c create mode 100644 grub-core/term/tparm.c create mode 100644 grub-core/term/usb_keyboard.c (limited to 'grub-core/term') diff --git a/grub-core/term/at_keyboard.c b/grub-core/term/at_keyboard.c new file mode 100644 index 0000000..f0f346b --- /dev/null +++ b/grub-core/term/at_keyboard.c @@ -0,0 +1,638 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static short at_keyboard_status = 0; +static int e0_received = 0; +static int f0_received = 0; + +static grub_uint8_t led_status; + +#define KEYBOARD_LED_SCROLL (1 << 0) +#define KEYBOARD_LED_NUM (1 << 1) +#define KEYBOARD_LED_CAPS (1 << 2) + +static grub_uint8_t grub_keyboard_controller_orig; +static grub_uint8_t grub_keyboard_orig_set; +static grub_uint8_t current_set; + +static const grub_uint8_t set1_mapping[128] = + { + /* 0x00 */ 0 /* Unused */, GRUB_KEYBOARD_KEY_ESCAPE, + /* 0x02 */ GRUB_KEYBOARD_KEY_1, GRUB_KEYBOARD_KEY_2, + /* 0x04 */ GRUB_KEYBOARD_KEY_3, GRUB_KEYBOARD_KEY_4, + /* 0x06 */ GRUB_KEYBOARD_KEY_5, GRUB_KEYBOARD_KEY_6, + /* 0x08 */ GRUB_KEYBOARD_KEY_7, GRUB_KEYBOARD_KEY_8, + /* 0x0a */ GRUB_KEYBOARD_KEY_9, GRUB_KEYBOARD_KEY_0, + /* 0x0c */ GRUB_KEYBOARD_KEY_DASH, GRUB_KEYBOARD_KEY_EQUAL, + /* 0x0e */ GRUB_KEYBOARD_KEY_BACKSPACE, GRUB_KEYBOARD_KEY_TAB, + /* 0x10 */ GRUB_KEYBOARD_KEY_Q, GRUB_KEYBOARD_KEY_W, + /* 0x12 */ GRUB_KEYBOARD_KEY_E, GRUB_KEYBOARD_KEY_R, + /* 0x14 */ GRUB_KEYBOARD_KEY_T, GRUB_KEYBOARD_KEY_Y, + /* 0x16 */ GRUB_KEYBOARD_KEY_U, GRUB_KEYBOARD_KEY_I, + /* 0x18 */ GRUB_KEYBOARD_KEY_O, GRUB_KEYBOARD_KEY_P, + /* 0x1a */ GRUB_KEYBOARD_KEY_LBRACKET, GRUB_KEYBOARD_KEY_RBRACKET, + /* 0x1c */ GRUB_KEYBOARD_KEY_ENTER, GRUB_KEYBOARD_KEY_LEFT_CTRL, + /* 0x1e */ GRUB_KEYBOARD_KEY_A, GRUB_KEYBOARD_KEY_S, + /* 0x20 */ GRUB_KEYBOARD_KEY_D, GRUB_KEYBOARD_KEY_F, + /* 0x22 */ GRUB_KEYBOARD_KEY_G, GRUB_KEYBOARD_KEY_H, + /* 0x24 */ GRUB_KEYBOARD_KEY_J, GRUB_KEYBOARD_KEY_K, + /* 0x26 */ GRUB_KEYBOARD_KEY_L, GRUB_KEYBOARD_KEY_SEMICOLON, + /* 0x28 */ GRUB_KEYBOARD_KEY_DQUOTE, GRUB_KEYBOARD_KEY_RQUOTE, + /* 0x2a */ GRUB_KEYBOARD_KEY_LEFT_SHIFT, GRUB_KEYBOARD_KEY_BACKSLASH, + /* 0x2c */ GRUB_KEYBOARD_KEY_Z, GRUB_KEYBOARD_KEY_X, + /* 0x2e */ GRUB_KEYBOARD_KEY_C, GRUB_KEYBOARD_KEY_V, + /* 0x30 */ GRUB_KEYBOARD_KEY_B, GRUB_KEYBOARD_KEY_N, + /* 0x32 */ GRUB_KEYBOARD_KEY_M, GRUB_KEYBOARD_KEY_COMMA, + /* 0x34 */ GRUB_KEYBOARD_KEY_DOT, GRUB_KEYBOARD_KEY_SLASH, + /* 0x36 */ GRUB_KEYBOARD_KEY_RIGHT_SHIFT, GRUB_KEYBOARD_KEY_NUMMUL, + /* 0x38 */ GRUB_KEYBOARD_KEY_LEFT_ALT, GRUB_KEYBOARD_KEY_SPACE, + /* 0x3a */ GRUB_KEYBOARD_KEY_CAPS_LOCK, GRUB_KEYBOARD_KEY_F1, + /* 0x3c */ GRUB_KEYBOARD_KEY_F2, GRUB_KEYBOARD_KEY_F3, + /* 0x3e */ GRUB_KEYBOARD_KEY_F4, GRUB_KEYBOARD_KEY_F5, + /* 0x40 */ GRUB_KEYBOARD_KEY_F6, GRUB_KEYBOARD_KEY_F7, + /* 0x42 */ GRUB_KEYBOARD_KEY_F8, GRUB_KEYBOARD_KEY_F9, + /* 0x44 */ GRUB_KEYBOARD_KEY_F10, GRUB_KEYBOARD_KEY_NUM_LOCK, + /* 0x46 */ GRUB_KEYBOARD_KEY_SCROLL_LOCK, GRUB_KEYBOARD_KEY_NUM7, + /* 0x48 */ GRUB_KEYBOARD_KEY_NUM8, GRUB_KEYBOARD_KEY_NUM9, + /* 0x4a */ GRUB_KEYBOARD_KEY_NUMMINUS, GRUB_KEYBOARD_KEY_NUM4, + /* 0x4c */ GRUB_KEYBOARD_KEY_NUM5, GRUB_KEYBOARD_KEY_NUM6, + /* 0x4e */ GRUB_KEYBOARD_KEY_NUMPLUS, GRUB_KEYBOARD_KEY_NUM1, + /* 0x50 */ GRUB_KEYBOARD_KEY_NUM2, GRUB_KEYBOARD_KEY_NUM3, + /* 0x52 */ GRUB_KEYBOARD_KEY_NUMDOT, GRUB_KEYBOARD_KEY_NUMDOT, + /* 0x54 */ 0, 0, + /* 0x56 */ GRUB_KEYBOARD_KEY_102ND, GRUB_KEYBOARD_KEY_F11, + /* 0x58 */ GRUB_KEYBOARD_KEY_F12, 0, + /* 0x5a */ 0, 0, + /* 0x5c */ 0, 0, + /* 0x5e */ 0, 0, + /* 0x60 */ 0, 0, + /* 0x62 */ 0, 0, + /* OLPC keys. Just mapped to normal keys. */ + /* 0x64 */ 0, GRUB_KEYBOARD_KEY_UP, + /* 0x66 */ GRUB_KEYBOARD_KEY_DOWN, GRUB_KEYBOARD_KEY_LEFT, + /* 0x68 */ GRUB_KEYBOARD_KEY_RIGHT + }; + +static const struct +{ + grub_uint8_t from, to; +} set1_e0_mapping[] = + { + {0x1c, GRUB_KEYBOARD_KEY_NUMENTER}, + {0x1d, GRUB_KEYBOARD_KEY_RIGHT_CTRL}, + {0x35, GRUB_KEYBOARD_KEY_NUMSLASH }, + {0x38, GRUB_KEYBOARD_KEY_RIGHT_ALT}, + {0x47, GRUB_KEYBOARD_KEY_HOME}, + {0x48, GRUB_KEYBOARD_KEY_UP}, + {0x49, GRUB_KEYBOARD_KEY_PPAGE}, + {0x4b, GRUB_KEYBOARD_KEY_LEFT}, + {0x4d, GRUB_KEYBOARD_KEY_RIGHT}, + {0x4f, GRUB_KEYBOARD_KEY_END}, + {0x50, GRUB_KEYBOARD_KEY_DOWN}, + {0x51, GRUB_KEYBOARD_KEY_NPAGE}, + {0x52, GRUB_KEYBOARD_KEY_INSERT}, + {0x53, GRUB_KEYBOARD_KEY_DELETE}, + }; + +static const grub_uint8_t set2_mapping[256] = + { + /* 0x00 */ 0, GRUB_KEYBOARD_KEY_F9, + /* 0x02 */ 0, GRUB_KEYBOARD_KEY_F5, + /* 0x04 */ GRUB_KEYBOARD_KEY_F3, GRUB_KEYBOARD_KEY_F1, + /* 0x06 */ GRUB_KEYBOARD_KEY_F2, GRUB_KEYBOARD_KEY_F12, + /* 0x08 */ 0, GRUB_KEYBOARD_KEY_F10, + /* 0x0a */ GRUB_KEYBOARD_KEY_F8, GRUB_KEYBOARD_KEY_F6, + /* 0x0c */ GRUB_KEYBOARD_KEY_F4, GRUB_KEYBOARD_KEY_TAB, + /* 0x0e */ GRUB_KEYBOARD_KEY_RQUOTE, 0, + /* 0x10 */ 0, GRUB_KEYBOARD_KEY_LEFT_ALT, + /* 0x12 */ GRUB_KEYBOARD_KEY_LEFT_SHIFT, 0, + /* 0x14 */ GRUB_KEYBOARD_KEY_LEFT_CTRL, GRUB_KEYBOARD_KEY_Q, + /* 0x16 */ GRUB_KEYBOARD_KEY_1, 0, + /* 0x18 */ 0, 0, + /* 0x1a */ GRUB_KEYBOARD_KEY_Z, GRUB_KEYBOARD_KEY_S, + /* 0x1c */ GRUB_KEYBOARD_KEY_A, GRUB_KEYBOARD_KEY_W, + /* 0x1e */ GRUB_KEYBOARD_KEY_2, 0, + /* 0x20 */ 0, GRUB_KEYBOARD_KEY_C, + /* 0x22 */ GRUB_KEYBOARD_KEY_X, GRUB_KEYBOARD_KEY_D, + /* 0x24 */ GRUB_KEYBOARD_KEY_E, GRUB_KEYBOARD_KEY_4, + /* 0x26 */ GRUB_KEYBOARD_KEY_3, 0, + /* 0x28 */ 0, GRUB_KEYBOARD_KEY_SPACE, + /* 0x2a */ GRUB_KEYBOARD_KEY_V, GRUB_KEYBOARD_KEY_F, + /* 0x2c */ GRUB_KEYBOARD_KEY_T, GRUB_KEYBOARD_KEY_R, + /* 0x2e */ GRUB_KEYBOARD_KEY_5, 0, + /* 0x30 */ 0, GRUB_KEYBOARD_KEY_N, + /* 0x32 */ GRUB_KEYBOARD_KEY_B, GRUB_KEYBOARD_KEY_H, + /* 0x34 */ GRUB_KEYBOARD_KEY_G, GRUB_KEYBOARD_KEY_Y, + /* 0x36 */ GRUB_KEYBOARD_KEY_6, 0, + /* 0x38 */ 0, 0, + /* 0x3a */ GRUB_KEYBOARD_KEY_M, GRUB_KEYBOARD_KEY_J, + /* 0x3c */ GRUB_KEYBOARD_KEY_U, GRUB_KEYBOARD_KEY_7, + /* 0x3e */ GRUB_KEYBOARD_KEY_8, 0, + /* 0x40 */ 0, GRUB_KEYBOARD_KEY_COMMA, + /* 0x42 */ GRUB_KEYBOARD_KEY_K, GRUB_KEYBOARD_KEY_I, + /* 0x44 */ GRUB_KEYBOARD_KEY_O, GRUB_KEYBOARD_KEY_0, + /* 0x46 */ GRUB_KEYBOARD_KEY_9, 0, + /* 0x48 */ 0, GRUB_KEYBOARD_KEY_DOT, + /* 0x4a */ GRUB_KEYBOARD_KEY_SLASH, GRUB_KEYBOARD_KEY_L, + /* 0x4c */ GRUB_KEYBOARD_KEY_SEMICOLON, GRUB_KEYBOARD_KEY_P, + /* 0x4e */ GRUB_KEYBOARD_KEY_DASH, 0, + /* 0x50 */ 0, 0, + /* 0x52 */ GRUB_KEYBOARD_KEY_DQUOTE, 0, + /* 0x54 */ GRUB_KEYBOARD_KEY_LBRACKET, GRUB_KEYBOARD_KEY_EQUAL, + /* 0x56 */ 0, 0, + /* 0x58 */ GRUB_KEYBOARD_KEY_CAPS_LOCK, GRUB_KEYBOARD_KEY_RIGHT_SHIFT, + /* 0x5a */ GRUB_KEYBOARD_KEY_ENTER, GRUB_KEYBOARD_KEY_RBRACKET, + /* 0x5c */ 0, GRUB_KEYBOARD_KEY_BACKSLASH, + /* 0x5e */ 0, 0, + /* 0x60 */ 0, GRUB_KEYBOARD_KEY_102ND, + /* 0x62 */ 0, 0, + /* 0x64 */ 0, 0, + /* 0x66 */ GRUB_KEYBOARD_KEY_BACKSPACE, 0, + /* 0x68 */ 0, GRUB_KEYBOARD_KEY_NUM1, + /* 0x6a */ 0, GRUB_KEYBOARD_KEY_NUM4, + /* 0x6c */ GRUB_KEYBOARD_KEY_NUM7, 0, + /* 0x6e */ 0, 0, + /* 0x70 */ GRUB_KEYBOARD_KEY_NUMDOT, GRUB_KEYBOARD_KEY_NUM0, + /* 0x72 */ GRUB_KEYBOARD_KEY_NUM2, GRUB_KEYBOARD_KEY_NUM5, + /* 0x74 */ GRUB_KEYBOARD_KEY_NUM6, GRUB_KEYBOARD_KEY_NUM8, + /* 0x76 */ GRUB_KEYBOARD_KEY_ESCAPE, GRUB_KEYBOARD_KEY_NUM_LOCK, + /* 0x78 */ GRUB_KEYBOARD_KEY_F11, GRUB_KEYBOARD_KEY_NUMPLUS, + /* 0x7a */ GRUB_KEYBOARD_KEY_NUM3, GRUB_KEYBOARD_KEY_NUMMINUS, + /* 0x7c */ GRUB_KEYBOARD_KEY_NUMMUL, GRUB_KEYBOARD_KEY_NUM9, + /* 0x7e */ GRUB_KEYBOARD_KEY_SCROLL_LOCK, 0, + /* 0x80 */ 0, 0, + /* 0x82 */ 0, GRUB_KEYBOARD_KEY_F7, + }; + +static const struct +{ + grub_uint8_t from, to; +} set2_e0_mapping[] = + { + {0x11, GRUB_KEYBOARD_KEY_RIGHT_ALT}, + {0x14, GRUB_KEYBOARD_KEY_RIGHT_CTRL}, + {0x4a, GRUB_KEYBOARD_KEY_NUMSLASH}, + {0x5a, GRUB_KEYBOARD_KEY_NUMENTER}, + {0x69, GRUB_KEYBOARD_KEY_END}, + {0x6b, GRUB_KEYBOARD_KEY_LEFT}, + {0x6c, GRUB_KEYBOARD_KEY_HOME}, + {0x70, GRUB_KEYBOARD_KEY_INSERT}, + {0x71, GRUB_KEYBOARD_KEY_DELETE}, + {0x72, GRUB_KEYBOARD_KEY_DOWN}, + {0x74, GRUB_KEYBOARD_KEY_RIGHT}, + {0x75, GRUB_KEYBOARD_KEY_UP}, + {0x7a, GRUB_KEYBOARD_KEY_NPAGE}, + {0x7d, GRUB_KEYBOARD_KEY_PPAGE}, + }; + +static void +keyboard_controller_wait_until_ready (void) +{ + while (! KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS))); +} + +static grub_uint8_t +wait_ack (void) +{ + grub_uint64_t endtime; + grub_uint8_t ack; + + endtime = grub_get_time_ms () + 20; + do + ack = grub_inb (KEYBOARD_REG_DATA); + while (ack != GRUB_AT_ACK && ack != GRUB_AT_NACK + && grub_get_time_ms () < endtime); + return ack; +} + +static int +at_command (grub_uint8_t data) +{ + unsigned i; + for (i = 0; i < GRUB_AT_TRIES; i++) + { + grub_uint8_t ack; + keyboard_controller_wait_until_ready (); + grub_outb (data, KEYBOARD_REG_STATUS); + ack = wait_ack (); + if (ack == GRUB_AT_NACK) + continue; + if (ack == GRUB_AT_ACK) + break; + return 0; + } + return (i != GRUB_AT_TRIES); +} + +static void +grub_keyboard_controller_write (grub_uint8_t c) +{ + at_command (KEYBOARD_COMMAND_WRITE); + keyboard_controller_wait_until_ready (); + grub_outb (c, KEYBOARD_REG_DATA); +} + +#if !defined (GRUB_MACHINE_MIPS_YEELOONG) && !defined (GRUB_MACHINE_QEMU) + +static grub_uint8_t +grub_keyboard_controller_read (void) +{ + at_command (KEYBOARD_COMMAND_READ); + keyboard_controller_wait_until_ready (); + return grub_inb (KEYBOARD_REG_DATA); +} + +#endif + +static int +write_mode (int mode) +{ + unsigned i; + for (i = 0; i < GRUB_AT_TRIES; i++) + { + grub_uint8_t ack; + keyboard_controller_wait_until_ready (); + grub_outb (0xf0, KEYBOARD_REG_DATA); + keyboard_controller_wait_until_ready (); + grub_outb (mode, KEYBOARD_REG_DATA); + keyboard_controller_wait_until_ready (); + ack = wait_ack (); + if (ack == GRUB_AT_NACK) + continue; + if (ack == GRUB_AT_ACK) + break; + return 0; + } + + return (i != GRUB_AT_TRIES); +} + +static int +query_mode (void) +{ + grub_uint8_t ret; + int e; + + e = write_mode (0); + if (!e) + return 0; + + keyboard_controller_wait_until_ready (); + + do + ret = grub_inb (KEYBOARD_REG_DATA); + while (ret == GRUB_AT_ACK); + + /* QEMU translates the set even in no-translate mode. */ + if (ret == 0x43 || ret == 1) + return 1; + if (ret == 0x41 || ret == 2) + return 2; + if (ret == 0x3f || ret == 3) + return 3; + return 0; +} + +static void +set_scancodes (void) +{ + /* You must have visited computer museum. Keyboard without scancode set + knowledge. Assume XT. */ + if (!grub_keyboard_orig_set) + { + grub_dprintf ("atkeyb", "No sets support assumed\n"); + current_set = 1; + return; + } + +#if !(defined (GRUB_MACHINE_MIPS_YEELOONG) || defined (GRUB_MACHINE_QEMU)) + current_set = 1; + return; +#endif + + grub_keyboard_controller_write (grub_keyboard_controller_orig + & ~KEYBOARD_AT_TRANSLATE); + + write_mode (2); + current_set = query_mode (); + grub_dprintf ("atkeyb", "returned set %d\n", current_set); + if (current_set == 2) + return; + + write_mode (1); + current_set = query_mode (); + grub_dprintf ("atkeyb", "returned set %d\n", current_set); + if (current_set == 1) + return; + grub_printf ("No supported scancode set found\n"); +} + +static void +keyboard_controller_led (grub_uint8_t leds) +{ + keyboard_controller_wait_until_ready (); + grub_outb (0xed, KEYBOARD_REG_DATA); + keyboard_controller_wait_until_ready (); + grub_outb (leds & 0x7, KEYBOARD_REG_DATA); +} + +static int +fetch_key (int *is_break) +{ + int was_ext = 0; + grub_uint8_t at_key; + int ret = 0; + + if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS))) + return -1; + at_key = grub_inb (KEYBOARD_REG_DATA); + if (at_key == 0xe0) + { + e0_received = 1; + return -1; + } + + if ((current_set == 2 || current_set == 3) && at_key == 0xf0) + { + f0_received = 1; + return -1; + } + + /* Setting LEDs may generate ACKs. */ + if (at_key == GRUB_AT_ACK) + return -1; + + was_ext = e0_received; + e0_received = 0; + + switch (current_set) + { + case 1: + *is_break = !!(at_key & 0x80); + if (!was_ext) + ret = set1_mapping[at_key & 0x7f]; + else + { + unsigned i; + for (i = 0; i < ARRAY_SIZE (set1_e0_mapping); i++) + if (set1_e0_mapping[i].from == (at_key & 0x7f)) + { + ret = set1_e0_mapping[i].to; + break; + } + } + break; + case 2: + *is_break = f0_received; + f0_received = 0; + if (!was_ext) + ret = set2_mapping[at_key]; + else + { + unsigned i; + for (i = 0; i < ARRAY_SIZE (set2_e0_mapping); i++) + if (set2_e0_mapping[i].from == at_key) + { + ret = set2_e0_mapping[i].to; + break; + } + } + break; + default: + return -1; + } + if (!ret) + { + if (was_ext) + grub_dprintf ("atkeyb", "Unknown key 0xe0+0x%02x from set %d\n", + at_key, current_set); + else + grub_dprintf ("atkeyb", "Unknown key 0x%02x from set %d\n", + at_key, current_set); + return -1; + } + return ret; +} + +/* FIXME: This should become an interrupt service routine. For now + it's just used to catch events from control keys. */ +static int +grub_keyboard_isr (grub_keyboard_key_t key, int is_break) +{ + if (!is_break) + switch (key) + { + case GRUB_KEYBOARD_KEY_LEFT_SHIFT: + at_keyboard_status |= GRUB_TERM_STATUS_LSHIFT; + return 1; + case GRUB_KEYBOARD_KEY_RIGHT_SHIFT: + at_keyboard_status |= GRUB_TERM_STATUS_RSHIFT; + return 1; + case GRUB_KEYBOARD_KEY_LEFT_CTRL: + at_keyboard_status |= GRUB_TERM_STATUS_LCTRL; + return 1; + case GRUB_KEYBOARD_KEY_RIGHT_CTRL: + at_keyboard_status |= GRUB_TERM_STATUS_RCTRL; + return 1; + case GRUB_KEYBOARD_KEY_RIGHT_ALT: + at_keyboard_status |= GRUB_TERM_STATUS_RALT; + return 1; + case GRUB_KEYBOARD_KEY_LEFT_ALT: + at_keyboard_status |= GRUB_TERM_STATUS_LALT; + return 1; + default: + return 0; + } + else + switch (key) + { + case GRUB_KEYBOARD_KEY_LEFT_SHIFT: + at_keyboard_status &= ~GRUB_TERM_STATUS_LSHIFT; + return 1; + case GRUB_KEYBOARD_KEY_RIGHT_SHIFT: + at_keyboard_status &= ~GRUB_TERM_STATUS_RSHIFT; + return 1; + case GRUB_KEYBOARD_KEY_LEFT_CTRL: + at_keyboard_status &= ~GRUB_TERM_STATUS_LCTRL; + return 1; + case GRUB_KEYBOARD_KEY_RIGHT_CTRL: + at_keyboard_status &= ~GRUB_TERM_STATUS_RCTRL; + return 1; + case GRUB_KEYBOARD_KEY_RIGHT_ALT: + at_keyboard_status &= ~GRUB_TERM_STATUS_RALT; + return 1; + case GRUB_KEYBOARD_KEY_LEFT_ALT: + at_keyboard_status &= ~GRUB_TERM_STATUS_LALT; + return 1; + default: + return 0; + } +} + +/* If there is a raw key pending, return it; otherwise return -1. */ +static int +grub_keyboard_getkey (void) +{ + int key; + int is_break = 0; + + key = fetch_key (&is_break); + if (key == -1) + return -1; + + if (grub_keyboard_isr (key, is_break)) + return -1; + if (is_break) + return -1; + return key; +} + +/* If there is a character pending, return it; + otherwise return GRUB_TERM_NO_KEY. */ +static int +grub_at_keyboard_getkey (struct grub_term_input *term __attribute__ ((unused))) +{ + int code; + code = grub_keyboard_getkey (); + if (code == -1) + return GRUB_TERM_NO_KEY; +#ifdef DEBUG_AT_KEYBOARD + grub_dprintf ("atkeyb", "Detected key 0x%x\n", key); +#endif + switch (code) + { + case GRUB_KEYBOARD_KEY_CAPS_LOCK: + at_keyboard_status ^= GRUB_TERM_STATUS_CAPS; + led_status ^= KEYBOARD_LED_CAPS; + keyboard_controller_led (led_status); + +#ifdef DEBUG_AT_KEYBOARD + grub_dprintf ("atkeyb", "caps_lock = %d\n", !!(at_keyboard_status & KEYBOARD_STATUS_CAPS_LOCK)); +#endif + return GRUB_TERM_NO_KEY; + case GRUB_KEYBOARD_KEY_NUM_LOCK: + at_keyboard_status ^= GRUB_TERM_STATUS_NUM; + led_status ^= KEYBOARD_LED_NUM; + keyboard_controller_led (led_status); + +#ifdef DEBUG_AT_KEYBOARD + grub_dprintf ("atkeyb", "num_lock = %d\n", !!(at_keyboard_status & KEYBOARD_STATUS_NUM_LOCK)); +#endif + return GRUB_TERM_NO_KEY; + case GRUB_KEYBOARD_KEY_SCROLL_LOCK: + at_keyboard_status ^= GRUB_TERM_STATUS_SCROLL; + led_status ^= KEYBOARD_LED_SCROLL; + keyboard_controller_led (led_status); + return GRUB_TERM_NO_KEY; + default: + return grub_term_map_key (code, at_keyboard_status); + } +} + +static grub_err_t +grub_keyboard_controller_init (struct grub_term_input *term __attribute__ ((unused))) +{ + at_keyboard_status = 0; + /* Drain input buffer. */ + while (1) + { + keyboard_controller_wait_until_ready (); + if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS))) + break; + keyboard_controller_wait_until_ready (); + grub_inb (KEYBOARD_REG_DATA); + } +#if defined (GRUB_MACHINE_MIPS_YEELOONG) || defined (GRUB_MACHINE_QEMU) + grub_keyboard_controller_orig = 0; + grub_keyboard_orig_set = 2; +#else + grub_keyboard_controller_orig = grub_keyboard_controller_read (); + grub_keyboard_orig_set = query_mode (); +#endif + set_scancodes (); + keyboard_controller_led (led_status); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_keyboard_controller_fini (struct grub_term_input *term __attribute__ ((unused))) +{ + if (grub_keyboard_orig_set) + write_mode (grub_keyboard_orig_set); + grub_keyboard_controller_write (grub_keyboard_controller_orig); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_at_fini_hw (int noreturn __attribute__ ((unused))) +{ + return grub_keyboard_controller_fini (NULL); +} + +static grub_err_t +grub_at_restore_hw (void) +{ + /* Drain input buffer. */ + while (1) + { + keyboard_controller_wait_until_ready (); + if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS))) + break; + keyboard_controller_wait_until_ready (); + grub_inb (KEYBOARD_REG_DATA); + } + set_scancodes (); + keyboard_controller_led (led_status); + + return GRUB_ERR_NONE; +} + + +static struct grub_term_input grub_at_keyboard_term = + { + .name = "at_keyboard", + .init = grub_keyboard_controller_init, + .fini = grub_keyboard_controller_fini, + .getkey = grub_at_keyboard_getkey + }; + +GRUB_MOD_INIT(at_keyboard) +{ + grub_term_register_input ("at_keyboard", &grub_at_keyboard_term); + grub_loader_register_preboot_hook (grub_at_fini_hw, grub_at_restore_hw, + GRUB_LOADER_PREBOOT_HOOK_PRIO_CONSOLE); +} + +GRUB_MOD_FINI(at_keyboard) +{ + grub_keyboard_controller_fini (NULL); + grub_term_unregister_input (&grub_at_keyboard_term); +} diff --git a/grub-core/term/efi/console.c b/grub-core/term/efi/console.c new file mode 100644 index 0000000..8fd89b0 --- /dev/null +++ b/grub-core/term/efi/console.c @@ -0,0 +1,291 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +static grub_uint32_t +map_char (grub_uint32_t c) +{ + /* Map some unicode characters to the EFI character. */ + switch (c) + { + case GRUB_UNICODE_LEFTARROW: + c = GRUB_UNICODE_BLACK_LEFT_TRIANGLE; + break; + case GRUB_UNICODE_UPARROW: + c = GRUB_UNICODE_BLACK_UP_TRIANGLE; + break; + case GRUB_UNICODE_RIGHTARROW: + c = GRUB_UNICODE_BLACK_RIGHT_TRIANGLE; + break; + case GRUB_UNICODE_DOWNARROW: + c = GRUB_UNICODE_BLACK_DOWN_TRIANGLE; + break; + case GRUB_UNICODE_HLINE: + c = GRUB_UNICODE_LIGHT_HLINE; + break; + case GRUB_UNICODE_VLINE: + c = GRUB_UNICODE_LIGHT_VLINE; + break; + case GRUB_UNICODE_CORNER_UL: + c = GRUB_UNICODE_LIGHT_CORNER_UL; + break; + case GRUB_UNICODE_CORNER_UR: + c = GRUB_UNICODE_LIGHT_CORNER_UR; + break; + case GRUB_UNICODE_CORNER_LL: + c = GRUB_UNICODE_LIGHT_CORNER_LL; + break; + case GRUB_UNICODE_CORNER_LR: + c = GRUB_UNICODE_LIGHT_CORNER_LR; + break; + } + + return c; +} + +static void +grub_console_putchar (struct grub_term_output *term __attribute__ ((unused)), + const struct grub_unicode_glyph *c) +{ + grub_efi_char16_t str[2 + c->ncomb]; + grub_efi_simple_text_output_interface_t *o; + unsigned i, j; + + if (grub_efi_is_finished) + return; + + o = grub_efi_system_table->con_out; + + /* For now, do not try to use a surrogate pair. */ + if (c->base > 0xffff) + str[0] = '?'; + else + str[0] = (grub_efi_char16_t) map_char (c->base & 0xffff); + j = 1; + for (i = 0; i < c->ncomb; i++) + if (c->base < 0xffff) + str[j++] = c->combining[i].code; + str[j] = 0; + + /* Should this test be cached? */ + if ((c->base > 0x7f || c->ncomb) + && efi_call_2 (o->test_string, o, str) != GRUB_EFI_SUCCESS) + return; + + efi_call_2 (o->output_string, o, str); +} + +const unsigned efi_codes[] = + { + 0, GRUB_TERM_KEY_UP, GRUB_TERM_KEY_DOWN, GRUB_TERM_KEY_RIGHT, + GRUB_TERM_KEY_LEFT, GRUB_TERM_KEY_HOME, GRUB_TERM_KEY_END, GRUB_TERM_KEY_INSERT, + GRUB_TERM_KEY_DC, GRUB_TERM_KEY_PPAGE, GRUB_TERM_KEY_NPAGE, GRUB_TERM_KEY_F1, + GRUB_TERM_KEY_F2, GRUB_TERM_KEY_F3, GRUB_TERM_KEY_F4, GRUB_TERM_KEY_F5, + GRUB_TERM_KEY_F6, GRUB_TERM_KEY_F7, GRUB_TERM_KEY_F8, GRUB_TERM_KEY_F9, + GRUB_TERM_KEY_F10, 0, 0, '\e' + }; + + +static int +grub_console_getkey (struct grub_term_input *term __attribute__ ((unused))) +{ + grub_efi_simple_input_interface_t *i; + grub_efi_input_key_t key; + grub_efi_status_t status; + + if (grub_efi_is_finished) + return 0; + + i = grub_efi_system_table->con_in; + status = efi_call_2 (i->read_key_stroke, i, &key); + + if (status != GRUB_EFI_SUCCESS) + return GRUB_TERM_NO_KEY; + + if (key.scan_code == 0) + return key.unicode_char; + else if (key.scan_code < ARRAY_SIZE (efi_codes)) + return efi_codes[key.scan_code]; + + return GRUB_TERM_NO_KEY; +} + +static grub_uint16_t +grub_console_getwh (struct grub_term_output *term __attribute__ ((unused))) +{ + grub_efi_simple_text_output_interface_t *o; + grub_efi_uintn_t columns, rows; + + o = grub_efi_system_table->con_out; + if (grub_efi_is_finished || efi_call_4 (o->query_mode, o, o->mode->mode, + &columns, &rows) != GRUB_EFI_SUCCESS) + { + /* Why does this fail? */ + columns = 80; + rows = 25; + } + + return ((columns << 8) | rows); +} + +static grub_uint16_t +grub_console_getxy (struct grub_term_output *term __attribute__ ((unused))) +{ + grub_efi_simple_text_output_interface_t *o; + + if (grub_efi_is_finished) + return 0; + + o = grub_efi_system_table->con_out; + return ((o->mode->cursor_column << 8) | o->mode->cursor_row); +} + +static void +grub_console_gotoxy (struct grub_term_output *term __attribute__ ((unused)), + grub_uint8_t x, grub_uint8_t y) +{ + grub_efi_simple_text_output_interface_t *o; + + if (grub_efi_is_finished) + return; + + o = grub_efi_system_table->con_out; + efi_call_3 (o->set_cursor_position, o, x, y); +} + +static void +grub_console_cls (struct grub_term_output *term __attribute__ ((unused))) +{ + grub_efi_simple_text_output_interface_t *o; + grub_efi_int32_t orig_attr; + + if (grub_efi_is_finished) + return; + + o = grub_efi_system_table->con_out; + orig_attr = o->mode->attribute; + efi_call_2 (o->set_attributes, o, GRUB_EFI_BACKGROUND_BLACK); + efi_call_1 (o->clear_screen, o); + efi_call_2 (o->set_attributes, o, orig_attr); +} + +static void +grub_console_setcolorstate (struct grub_term_output *term, + grub_term_color_state state) +{ + grub_efi_simple_text_output_interface_t *o; + + if (grub_efi_is_finished) + return; + + o = grub_efi_system_table->con_out; + + switch (state) { + case GRUB_TERM_COLOR_STANDARD: + efi_call_2 (o->set_attributes, o, GRUB_TERM_DEFAULT_STANDARD_COLOR + & 0x7f); + break; + case GRUB_TERM_COLOR_NORMAL: + efi_call_2 (o->set_attributes, o, term->normal_color & 0x7f); + break; + case GRUB_TERM_COLOR_HIGHLIGHT: + efi_call_2 (o->set_attributes, o, term->highlight_color & 0x7f); + break; + default: + break; + } +} + +static void +grub_console_setcursor (struct grub_term_output *term __attribute__ ((unused)), + int on) +{ + grub_efi_simple_text_output_interface_t *o; + + if (grub_efi_is_finished) + return; + + o = grub_efi_system_table->con_out; + efi_call_2 (o->enable_cursor, o, on); +} + +static grub_err_t +grub_efi_console_init (struct grub_term_output *term) +{ + grub_console_setcursor (term, 1); + return 0; +} + +static grub_err_t +grub_efi_console_fini (struct grub_term_output *term) +{ + grub_console_setcursor (term, 0); + return 0; +} + +static struct grub_term_input grub_console_term_input = + { + .name = "console", + .getkey = grub_console_getkey, + }; + +static struct grub_term_output grub_console_term_output = + { + .name = "console", + .init = grub_efi_console_init, + .fini = grub_efi_console_fini, + .putchar = grub_console_putchar, + .getwh = grub_console_getwh, + .getxy = grub_console_getxy, + .gotoxy = grub_console_gotoxy, + .cls = grub_console_cls, + .setcolorstate = grub_console_setcolorstate, + .setcursor = grub_console_setcursor, + .normal_color = GRUB_TERM_DEFAULT_NORMAL_COLOR, + .highlight_color = GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR, + .flags = GRUB_TERM_CODE_TYPE_VISUAL_GLYPHS + }; + +void +grub_console_init (void) +{ + /* FIXME: it is necessary to consider the case where no console control + is present but the default is already in text mode. */ + if (! grub_efi_set_text_mode (1)) + { + grub_error (GRUB_ERR_BAD_DEVICE, "cannot set text mode"); + return; + } + + grub_term_register_input ("console", &grub_console_term_input); + grub_term_register_output ("console", &grub_console_term_output); +} + +void +grub_console_fini (void) +{ + grub_term_unregister_input (&grub_console_term_input); + grub_term_unregister_output (&grub_console_term_output); +} diff --git a/grub-core/term/gfxterm.c b/grub-core/term/gfxterm.c new file mode 100644 index 0000000..e58d672 --- /dev/null +++ b/grub-core/term/gfxterm.c @@ -0,0 +1,1220 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define DEFAULT_VIDEO_MODE "auto" +#define DEFAULT_BORDER_WIDTH 10 + +#define DEFAULT_STANDARD_COLOR 0x07 + +struct grub_dirty_region +{ + int top_left_x; + int top_left_y; + int bottom_right_x; + int bottom_right_y; +}; + +struct grub_colored_char +{ + /* An Unicode codepoint. */ + struct grub_unicode_glyph *code; + + /* Color values. */ + grub_video_color_t fg_color; + grub_video_color_t bg_color; + + /* The width of this character minus one. */ + unsigned char width; + + /* The column index of this character. */ + unsigned char index; +}; + +struct grub_virtual_screen +{ + /* Dimensions of the virtual screen in pixels. */ + unsigned int width; + unsigned int height; + + /* Offset in the display in pixels. */ + unsigned int offset_x; + unsigned int offset_y; + + /* TTY Character sizes in pixes. */ + unsigned int normal_char_width; + unsigned int normal_char_height; + + /* Virtual screen TTY size in characters. */ + unsigned int columns; + unsigned int rows; + + /* Current cursor location in characters. */ + unsigned int cursor_x; + unsigned int cursor_y; + + /* Current cursor state. */ + int cursor_state; + + /* Font settings. */ + grub_font_t font; + + /* Terminal color settings. */ + grub_uint8_t standard_color_setting; + grub_uint8_t term_color; + + /* Color settings. */ + grub_video_color_t fg_color; + grub_video_color_t bg_color; + grub_video_color_t bg_color_display; + + /* Text buffer for virtual screen. Contains (columns * rows) number + of entries. */ + struct grub_colored_char *text_buffer; + + int total_scroll; +}; + +struct grub_gfxterm_window +{ + unsigned x; + unsigned y; + unsigned width; + unsigned height; + int double_repaint; +}; + +static struct grub_video_render_target *render_target; +void (*grub_gfxterm_decorator_hook) (void) = NULL; +static struct grub_gfxterm_window window; +static struct grub_virtual_screen virtual_screen; +static grub_gfxterm_repaint_callback_t repaint_callback; +static int repaint_scheduled = 0; +static int repaint_was_scheduled = 0; + +static void destroy_window (void); + +static struct grub_video_render_target *text_layer; + +static unsigned int bitmap_width; +static unsigned int bitmap_height; +static struct grub_video_bitmap *bitmap; + +static struct grub_dirty_region dirty_region; + +static void dirty_region_reset (void); + +static int dirty_region_is_empty (void); + +static void dirty_region_add (int x, int y, + unsigned int width, unsigned int height); + +static unsigned int calculate_normal_character_width (grub_font_t font); + +static unsigned char calculate_character_width (struct grub_font_glyph *glyph); + +static void grub_gfxterm_refresh (struct grub_term_output *term __attribute__ ((unused))); + +static grub_ssize_t +grub_gfxterm_getcharwidth (struct grub_term_output *term __attribute__ ((unused)), + const struct grub_unicode_glyph *c); + +static void +set_term_color (grub_uint8_t term_color) +{ + struct grub_video_render_target *old_target; + + /* Save previous target and switch to text layer. */ + grub_video_get_active_render_target (&old_target); + grub_video_set_active_render_target (text_layer); + + /* Map terminal color to text layer compatible video colors. */ + virtual_screen.fg_color = grub_video_map_color(term_color & 0x0f); + + /* Special case: use black as transparent color. */ + if (((term_color >> 4) & 0x0f) == 0) + { + virtual_screen.bg_color = grub_video_map_rgba(0, 0, 0, 0); + } + else + { + virtual_screen.bg_color = grub_video_map_color((term_color >> 4) & 0x0f); + } + + /* Restore previous target. */ + grub_video_set_active_render_target (old_target); +} + +static void +clear_char (struct grub_colored_char *c) +{ + grub_free (c->code); + c->code = grub_unicode_glyph_from_code (' '); + if (!c->code) + grub_errno = GRUB_ERR_NONE; + c->fg_color = virtual_screen.fg_color; + c->bg_color = virtual_screen.bg_color; + c->width = 0; + c->index = 0; +} + +static void +grub_virtual_screen_free (void) +{ + /* If virtual screen has been allocated, free it. */ + if (virtual_screen.text_buffer != 0) + grub_free (virtual_screen.text_buffer); + + /* Reset virtual screen data. */ + grub_memset (&virtual_screen, 0, sizeof (virtual_screen)); + + /* Free render targets. */ + grub_video_delete_render_target (text_layer); + text_layer = 0; +} + +static grub_err_t +grub_virtual_screen_setup (unsigned int x, unsigned int y, + unsigned int width, unsigned int height, + const char *font_name) +{ + unsigned int i; + + /* Free old virtual screen. */ + grub_virtual_screen_free (); + + /* Initialize with default data. */ + virtual_screen.font = grub_font_get (font_name); + if (!virtual_screen.font) + return grub_error (GRUB_ERR_BAD_FONT, + "no font loaded"); + virtual_screen.width = width; + virtual_screen.height = height; + virtual_screen.offset_x = x; + virtual_screen.offset_y = y; + virtual_screen.normal_char_width = + calculate_normal_character_width (virtual_screen.font); + virtual_screen.normal_char_height = + grub_font_get_max_char_height (virtual_screen.font); + virtual_screen.cursor_x = 0; + virtual_screen.cursor_y = 0; + virtual_screen.cursor_state = 1; + virtual_screen.total_scroll = 0; + + /* Calculate size of text buffer. */ + virtual_screen.columns = virtual_screen.width / virtual_screen.normal_char_width; + virtual_screen.rows = virtual_screen.height / virtual_screen.normal_char_height; + + /* Allocate memory for text buffer. */ + virtual_screen.text_buffer = + (struct grub_colored_char *) grub_malloc (virtual_screen.columns + * virtual_screen.rows + * sizeof (*virtual_screen.text_buffer)); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + + /* Create new render target for text layer. */ + grub_video_create_render_target (&text_layer, + virtual_screen.width, + virtual_screen.height, + GRUB_VIDEO_MODE_TYPE_RGB + | GRUB_VIDEO_MODE_TYPE_ALPHA); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + + /* As we want to have colors compatible with rendering target, + we can only have those after mode is initialized. */ + grub_video_set_active_render_target (text_layer); + + virtual_screen.standard_color_setting = DEFAULT_STANDARD_COLOR; + + virtual_screen.term_color = GRUB_TERM_DEFAULT_NORMAL_COLOR; + + set_term_color (virtual_screen.term_color); + + grub_video_set_active_render_target (render_target); + + virtual_screen.bg_color_display = grub_video_map_rgba(0, 0, 0, 0); + + /* Clear out text buffer. */ + for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++) + { + virtual_screen.text_buffer[i].code = 0; + clear_char (&(virtual_screen.text_buffer[i])); + } + + return grub_errno; +} + +void +grub_gfxterm_schedule_repaint (void) +{ + repaint_scheduled = 1; +} + +grub_err_t +grub_gfxterm_set_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + int double_repaint, + const char *font_name, int border_width) +{ + /* Clean up any prior instance. */ + destroy_window (); + + /* Set the render target. */ + render_target = target; + + /* Create virtual screen. */ + if (grub_virtual_screen_setup (border_width, border_width, + width - 2 * border_width, + height - 2 * border_width, + font_name) + != GRUB_ERR_NONE) + { + return grub_errno; + } + + /* Set window bounds. */ + window.x = x; + window.y = y; + window.width = width; + window.height = height; + window.double_repaint = double_repaint; + + dirty_region_reset (); + grub_gfxterm_schedule_repaint (); + + return grub_errno; +} + +grub_err_t +grub_gfxterm_fullscreen (void) +{ + const char *font_name; + struct grub_video_mode_info mode_info; + grub_video_color_t color; + grub_err_t err; + int double_redraw; + + err = grub_video_get_info (&mode_info); + /* Figure out what mode we ended up. */ + if (err) + return err; + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + double_redraw = mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED + && !(mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_UPDATING_SWAP); + + /* Make sure screen is black. */ + color = grub_video_map_rgb (0, 0, 0); + grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height); + if (double_redraw) + { + grub_video_swap_buffers (); + grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height); + } + + /* Select the font to use. */ + font_name = grub_env_get ("gfxterm_font"); + if (! font_name) + font_name = ""; /* Allow fallback to any font. */ + + grub_gfxterm_decorator_hook = NULL; + + return grub_gfxterm_set_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY, + 0, 0, mode_info.width, mode_info.height, + double_redraw, + font_name, DEFAULT_BORDER_WIDTH); +} + +static grub_err_t +grub_gfxterm_term_init (struct grub_term_output *term __attribute__ ((unused))) +{ + char *tmp; + grub_err_t err; + const char *modevar; + + /* Parse gfxmode environment variable if set. */ + modevar = grub_env_get ("gfxmode"); + if (! modevar || *modevar == 0) + err = grub_video_set_mode (DEFAULT_VIDEO_MODE, + GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0); + else + { + tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar); + if (!tmp) + return grub_errno; + err = grub_video_set_mode (tmp, GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0); + grub_free (tmp); + } + + if (err) + return err; + + err = grub_gfxterm_fullscreen (); + if (err) + grub_video_restore (); + + return err; +} + +static void +destroy_window (void) +{ + repaint_callback = 0; + grub_virtual_screen_free (); +} + +static grub_err_t +grub_gfxterm_term_fini (struct grub_term_output *term __attribute__ ((unused))) +{ + unsigned i; + destroy_window (); + grub_video_restore (); + + for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++) + { + grub_free (virtual_screen.text_buffer[i].code); + virtual_screen.text_buffer[i].code = 0; + } + + /* Clear error state. */ + grub_errno = GRUB_ERR_NONE; + return GRUB_ERR_NONE; +} + +static void +redraw_screen_rect (unsigned int x, unsigned int y, + unsigned int width, unsigned int height) +{ + grub_video_color_t color; + grub_video_rect_t saved_view; + + grub_video_set_active_render_target (render_target); + /* Save viewport and set it to our window. */ + grub_video_get_viewport ((unsigned *) &saved_view.x, + (unsigned *) &saved_view.y, + (unsigned *) &saved_view.width, + (unsigned *) &saved_view.height); + grub_video_set_viewport (window.x, window.y, window.width, window.height); + + if (bitmap) + { + /* Render bitmap as background. */ + grub_video_blit_bitmap (bitmap, GRUB_VIDEO_BLIT_REPLACE, x, y, + x, y, + width, height); + + /* If bitmap is smaller than requested blit area, use background + color. */ + color = virtual_screen.bg_color_display; + + /* Fill right side of the bitmap if needed. */ + if ((x + width >= bitmap_width) && (y < bitmap_height)) + { + int w = (x + width) - bitmap_width; + int h = height; + unsigned int tx = x; + + if (y + height >= bitmap_height) + { + h = bitmap_height - y; + } + + if (bitmap_width > tx) + { + tx = bitmap_width; + } + + /* Render background layer. */ + grub_video_fill_rect (color, tx, y, w, h); + } + + /* Fill bottom side of the bitmap if needed. */ + if (y + height >= bitmap_height) + { + int h = (y + height) - bitmap_height; + unsigned int ty = y; + + if (bitmap_height > ty) + { + ty = bitmap_height; + } + + /* Render background layer. */ + grub_video_fill_rect (color, x, ty, width, h); + } + + /* Render text layer as blended. */ + grub_video_blit_render_target (text_layer, GRUB_VIDEO_BLIT_BLEND, x, y, + x - virtual_screen.offset_x, + y - virtual_screen.offset_y, + width, height); + } + else + { + /* Render background layer. */ + color = virtual_screen.bg_color_display; + grub_video_fill_rect (color, x, y, width, height); + + /* Render text layer as replaced (to get texts background color). */ + grub_video_blit_render_target (text_layer, GRUB_VIDEO_BLIT_REPLACE, x, y, + x - virtual_screen.offset_x, + y - virtual_screen.offset_y, + width, height); + } + + /* Restore saved viewport. */ + grub_video_set_viewport (saved_view.x, saved_view.y, + saved_view.width, saved_view.height); + grub_video_set_active_render_target (render_target); + + if (repaint_callback) + repaint_callback (x, y, width, height); +} + +static void +dirty_region_reset (void) +{ + dirty_region.top_left_x = -1; + dirty_region.top_left_y = -1; + dirty_region.bottom_right_x = -1; + dirty_region.bottom_right_y = -1; + repaint_was_scheduled = 0; +} + +static int +dirty_region_is_empty (void) +{ + if ((dirty_region.top_left_x == -1) + || (dirty_region.top_left_y == -1) + || (dirty_region.bottom_right_x == -1) + || (dirty_region.bottom_right_y == -1)) + return 1; + return 0; +} + +static void +dirty_region_add_real (int x, int y, unsigned int width, unsigned int height) +{ + if (dirty_region_is_empty ()) + { + dirty_region.top_left_x = x; + dirty_region.top_left_y = y; + dirty_region.bottom_right_x = x + width - 1; + dirty_region.bottom_right_y = y + height - 1; + } + else + { + if (x < dirty_region.top_left_x) + dirty_region.top_left_x = x; + if (y < dirty_region.top_left_y) + dirty_region.top_left_y = y; + if ((x + (int)width - 1) > dirty_region.bottom_right_x) + dirty_region.bottom_right_x = x + width - 1; + if ((y + (int)height - 1) > dirty_region.bottom_right_y) + dirty_region.bottom_right_y = y + height - 1; + } +} + +static void +dirty_region_add (int x, int y, unsigned int width, unsigned int height) +{ + if ((width == 0) || (height == 0)) + return; + + if (repaint_scheduled) + { + dirty_region_add_real (virtual_screen.offset_x, virtual_screen.offset_y, + virtual_screen.width, virtual_screen.height); + repaint_scheduled = 0; + repaint_was_scheduled = 1; + } + dirty_region_add_real (x, y, width, height); +} + +static void +dirty_region_add_virtualscreen (void) +{ + /* Mark virtual screen as dirty. */ + dirty_region_add (virtual_screen.offset_x, virtual_screen.offset_y, + virtual_screen.width, virtual_screen.height); +} + + +static void +dirty_region_redraw (void) +{ + int x; + int y; + int width; + int height; + + if (dirty_region_is_empty ()) + return; + + x = dirty_region.top_left_x; + y = dirty_region.top_left_y; + + width = dirty_region.bottom_right_x - x + 1; + height = dirty_region.bottom_right_y - y + 1; + + if (repaint_was_scheduled && grub_gfxterm_decorator_hook) + grub_gfxterm_decorator_hook (); + + redraw_screen_rect (x, y, width, height); +} + +static inline void +paint_char (unsigned cx, unsigned cy) +{ + struct grub_colored_char *p; + struct grub_font_glyph *glyph; + grub_video_color_t color; + grub_video_color_t bgcolor; + unsigned int x; + unsigned int y; + int ascent; + unsigned int height; + unsigned int width; + + if (cy + virtual_screen.total_scroll >= virtual_screen.rows) + return; + + /* Find out active character. */ + p = (virtual_screen.text_buffer + + cx + (cy * virtual_screen.columns)); + + p -= p->index; + + /* Get glyph for character. */ + glyph = grub_font_construct_glyph (virtual_screen.font, p->code); + if (!glyph) + { + grub_errno = GRUB_ERR_NONE; + return; + } + ascent = grub_font_get_ascent (virtual_screen.font); + + width = virtual_screen.normal_char_width * calculate_character_width(glyph); + height = virtual_screen.normal_char_height; + + color = p->fg_color; + bgcolor = p->bg_color; + + x = cx * virtual_screen.normal_char_width; + y = (cy + virtual_screen.total_scroll) * virtual_screen.normal_char_height; + + /* Render glyph to text layer. */ + grub_video_set_active_render_target (text_layer); + grub_video_fill_rect (bgcolor, x, y, width, height); + grub_font_draw_glyph (glyph, color, x, y + ascent); + grub_video_set_active_render_target (render_target); + + /* Mark character to be drawn. */ + dirty_region_add (virtual_screen.offset_x + x, virtual_screen.offset_y + y, + width, height); + grub_free (glyph); +} + +static inline void +write_char (void) +{ + paint_char (virtual_screen.cursor_x, virtual_screen.cursor_y); +} + +static inline void +draw_cursor (int show) +{ + unsigned int x; + unsigned int y; + unsigned int width; + unsigned int height; + grub_video_color_t color; + + write_char (); + + if (!show) + return; + + if (virtual_screen.cursor_y + virtual_screen.total_scroll + >= virtual_screen.rows) + return; + + /* Determine cursor properties and position on text layer. */ + x = virtual_screen.cursor_x * virtual_screen.normal_char_width; + width = virtual_screen.normal_char_width; + color = virtual_screen.fg_color; + y = ((virtual_screen.cursor_y + virtual_screen.total_scroll) + * virtual_screen.normal_char_height + + grub_font_get_ascent (virtual_screen.font)); + height = 2; + + /* Render cursor to text layer. */ + grub_video_set_active_render_target (text_layer); + grub_video_fill_rect (color, x, y, width, height); + grub_video_set_active_render_target (render_target); + + /* Mark cursor to be redrawn. */ + dirty_region_add (virtual_screen.offset_x + x, + virtual_screen.offset_y + y, + width, height); +} + +static void +real_scroll (void) +{ + unsigned int i, j, was_scroll; + grub_video_color_t color; + + if (!virtual_screen.total_scroll) + return; + + /* If we have bitmap, re-draw screen, otherwise scroll physical screen too. */ + if (bitmap) + { + /* Scroll physical screen. */ + grub_video_set_active_render_target (text_layer); + color = virtual_screen.bg_color; + grub_video_scroll (color, 0, -virtual_screen.normal_char_height + * virtual_screen.total_scroll); + + /* Mark virtual screen to be redrawn. */ + dirty_region_add_virtualscreen (); + } + else + { + grub_video_rect_t saved_view; + + /* Remove cursor. */ + draw_cursor (0); + + grub_video_set_active_render_target (render_target); + + i = window.double_repaint ? 2 : 1; + + color = virtual_screen.bg_color; + + while (i--) + { + /* Save viewport and set it to our window. */ + grub_video_get_viewport ((unsigned *) &saved_view.x, + (unsigned *) &saved_view.y, + (unsigned *) &saved_view.width, + (unsigned *) &saved_view.height); + + grub_video_set_viewport (window.x, window.y, window.width, + window.height); + + /* Clear new border area. */ + grub_video_fill_rect (color, + virtual_screen.offset_x, + virtual_screen.offset_y, + virtual_screen.width, + virtual_screen.normal_char_height + * virtual_screen.total_scroll); + + grub_video_set_active_render_target (render_target); + dirty_region_redraw (); + + /* Scroll physical screen. */ + grub_video_scroll (color, 0, -virtual_screen.normal_char_height + * virtual_screen.total_scroll); + + /* Restore saved viewport. */ + grub_video_set_viewport (saved_view.x, saved_view.y, + saved_view.width, saved_view.height); + + if (i) + grub_video_swap_buffers (); + } + dirty_region_reset (); + + /* Scroll physical screen. */ + grub_video_set_active_render_target (text_layer); + color = virtual_screen.bg_color; + grub_video_scroll (color, 0, -virtual_screen.normal_char_height + * virtual_screen.total_scroll); + + grub_video_set_active_render_target (render_target); + + } + + was_scroll = virtual_screen.total_scroll; + virtual_screen.total_scroll = 0; + + if (was_scroll > virtual_screen.rows) + was_scroll = virtual_screen.rows; + + /* Draw shadow part. */ + for (i = virtual_screen.rows - was_scroll; + i < virtual_screen.rows; i++) + for (j = 0; j < virtual_screen.columns; j++) + paint_char (j, i); + + /* Draw cursor if visible. */ + if (virtual_screen.cursor_state) + draw_cursor (1); + + if (repaint_callback) + repaint_callback (window.x, window.y, window.width, window.height); +} + +static void +scroll_up (void) +{ + unsigned int i; + + /* Clear first line in text buffer. */ + for (i = 0; i < virtual_screen.columns; i++) + grub_free (virtual_screen.text_buffer[i].code); + + /* Scroll text buffer with one line to up. */ + grub_memmove (virtual_screen.text_buffer, + virtual_screen.text_buffer + virtual_screen.columns, + sizeof (*virtual_screen.text_buffer) + * virtual_screen.columns + * (virtual_screen.rows - 1)); + + /* Clear last line in text buffer. */ + for (i = virtual_screen.columns * (virtual_screen.rows - 1); + i < virtual_screen.columns * virtual_screen.rows; + i++) + { + virtual_screen.text_buffer[i].code = 0; + clear_char (&(virtual_screen.text_buffer[i])); + } + + virtual_screen.total_scroll++; +} + +static void +grub_gfxterm_putchar (struct grub_term_output *term, + const struct grub_unicode_glyph *c) +{ + if (c->base == '\a') + /* FIXME */ + return; + + /* Erase current cursor, if any. */ + if (virtual_screen.cursor_state) + draw_cursor (0); + + if (c->base == '\b' || c->base == '\n' || c->base == '\r') + { + switch (c->base) + { + case '\b': + if (virtual_screen.cursor_x > 0) + virtual_screen.cursor_x--; + break; + + case '\n': + if (virtual_screen.cursor_y >= virtual_screen.rows - 1) + scroll_up (); + else + virtual_screen.cursor_y++; + break; + + case '\r': + virtual_screen.cursor_x = 0; + break; + } + } + else + { + struct grub_colored_char *p; + unsigned char char_width; + + /* Calculate actual character width for glyph. This is number of + times of normal_font_width. */ + char_width = grub_gfxterm_getcharwidth (term, c); + + /* If we are about to exceed line length, wrap to next line. */ + if (virtual_screen.cursor_x + char_width > virtual_screen.columns) + { + if (virtual_screen.cursor_y >= virtual_screen.rows - 1) + scroll_up (); + else + virtual_screen.cursor_y++; + } + + /* Find position on virtual screen, and fill information. */ + p = (virtual_screen.text_buffer + + virtual_screen.cursor_x + + virtual_screen.cursor_y * virtual_screen.columns); + grub_free (p->code); + p->code = grub_unicode_glyph_dup (c); + if (!p->code) + grub_errno = GRUB_ERR_NONE; + p->fg_color = virtual_screen.fg_color; + p->bg_color = virtual_screen.bg_color; + p->width = char_width - 1; + p->index = 0; + + /* If we have large glyph, add fixup info. */ + if (char_width > 1) + { + unsigned i; + + for (i = 1; i < char_width; i++) + { + grub_free (p[i].code); + p[i].code = grub_unicode_glyph_from_code (' '); + if (!p[i].code) + grub_errno = GRUB_ERR_NONE; + p[i].width = char_width - 1; + p[i].index = i; + } + } + + /* Draw glyph. */ + write_char (); + + /* Make sure we scroll screen when needed and wrap line correctly. */ + virtual_screen.cursor_x += char_width; + if (virtual_screen.cursor_x >= virtual_screen.columns) + { + virtual_screen.cursor_x = 0; + + if (virtual_screen.cursor_y >= virtual_screen.rows - 1) + scroll_up (); + else + virtual_screen.cursor_y++; + } + } + + /* Redraw cursor if it should be visible. */ + /* Note: This will redraw the character as well, which means that the + above call to write_char is redundant when the cursor is showing. */ + if (virtual_screen.cursor_state) + draw_cursor (1); +} + +/* Use ASCII characters to determine normal character width. */ +static unsigned int +calculate_normal_character_width (grub_font_t font) +{ + struct grub_font_glyph *glyph; + unsigned int width = 0; + unsigned int i; + + /* Get properties of every printable ASCII character. */ + for (i = 32; i < 127; i++) + { + glyph = grub_font_get_glyph (font, i); + + /* Skip unknown characters. Should never happen on normal conditions. */ + if (! glyph) + continue; + + if (glyph->device_width > width) + width = glyph->device_width; + } + if (!width) + return 8; + + return width; +} + +static unsigned char +calculate_character_width (struct grub_font_glyph *glyph) +{ + if (! glyph || glyph->device_width == 0) + return 1; + + return (glyph->device_width + + (virtual_screen.normal_char_width - 1)) + / virtual_screen.normal_char_width; +} + +static grub_ssize_t +grub_gfxterm_getcharwidth (struct grub_term_output *term __attribute__ ((unused)), + const struct grub_unicode_glyph *c) +{ + int dev_width; + dev_width = grub_font_get_constructed_device_width (virtual_screen.font, c); + + if (dev_width == 0) + return 1; + + return (dev_width + (virtual_screen.normal_char_width - 1)) + / virtual_screen.normal_char_width; +} + +static grub_uint16_t +grub_virtual_screen_getwh (struct grub_term_output *term __attribute__ ((unused))) +{ + return (virtual_screen.columns << 8) | virtual_screen.rows; +} + +static grub_uint16_t +grub_virtual_screen_getxy (struct grub_term_output *term __attribute__ ((unused))) +{ + return ((virtual_screen.cursor_x << 8) | virtual_screen.cursor_y); +} + +static void +grub_gfxterm_gotoxy (struct grub_term_output *term __attribute__ ((unused)), + grub_uint8_t x, grub_uint8_t y) +{ + if (x >= virtual_screen.columns) + x = virtual_screen.columns - 1; + + if (y >= virtual_screen.rows) + y = virtual_screen.rows - 1; + + /* Erase current cursor, if any. */ + if (virtual_screen.cursor_state) + draw_cursor (0); + + virtual_screen.cursor_x = x; + virtual_screen.cursor_y = y; + + /* Draw cursor if visible. */ + if (virtual_screen.cursor_state) + draw_cursor (1); +} + +static void +grub_virtual_screen_cls (struct grub_term_output *term __attribute__ ((unused))) +{ + grub_uint32_t i; + + for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++) + clear_char (&(virtual_screen.text_buffer[i])); + + virtual_screen.cursor_x = virtual_screen.cursor_y = 0; +} + +static void +grub_gfxterm_cls (struct grub_term_output *term) +{ + grub_video_color_t color; + + /* Clear virtual screen. */ + grub_virtual_screen_cls (term); + + /* Clear text layer. */ + grub_video_set_active_render_target (text_layer); + color = virtual_screen.bg_color; + grub_video_fill_rect (color, 0, 0, + virtual_screen.width, virtual_screen.height); + grub_video_set_active_render_target (render_target); + + /* Mark virtual screen to be redrawn. */ + dirty_region_add_virtualscreen (); + + grub_gfxterm_refresh (term); +} + +static void +grub_virtual_screen_setcolorstate (struct grub_term_output *term, + grub_term_color_state state) +{ + switch (state) + { + case GRUB_TERM_COLOR_STANDARD: + virtual_screen.term_color = virtual_screen.standard_color_setting; + break; + + case GRUB_TERM_COLOR_NORMAL: + virtual_screen.term_color = term->normal_color; + break; + + case GRUB_TERM_COLOR_HIGHLIGHT: + virtual_screen.term_color = term->highlight_color; + break; + + default: + break; + } + + /* Change color to virtual terminal. */ + set_term_color (virtual_screen.term_color); +} + +static void +grub_gfxterm_setcursor (struct grub_term_output *term __attribute__ ((unused)), + int on) +{ + if (virtual_screen.cursor_state != on) + { + if (virtual_screen.cursor_state) + draw_cursor (0); + else + draw_cursor (1); + + virtual_screen.cursor_state = on; + } +} + +static void +grub_gfxterm_refresh (struct grub_term_output *term __attribute__ ((unused))) +{ + real_scroll (); + + /* Redraw only changed regions. */ + dirty_region_redraw (); + + grub_video_swap_buffers (); + + if (window.double_repaint) + dirty_region_redraw (); + dirty_region_reset (); +} + +void +grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func) +{ + repaint_callback = func; +} + +/* Option array indices. */ +#define BACKGROUND_CMD_ARGINDEX_MODE 0 + +static const struct grub_arg_option background_image_cmd_options[] = + { + {"mode", 'm', 0, "Background image mode.", "stretch|normal", + ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +static grub_err_t +grub_gfxterm_background_image_cmd (grub_extcmd_context_t ctxt, + int argc, char **args) +{ + struct grub_arg_list *state = ctxt->state; + + /* Check that we have video adapter active. */ + if (grub_video_get_info(NULL) != GRUB_ERR_NONE) + return grub_errno; + + /* Destroy existing background bitmap if loaded. */ + if (bitmap) + { + grub_video_bitmap_destroy (bitmap); + bitmap = 0; + + /* Mark whole screen as dirty. */ + dirty_region_add (0, 0, window.width, window.height); + } + + /* If filename was provided, try to load that. */ + if (argc >= 1) + { + /* Try to load new one. */ + grub_video_bitmap_load (&bitmap, args[0]); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + + /* Determine if the bitmap should be scaled to fit the screen. */ + if (!state[BACKGROUND_CMD_ARGINDEX_MODE].set + || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg, + "stretch") == 0) + { + if (window.width != grub_video_bitmap_get_width (bitmap) + || window.height != grub_video_bitmap_get_height (bitmap)) + { + struct grub_video_bitmap *scaled_bitmap; + grub_video_bitmap_create_scaled (&scaled_bitmap, + window.width, + window.height, + bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + if (grub_errno == GRUB_ERR_NONE) + { + /* Replace the original bitmap with the scaled one. */ + grub_video_bitmap_destroy (bitmap); + bitmap = scaled_bitmap; + } + } + } + + /* If bitmap was loaded correctly, display it. */ + if (bitmap) + { + /* Determine bitmap dimensions. */ + bitmap_width = grub_video_bitmap_get_width (bitmap); + bitmap_height = grub_video_bitmap_get_height (bitmap); + + /* Mark whole screen as dirty. */ + dirty_region_add (0, 0, window.width, window.height); + } + } + + /* All was ok. */ + grub_errno = GRUB_ERR_NONE; + return grub_errno; +} + +static struct grub_term_output grub_video_term = + { + .name = "gfxterm", + .init = grub_gfxterm_term_init, + .fini = grub_gfxterm_term_fini, + .putchar = grub_gfxterm_putchar, + .getcharwidth = grub_gfxterm_getcharwidth, + .getwh = grub_virtual_screen_getwh, + .getxy = grub_virtual_screen_getxy, + .gotoxy = grub_gfxterm_gotoxy, + .cls = grub_gfxterm_cls, + .setcolorstate = grub_virtual_screen_setcolorstate, + .setcursor = grub_gfxterm_setcursor, + .refresh = grub_gfxterm_refresh, + .flags = GRUB_TERM_CODE_TYPE_VISUAL_GLYPHS, + .normal_color = GRUB_TERM_DEFAULT_NORMAL_COLOR, + .highlight_color = GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR, + .next = 0 + }; + +static grub_extcmd_t background_image_cmd_handle; + +GRUB_MOD_INIT(gfxterm) +{ + grub_term_register_output ("gfxterm", &grub_video_term); + background_image_cmd_handle = + grub_register_extcmd ("background_image", + grub_gfxterm_background_image_cmd, 0, + N_("[-m (stretch|normal)] FILE"), + N_("Load background image for active terminal."), + background_image_cmd_options); +} + +GRUB_MOD_FINI(gfxterm) +{ + grub_unregister_extcmd (background_image_cmd_handle); + grub_term_unregister_output (&grub_video_term); +} diff --git a/grub-core/term/i386/pc/console.c b/grub-core/term/i386/pc/console.c new file mode 100644 index 0000000..0efeafe --- /dev/null +++ b/grub-core/term/i386/pc/console.c @@ -0,0 +1,68 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include + +static const struct grub_machine_bios_data_area *bios_data_area = + (struct grub_machine_bios_data_area *) GRUB_MEMORY_MACHINE_BIOS_DATA_AREA_ADDR; + +static int +grub_console_getkeystatus (struct grub_term_input *term __attribute__ ((unused))) +{ + /* conveniently GRUB keystatus is modelled after BIOS one. */ + return bios_data_area->keyboard_flag_lower & ~0x80; +} + +static struct grub_term_input grub_console_term_input = + { + .name = "console", + .getkey = grub_console_getkey, + .getkeystatus = grub_console_getkeystatus + }; + +static struct grub_term_output grub_console_term_output = + { + .name = "console", + .putchar = grub_console_putchar, + .getwh = grub_console_getwh, + .getxy = grub_console_getxy, + .gotoxy = grub_console_gotoxy, + .cls = grub_console_cls, + .setcolorstate = grub_console_setcolorstate, + .setcursor = grub_console_setcursor, + .flags = GRUB_TERM_CODE_TYPE_CP437, + .normal_color = GRUB_TERM_DEFAULT_NORMAL_COLOR, + .highlight_color = GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR, + }; + +void +grub_console_init (void) +{ + grub_term_register_output ("console", &grub_console_term_output); + grub_term_register_input ("console", &grub_console_term_input); +} + +void +grub_console_fini (void) +{ + grub_term_unregister_input (&grub_console_term_input); + grub_term_unregister_output (&grub_console_term_output); +} diff --git a/grub-core/term/i386/pc/vga_text.c b/grub-core/term/i386/pc/vga_text.c new file mode 100644 index 0000000..1816bfa --- /dev/null +++ b/grub-core/term/i386/pc/vga_text.c @@ -0,0 +1,175 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007, 2008, 2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define COLS 80 +#define ROWS 25 + +static int grub_curr_x, grub_curr_y; + +#define VGA_TEXT_SCREEN ((grub_uint16_t *) 0xb8000) + +static void +screen_write_char (int x, int y, short c) +{ + VGA_TEXT_SCREEN[y * COLS + x] = c; +} + +static short +screen_read_char (int x, int y) +{ + return VGA_TEXT_SCREEN[y * COLS + x]; +} + +static void +update_cursor (void) +{ + unsigned int pos = grub_curr_y * COLS + grub_curr_x; + grub_vga_cr_write (pos >> 8, GRUB_VGA_CR_CURSOR_ADDR_HIGH); + grub_vga_cr_write (pos & 0xFF, GRUB_VGA_CR_CURSOR_ADDR_LOW); +} + +static void +inc_y (void) +{ + grub_curr_x = 0; + if (grub_curr_y < ROWS - 1) + grub_curr_y++; + else + { + int x, y; + for (y = 0; y < ROWS - 1; y++) + for (x = 0; x < COLS; x++) + screen_write_char (x, y, screen_read_char (x, y + 1)); + for (x = 0; x < COLS; x++) + screen_write_char (x, ROWS - 1, ' ' | (grub_console_cur_color << 8)); + } +} + +static void +inc_x (void) +{ + if (grub_curr_x >= COLS - 1) + inc_y (); + else + grub_curr_x++; +} + +static void +grub_vga_text_putchar (struct grub_term_output *term __attribute__ ((unused)), + const struct grub_unicode_glyph *c) +{ + switch (c->base) + { + case '\b': + if (grub_curr_x != 0) + screen_write_char (grub_curr_x--, grub_curr_y, ' '); + break; + case '\n': + inc_y (); + break; + case '\r': + grub_curr_x = 0; + break; + default: + screen_write_char (grub_curr_x, grub_curr_y, + c->base | (grub_console_cur_color << 8)); + inc_x (); + } + + update_cursor (); +} + +static grub_uint16_t +grub_vga_text_getxy (struct grub_term_output *term __attribute__ ((unused))) +{ + return (grub_curr_x << 8) | grub_curr_y; +} + +static void +grub_vga_text_gotoxy (struct grub_term_output *term __attribute__ ((unused)), + grub_uint8_t x, grub_uint8_t y) +{ + grub_curr_x = x; + grub_curr_y = y; + update_cursor (); +} + +static void +grub_vga_text_cls (struct grub_term_output *term) +{ + int i; + for (i = 0; i < ROWS * COLS; i++) + VGA_TEXT_SCREEN[i] = ' ' | (grub_console_cur_color << 8); + grub_vga_text_gotoxy (term, 0, 0); +} + +static void +grub_vga_text_setcursor (struct grub_term_output *term __attribute__ ((unused)), + int on) +{ + grub_uint8_t old; + old = grub_vga_cr_read (GRUB_VGA_CR_CURSOR_START); + if (on) + grub_vga_cr_write (old & ~GRUB_VGA_CR_CURSOR_START_DISABLE, + GRUB_VGA_CR_CURSOR_START); + else + grub_vga_cr_write (old | GRUB_VGA_CR_CURSOR_START_DISABLE, + GRUB_VGA_CR_CURSOR_START); +} + +static grub_err_t +grub_vga_text_init_fini (struct grub_term_output *term) +{ + grub_vga_text_cls (term); + return 0; +} + +static struct grub_term_output grub_vga_text_term = + { + .name = "vga_text", + .init = grub_vga_text_init_fini, + .fini = grub_vga_text_init_fini, + .putchar = grub_vga_text_putchar, + .getwh = grub_console_getwh, + .getxy = grub_vga_text_getxy, + .gotoxy = grub_vga_text_gotoxy, + .cls = grub_vga_text_cls, + .setcolorstate = grub_console_setcolorstate, + .setcursor = grub_vga_text_setcursor, + .flags = GRUB_TERM_CODE_TYPE_CP437, + .normal_color = GRUB_TERM_DEFAULT_NORMAL_COLOR, + .highlight_color = GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR, + }; + +GRUB_MOD_INIT(vga_text) +{ + grub_term_register_output ("vga_text", &grub_vga_text_term); +} + +GRUB_MOD_FINI(vga_text) +{ + grub_term_unregister_output (&grub_vga_text_term); +} diff --git a/grub-core/term/i386/vga_common.c b/grub-core/term/i386/vga_common.c new file mode 100644 index 0000000..0c21769 --- /dev/null +++ b/grub-core/term/i386/vga_common.c @@ -0,0 +1,48 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include + +grub_uint8_t grub_console_cur_color = 0x7; + +grub_uint16_t +grub_console_getwh (struct grub_term_output *term __attribute__ ((unused))) +{ + return (80 << 8) | 25; +} + +void +grub_console_setcolorstate (struct grub_term_output *term, + grub_term_color_state state) +{ + switch (state) { + case GRUB_TERM_COLOR_STANDARD: + grub_console_cur_color = GRUB_TERM_DEFAULT_STANDARD_COLOR & 0x7f; + break; + case GRUB_TERM_COLOR_NORMAL: + grub_console_cur_color = term->normal_color & 0x7f; + break; + case GRUB_TERM_COLOR_HIGHLIGHT: + grub_console_cur_color = term->highlight_color & 0x7f; + break; + default: + break; + } +} diff --git a/grub-core/term/ieee1275/ofconsole.c b/grub-core/term/ieee1275/ofconsole.c new file mode 100644 index 0000000..ab74f21 --- /dev/null +++ b/grub-core/term/ieee1275/ofconsole.c @@ -0,0 +1,249 @@ +/* ofconsole.c -- Open Firmware console for GRUB. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static grub_ieee1275_ihandle_t stdout_ihandle; +static grub_ieee1275_ihandle_t stdin_ihandle; + +extern struct grub_terminfo_output_state grub_ofconsole_terminfo_output; + +struct color +{ + int red; + int green; + int blue; +}; + +/* Use serial colors as they are default on most firmwares and some firmwares + ignore set-color!. Additionally output may be redirected to serial. */ +static struct color colors[] = + { + // {R, G, B} + {0x00, 0x00, 0x00}, // 0 = black + {0xA8, 0x00, 0x00}, // 1 = red + {0x00, 0xA8, 0x00}, // 2 = green + {0xFE, 0xFE, 0x54}, // 3 = yellow + {0x00, 0x00, 0xA8}, // 4 = blue + {0xA8, 0x00, 0xA8}, // 5 = magenta + {0x00, 0xA8, 0xA8}, // 6 = cyan + {0xFE, 0xFE, 0xFE} // 7 = white + }; + +static void +put (struct grub_term_output *term __attribute__ ((unused)), const int c) +{ + char chr = c; + + grub_ieee1275_write (stdout_ihandle, &chr, 1, 0); +} + +static int +readkey (struct grub_term_input *term __attribute__ ((unused))) +{ + grub_uint8_t c; + grub_ssize_t actual = 0; + + grub_ieee1275_read (stdin_ihandle, &c, 1, &actual); + if (actual > 0) + return c; + return -1; +} + +static void +grub_ofconsole_dimensions (void) +{ + grub_ieee1275_ihandle_t options; + grub_ssize_t lval; + + if (! grub_ieee1275_finddevice ("/options", &options) + && options != (grub_ieee1275_ihandle_t) -1) + { + if (! grub_ieee1275_get_property_length (options, "screen-#columns", + &lval) + && lval >= 0 && lval < 1024) + { + char val[lval]; + + if (! grub_ieee1275_get_property (options, "screen-#columns", + val, lval, 0)) + grub_ofconsole_terminfo_output.width + = (grub_uint8_t) grub_strtoul (val, 0, 10); + } + if (! grub_ieee1275_get_property_length (options, "screen-#rows", &lval) + && lval >= 0 && lval < 1024) + { + char val[lval]; + if (! grub_ieee1275_get_property (options, "screen-#rows", + val, lval, 0)) + grub_ofconsole_terminfo_output.height + = (grub_uint8_t) grub_strtoul (val, 0, 10); + } + } + + /* Use a small console by default. */ + if (! grub_ofconsole_terminfo_output.width) + grub_ofconsole_terminfo_output.width = 80; + if (! grub_ofconsole_terminfo_output.height) + grub_ofconsole_terminfo_output.height = 24; +} + +static void +grub_ofconsole_setcursor (struct grub_term_output *term, + int on) +{ + grub_terminfo_setcursor (term, on); + + if (!grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_HAS_CURSORONOFF)) + return; + + /* Understood by the Open Firmware flavour in OLPC. */ + if (on) + grub_ieee1275_interpret ("cursor-on", 0); + else + grub_ieee1275_interpret ("cursor-off", 0); +} + +static grub_err_t +grub_ofconsole_init_input (struct grub_term_input *term) +{ + grub_ssize_t actual; + + if (grub_ieee1275_get_integer_property (grub_ieee1275_chosen, "stdin", &stdin_ihandle, + sizeof stdin_ihandle, &actual) + || actual != sizeof stdin_ihandle) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "cannot find stdin"); + + return grub_terminfo_input_init (term); +} + +static grub_err_t +grub_ofconsole_init_output (struct grub_term_output *term) +{ + grub_ssize_t actual; + + /* The latest PowerMacs don't actually initialize the screen for us, so we + * use this trick to re-open the output device (but we avoid doing this on + * platforms where it's known to be broken). */ + if (! grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_BROKEN_OUTPUT)) + grub_ieee1275_interpret ("output-device output", 0); + + if (grub_ieee1275_get_integer_property (grub_ieee1275_chosen, "stdout", &stdout_ihandle, + sizeof stdout_ihandle, &actual) + || actual != sizeof stdout_ihandle) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "cannot find stdout"); + + /* Initialize colors. */ + if (! grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CANNOT_SET_COLORS)) + { + unsigned col; + for (col = 0; col < ARRAY_SIZE (colors); col++) + grub_ieee1275_set_color (stdout_ihandle, col, colors[col].red, + colors[col].green, colors[col].blue); + + /* Set the right fg and bg colors. */ + grub_terminfo_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + } + + grub_ofconsole_dimensions (); + + grub_terminfo_output_init (term); + + return 0; +} + + + +struct grub_terminfo_input_state grub_ofconsole_terminfo_input = + { + .readkey = readkey + }; + +struct grub_terminfo_output_state grub_ofconsole_terminfo_output = + { + .put = put, + .width = 80, + .height = 24 + }; + +static struct grub_term_input grub_ofconsole_term_input = + { + .name = "ofconsole", + .init = grub_ofconsole_init_input, + .getkey = grub_terminfo_getkey, + .data = &grub_ofconsole_terminfo_input + }; + +static struct grub_term_output grub_ofconsole_term_output = + { + .name = "ofconsole", + .init = grub_ofconsole_init_output, + .putchar = grub_terminfo_putchar, + .getxy = grub_terminfo_getxy, + .getwh = grub_terminfo_getwh, + .gotoxy = grub_terminfo_gotoxy, + .cls = grub_terminfo_cls, + .setcolorstate = grub_terminfo_setcolorstate, + .setcursor = grub_ofconsole_setcursor, + .flags = GRUB_TERM_CODE_TYPE_ASCII, + .data = &grub_ofconsole_terminfo_output, + .normal_color = GRUB_TERM_DEFAULT_NORMAL_COLOR, + .highlight_color = GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR, + }; + +void grub_terminfo_fini (void); +void grub_terminfo_init (void); + +void +grub_console_init_early (void) +{ + grub_term_register_input ("ofconsole", &grub_ofconsole_term_input); + grub_term_register_output ("ofconsole", &grub_ofconsole_term_output); +} + +void +grub_console_init_lately (void) +{ + const char *type; + + if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_ANSI)) + type = "dumb"; + else + type = "ieee1275"; + + grub_terminfo_init (); + grub_terminfo_output_register (&grub_ofconsole_term_output, type); +} + +void +grub_console_fini (void) +{ + grub_term_unregister_input (&grub_ofconsole_term_input); + grub_term_unregister_output (&grub_ofconsole_term_output); + grub_terminfo_output_unregister (&grub_ofconsole_term_output); + + grub_terminfo_fini (); +} diff --git a/grub-core/term/ns8250.c b/grub-core/term/ns8250.c new file mode 100644 index 0000000..4be528d --- /dev/null +++ b/grub-core/term/ns8250.c @@ -0,0 +1,278 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef GRUB_MACHINE_PCBIOS +#include +static const unsigned short *serial_hw_io_addr = (const unsigned short *) GRUB_MEMORY_MACHINE_BIOS_DATA_AREA_ADDR; +#define GRUB_SERIAL_PORT_NUM 4 +#else +#include +static const grub_port_t serial_hw_io_addr[] = GRUB_MACHINE_SERIAL_PORTS; +#define GRUB_SERIAL_PORT_NUM (ARRAY_SIZE(serial_hw_io_addr)) +#endif + +/* Convert speed to divisor. */ +static unsigned short +serial_get_divisor (unsigned int speed) +{ + unsigned int i; + + /* The structure for speed vs. divisor. */ + struct divisor + { + unsigned int speed; + unsigned short div; + }; + + /* The table which lists common configurations. */ + /* 1843200 / (speed * 16) */ + static struct divisor divisor_tab[] = + { + { 2400, 0x0030 }, + { 4800, 0x0018 }, + { 9600, 0x000C }, + { 19200, 0x0006 }, + { 38400, 0x0003 }, + { 57600, 0x0002 }, + { 115200, 0x0001 } + }; + + /* Set the baud rate. */ + for (i = 0; i < ARRAY_SIZE (divisor_tab); i++) + if (divisor_tab[i].speed == speed) + /* UART in Yeeloong runs twice the usual rate. */ +#ifdef GRUB_MACHINE_MIPS_YEELOONG + return 2 * divisor_tab[i].div; +#else + return divisor_tab[i].div; +#endif + return 0; +} + +static void +do_real_config (struct grub_serial_port *port) +{ + int divisor; + unsigned char status = 0; + const unsigned char parities[] = { + [GRUB_SERIAL_PARITY_NONE] = UART_NO_PARITY, + [GRUB_SERIAL_PARITY_ODD] = UART_ODD_PARITY, + [GRUB_SERIAL_PARITY_EVEN] = UART_EVEN_PARITY + }; + const unsigned char stop_bits[] = { + [GRUB_SERIAL_STOP_BITS_1] = UART_1_STOP_BIT, + [GRUB_SERIAL_STOP_BITS_2] = UART_2_STOP_BITS, + }; + + if (port->configured) + return; + + port->broken = 0; + + divisor = serial_get_divisor (port->config.speed); + + /* Turn off the interrupt. */ + grub_outb (0, port->port + UART_IER); + + /* Set DLAB. */ + grub_outb (UART_DLAB, port->port + UART_LCR); + + /* Set the baud rate. */ + grub_outb (divisor & 0xFF, port->port + UART_DLL); + grub_outb (divisor >> 8, port->port + UART_DLH); + + /* Set the line status. */ + status |= (parities[port->config.parity] + | (port->config.word_len - 5) + | stop_bits[port->config.stop_bits]); + grub_outb (status, port->port + UART_LCR); + + /* In Yeeloong serial port has only 3 wires. */ +#ifndef GRUB_MACHINE_MIPS_YEELOONG + /* Enable the FIFO. */ + grub_outb (UART_ENABLE_FIFO_TRIGGER1, port->port + UART_FCR); + + /* Turn on DTR and RTS. */ + grub_outb (UART_ENABLE_DTRRTS, port->port + UART_MCR); +#else + /* Enable the FIFO. */ + grub_outb (UART_ENABLE_FIFO_TRIGGER14, port->port + UART_FCR); + + /* Turn on DTR, RTS, and OUT2. */ + grub_outb (UART_ENABLE_DTRRTS | UART_ENABLE_OUT2, port->port + UART_MCR); +#endif + + /* Drain the input buffer. */ + while (grub_inb (port->port + UART_LSR) & UART_DATA_READY) + grub_inb (port->port + UART_RX); + + port->configured = 1; +} + +/* Fetch a key. */ +static int +serial_hw_fetch (struct grub_serial_port *port) +{ + do_real_config (port); + if (grub_inb (port->port + UART_LSR) & UART_DATA_READY) + return grub_inb (port->port + UART_RX); + + return -1; +} + +/* Put a character. */ +static void +serial_hw_put (struct grub_serial_port *port, const int c) +{ + grub_uint64_t endtime; + + do_real_config (port); + + if (port->broken > 5) + endtime = grub_get_time_ms (); + else if (port->broken > 1) + endtime = grub_get_time_ms () + 50; + else + endtime = grub_get_time_ms () + 200; + /* Wait until the transmitter holding register is empty. */ + while ((grub_inb (port->port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0) + { + if (grub_get_time_ms () > endtime) + { + port->broken++; + /* There is something wrong. But what can I do? */ + return; + } + } + + if (port->broken) + port->broken--; + + grub_outb (c, port->port + UART_TX); +} + +/* Initialize a serial device. PORT is the port number for a serial device. + SPEED is a DTE-DTE speed which must be one of these: 2400, 4800, 9600, + 19200, 38400, 57600 and 115200. WORD_LEN is the word length to be used + for the device. Likewise, PARITY is the type of the parity and + STOP_BIT_LEN is the length of the stop bit. The possible values for + WORD_LEN, PARITY and STOP_BIT_LEN are defined in the header file as + macros. */ +static grub_err_t +serial_hw_configure (struct grub_serial_port *port, + struct grub_serial_config *config) +{ + unsigned short divisor; + + divisor = serial_get_divisor (config->speed); + if (divisor == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad speed"); + + if (config->parity != GRUB_SERIAL_PARITY_NONE + && config->parity != GRUB_SERIAL_PARITY_ODD + && config->parity != GRUB_SERIAL_PARITY_EVEN) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported parity"); + + if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1 + && config->stop_bits != GRUB_SERIAL_STOP_BITS_2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported stop bits"); + + if (config->word_len < 5 || config->word_len > 8) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported word length"); + + port->config = *config; + port->configured = 0; + + /* FIXME: should check if the serial terminal was found. */ + + return GRUB_ERR_NONE; +} + +struct grub_serial_driver grub_ns8250_driver = + { + .configure = serial_hw_configure, + .fetch = serial_hw_fetch, + .put = serial_hw_put + }; + +static char com_names[GRUB_SERIAL_PORT_NUM][20]; +static struct grub_serial_port com_ports[GRUB_SERIAL_PORT_NUM]; + +void +grub_ns8250_init (void) +{ + unsigned i; + for (i = 0; i < GRUB_SERIAL_PORT_NUM; i++) + if (serial_hw_io_addr[i]) + { + grub_err_t err; + grub_snprintf (com_names[i], sizeof (com_names[i]), "com%d", i); + com_ports[i].name = com_names[i]; + com_ports[i].driver = &grub_ns8250_driver; + com_ports[i].port = serial_hw_io_addr[i]; + err = grub_serial_config_defaults (&com_ports[i]); + if (err) + grub_print_error (); + + grub_serial_register (&com_ports[i]); + } +} + +/* Return the port number for the UNITth serial device. */ +grub_port_t +grub_ns8250_hw_get_port (const unsigned int unit) +{ + if (unit < GRUB_SERIAL_PORT_NUM) + return serial_hw_io_addr[unit]; + else + return 0; +} + +char * +grub_serial_ns8250_add_port (grub_port_t port) +{ + struct grub_serial_port *p; + unsigned i; + for (i = 0; i < GRUB_SERIAL_PORT_NUM; i++) + if (com_ports[i].port == port) + return com_names[i]; + p = grub_malloc (sizeof (*p)); + if (!p) + return NULL; + p->name = grub_xasprintf ("port%lx", (unsigned long) port); + if (!p->name) + { + grub_free (p); + return NULL; + } + p->driver = &grub_ns8250_driver; + grub_serial_config_defaults (p); + p->port = port; + grub_serial_register (p); + + return p->name; +} diff --git a/grub-core/term/serial.c b/grub-core/term/serial.c new file mode 100644 index 0000000..073c27a --- /dev/null +++ b/grub-core/term/serial.c @@ -0,0 +1,372 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define FOR_SERIAL_PORTS(var) FOR_LIST_ELEMENTS((var), (grub_serial_ports)) + +/* Argument options. */ +static const struct grub_arg_option options[] = +{ + {"unit", 'u', 0, N_("Set the serial unit."), 0, ARG_TYPE_INT}, + {"port", 'p', 0, N_("Set the serial port address."), 0, ARG_TYPE_STRING}, + {"speed", 's', 0, N_("Set the serial port speed."), 0, ARG_TYPE_INT}, + {"word", 'w', 0, N_("Set the serial port word length."), 0, ARG_TYPE_INT}, + {"parity", 'r', 0, N_("Set the serial port parity."), 0, ARG_TYPE_STRING}, + {"stop", 't', 0, N_("Set the serial port stop bits."), 0, ARG_TYPE_INT}, + {0, 0, 0, 0, 0, 0} +}; + +static struct grub_serial_port *grub_serial_ports; + +struct grub_serial_output_state +{ + struct grub_terminfo_output_state tinfo; + struct grub_serial_port *port; +}; + +struct grub_serial_input_state +{ + struct grub_terminfo_input_state tinfo; + struct grub_serial_port *port; +}; + +static void +serial_put (grub_term_output_t term, const int c) +{ + struct grub_serial_output_state *data = term->data; + data->port->driver->put (data->port, c); +} + +static int +serial_fetch (grub_term_input_t term) +{ + struct grub_serial_input_state *data = term->data; + return data->port->driver->fetch (data->port); +} + +static const struct grub_serial_input_state grub_serial_terminfo_input_template = + { + .tinfo = + { + .readkey = serial_fetch + } + }; + +static const struct grub_serial_output_state grub_serial_terminfo_output_template = + { + .tinfo = + { + .put = serial_put, + .width = 80, + .height = 24 + } + }; + +static struct grub_serial_input_state grub_serial_terminfo_input; + +static struct grub_serial_output_state grub_serial_terminfo_output; + +static int registered = 0; + +static struct grub_term_input grub_serial_term_input = +{ + .name = "serial", + .init = grub_terminfo_input_init, + .getkey = grub_terminfo_getkey, + .data = &grub_serial_terminfo_input +}; + +static struct grub_term_output grub_serial_term_output = +{ + .name = "serial", + .init = grub_terminfo_output_init, + .putchar = grub_terminfo_putchar, + .getwh = grub_terminfo_getwh, + .getxy = grub_terminfo_getxy, + .gotoxy = grub_terminfo_gotoxy, + .cls = grub_terminfo_cls, + .setcolorstate = grub_terminfo_setcolorstate, + .setcursor = grub_terminfo_setcursor, + .flags = GRUB_TERM_CODE_TYPE_ASCII, + .data = &grub_serial_terminfo_output, + .normal_color = GRUB_TERM_DEFAULT_NORMAL_COLOR, + .highlight_color = GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR, +}; + + + +static struct grub_serial_port * +grub_serial_find (char *name) +{ + struct grub_serial_port *port; + + FOR_SERIAL_PORTS (port) + if (grub_strcmp (port->name, name) == 0) + break; + +#ifndef GRUB_MACHINE_EMU + if (!port && grub_memcmp (name, "port", sizeof ("port") - 1) == 0 + && grub_isdigit (name [sizeof ("port") - 1])) + { + name = grub_serial_ns8250_add_port (grub_strtoul (&name[sizeof ("port") - 1], + 0, 16)); + if (!name) + return NULL; + + FOR_SERIAL_PORTS (port) + if (grub_strcmp (port->name, name) == 0) + break; + } +#endif + + return port; +} + +static grub_err_t +grub_cmd_serial (grub_extcmd_context_t ctxt, int argc, char **args) +{ + struct grub_arg_list *state = ctxt->state; + char pname[40]; + char *name = NULL; + struct grub_serial_port *port; + struct grub_serial_config config; + grub_err_t err; + + if (state[0].set) + { + grub_snprintf (pname, sizeof (pname), "com%ld", + grub_strtoul (state[0].arg, 0, 0)); + name = pname; + } + + if (state[1].set) + { + grub_snprintf (pname, sizeof (pname), "port%lx", + grub_strtoul (state[1].arg, 0, 0)); + name = pname; + } + + if (argc >= 1) + name = args[0]; + + if (!name) + name = "com0"; + + port = grub_serial_find (name); + if (!port) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unknown serial port"); + + config = port->config; + + if (state[2].set) + config.speed = grub_strtoul (state[2].arg, 0, 0); + + if (state[3].set) + config.word_len = grub_strtoul (state[3].arg, 0, 0); + + if (state[4].set) + { + if (! grub_strcmp (state[4].arg, "no")) + config.parity = GRUB_SERIAL_PARITY_NONE; + else if (! grub_strcmp (state[4].arg, "odd")) + config.parity = GRUB_SERIAL_PARITY_ODD; + else if (! grub_strcmp (state[4].arg, "even")) + config.parity = GRUB_SERIAL_PARITY_EVEN; + else + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad parity"); + } + + if (state[5].set) + { + if (! grub_strcmp (state[5].arg, "1")) + config.stop_bits = GRUB_SERIAL_STOP_BITS_1; + else if (! grub_strcmp (state[5].arg, "2")) + config.stop_bits = GRUB_SERIAL_STOP_BITS_2; + else + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad number of stop bits"); + } + + /* Initialize with new settings. */ + err = port->driver->configure (port, &config); + if (err) + return err; +#ifndef GRUB_MACHINE_EMU + /* Compatibility kludge. */ + if (port->driver == &grub_ns8250_driver) + { + if (!registered) + { + grub_terminfo_output_register (&grub_serial_term_output, "vt100"); + + grub_term_register_input ("serial", &grub_serial_term_input); + grub_term_register_output ("serial", &grub_serial_term_output); + } + grub_serial_terminfo_output.port = port; + grub_serial_terminfo_input.port = port; + registered = 1; + } +#endif + return GRUB_ERR_NONE; +} + +grub_err_t +grub_serial_register (struct grub_serial_port *port) +{ + struct grub_term_input *in; + struct grub_term_output *out; + struct grub_serial_input_state *indata; + struct grub_serial_output_state *outdata; + + in = grub_malloc (sizeof (*in)); + if (!in) + return grub_errno; + + indata = grub_malloc (sizeof (*indata)); + if (!indata) + { + grub_free (in); + return grub_errno; + } + + grub_memcpy (in, &grub_serial_term_input, sizeof (*in)); + in->data = indata; + in->name = grub_xasprintf ("serial_%s", port->name); + grub_memcpy (indata, &grub_serial_terminfo_input, sizeof (*indata)); + + if (!in->name) + { + grub_free (in); + grub_free (indata); + return grub_errno; + } + + out = grub_malloc (sizeof (*out)); + if (!out) + { + grub_free (in); + grub_free (indata); + grub_free ((char *) in->name); + return grub_errno; + } + + outdata = grub_malloc (sizeof (*outdata)); + if (!outdata) + { + grub_free (in); + grub_free (indata); + grub_free ((char *) in->name); + grub_free (out); + return grub_errno; + } + + grub_memcpy (out, &grub_serial_term_output, sizeof (*out)); + out->data = outdata; + out->name = in->name; + grub_memcpy (outdata, &grub_serial_terminfo_output, sizeof (*outdata)); + + grub_list_push (GRUB_AS_LIST_P (&grub_serial_ports), GRUB_AS_LIST (port)); + ((struct grub_serial_input_state *) in->data)->port = port; + ((struct grub_serial_output_state *) out->data)->port = port; + port->term_in = in; + port->term_out = out; + grub_terminfo_output_register (out, "vt100"); +#ifdef GRUB_MACHINE_MIPS_YEELOONG + if (grub_strcmp (port->name, "com0") == 0) + { + grub_term_register_input_active ("serial_*", in); + grub_term_register_output_active ("serial_*", out); + } + else +#endif + { + grub_term_register_input ("serial_*", in); + grub_term_register_output ("serial_*", out); + } + + return GRUB_ERR_NONE; +} + +void +grub_serial_unregister (struct grub_serial_port *port) +{ + if (port->driver->fini) + port->driver->fini (port); + + if (port->term_in) + grub_term_unregister_input (port->term_in); + if (port->term_out) + grub_term_unregister_output (port->term_out); + + grub_list_remove (GRUB_AS_LIST_P (&grub_serial_ports), GRUB_AS_LIST (port)); +} + +void +grub_serial_unregister_driver (struct grub_serial_driver *driver) +{ + struct grub_serial_port *port, *next; + for (port = grub_serial_ports; port; port = next) + { + next = port->next; + if (port->driver == driver) + grub_serial_unregister (port); + } +} + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT(serial) +{ + cmd = grub_register_extcmd ("serial", grub_cmd_serial, 0, + N_("[OPTIONS...]"), + N_("Configure serial port."), options); + grub_memcpy (&grub_serial_terminfo_output, + &grub_serial_terminfo_output_template, + sizeof (grub_serial_terminfo_output)); + + grub_memcpy (&grub_serial_terminfo_input, + &grub_serial_terminfo_input_template, + sizeof (grub_serial_terminfo_input)); + +#ifndef GRUB_MACHINE_EMU + grub_ns8250_init (); +#endif +} + +GRUB_MOD_FINI(serial) +{ + while (grub_serial_ports) + grub_serial_unregister (grub_serial_ports); + if (registered) + { + grub_term_unregister_input (&grub_serial_term_input); + grub_term_unregister_output (&grub_serial_term_output); + } + grub_unregister_extcmd (cmd); +} diff --git a/grub-core/term/terminfo.c b/grub-core/term/terminfo.c new file mode 100644 index 0000000..1615813 --- /dev/null +++ b/grub-core/term/terminfo.c @@ -0,0 +1,652 @@ +/* terminfo.c - simple terminfo module */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +/* + * This file contains various functions dealing with different + * terminal capabilities. For example, vt52 and vt100. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static struct grub_term_output *terminfo_outputs; + +/* Get current terminfo name. */ +char * +grub_terminfo_get_current (struct grub_term_output *term) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + return data->name; +} + +/* Free *PTR and set *PTR to NULL, to prevent double-free. */ +static void +grub_terminfo_free (char **ptr) +{ + grub_free (*ptr); + *ptr = 0; +} + +static void +grub_terminfo_all_free (struct grub_term_output *term) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + /* Free previously allocated memory. */ + grub_terminfo_free (&data->name); + grub_terminfo_free (&data->gotoxy); + grub_terminfo_free (&data->cls); + grub_terminfo_free (&data->reverse_video_on); + grub_terminfo_free (&data->reverse_video_off); + grub_terminfo_free (&data->cursor_on); + grub_terminfo_free (&data->cursor_off); +} + +/* Set current terminfo type. */ +grub_err_t +grub_terminfo_set_current (struct grub_term_output *term, + const char *str) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + /* TODO + * Lookup user specified terminfo type. If found, set term variables + * as appropriate. Otherwise return an error. + * + * How should this be done? + * a. A static table included in this module. + * - I do not like this idea. + * b. A table stored in the configuration directory. + * - Users must convert their terminfo settings if we have not already. + * c. Look for terminfo files in the configuration directory. + * - /usr/share/terminfo is 6.3M on my system. + * - /usr/share/terminfo is not on most users boot partition. + * + Copying the terminfo files you want to use to the grub + * configuration directory is easier then (b). + * d. Your idea here. + */ + + grub_terminfo_all_free (term); + + if (grub_strcmp ("vt100", str) == 0) + { + data->name = grub_strdup ("vt100"); + data->gotoxy = grub_strdup ("\e[%i%p1%d;%p2%dH"); + data->cls = grub_strdup ("\e[H\e[J"); + data->reverse_video_on = grub_strdup ("\e[7m"); + data->reverse_video_off = grub_strdup ("\e[m"); + data->cursor_on = grub_strdup ("\e[?25h"); + data->cursor_off = grub_strdup ("\e[?25l"); + data->setcolor = NULL; + return grub_errno; + } + + if (grub_strcmp ("vt100-color", str) == 0) + { + data->name = grub_strdup ("vt100-color"); + data->gotoxy = grub_strdup ("\e[%i%p1%d;%p2%dH"); + data->cls = grub_strdup ("\e[H\e[J"); + data->reverse_video_on = grub_strdup ("\e[7m"); + data->reverse_video_off = grub_strdup ("\e[m"); + data->cursor_on = grub_strdup ("\e[?25h"); + data->cursor_off = grub_strdup ("\e[?25l"); + data->setcolor = grub_strdup ("\e[3%p1%dm\e[4%p2%dm"); + return grub_errno; + } + + if (grub_strcmp ("ieee1275", str) == 0) + { + data->name = grub_strdup ("ieee1275"); + data->gotoxy = grub_strdup ("\e[%i%p1%d;%p2%dH"); + /* Clear the screen. Using serial console, screen(1) only recognizes the + * ANSI escape sequence. Using video console, Apple Open Firmware + * (version 3.1.1) only recognizes the literal ^L. So use both. */ + data->cls = grub_strdup (" \e[2J"); + data->reverse_video_on = grub_strdup ("\e[7m"); + data->reverse_video_off = grub_strdup ("\e[m"); + data->cursor_on = grub_strdup ("\e[?25h"); + data->cursor_off = grub_strdup ("\e[?25l"); + data->setcolor = grub_strdup ("\e[3%p1%dm\e[4%p2%dm"); + return grub_errno; + } + + if (grub_strcmp ("dumb", str) == 0) + { + data->name = grub_strdup ("dumb"); + data->gotoxy = NULL; + data->cls = NULL; + data->reverse_video_on = NULL; + data->reverse_video_off = NULL; + data->cursor_on = NULL; + data->cursor_off = NULL; + data->setcolor = NULL; + return grub_errno; + } + + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unknown terminfo type"); +} + +grub_err_t +grub_terminfo_output_register (struct grub_term_output *term, + const char *type) +{ + grub_err_t err; + struct grub_terminfo_output_state *data; + + err = grub_terminfo_set_current (term, type); + + if (err) + return err; + + data = (struct grub_terminfo_output_state *) term->data; + data->next = terminfo_outputs; + terminfo_outputs = term; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_terminfo_output_unregister (struct grub_term_output *term) +{ + struct grub_term_output **ptr; + + for (ptr = &terminfo_outputs; *ptr; + ptr = &((struct grub_terminfo_output_state *) (*ptr)->data)->next) + if (*ptr == term) + { + grub_terminfo_all_free (term); + *ptr = ((struct grub_terminfo_output_state *) (*ptr)->data)->next; + return GRUB_ERR_NONE; + } + return grub_error (GRUB_ERR_BAD_ARGUMENT, "terminal not found"); +} + +/* Wrapper for grub_putchar to write strings. */ +static void +putstr (struct grub_term_output *term, const char *str) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + while (*str) + data->put (term, *str++); +} + +grub_uint16_t +grub_terminfo_getxy (struct grub_term_output *term) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + return ((data->xpos << 8) | data->ypos); +} + +void +grub_terminfo_gotoxy (struct grub_term_output *term, + grub_uint8_t x, grub_uint8_t y) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + if (x > grub_term_width (term) || y > grub_term_height (term)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid point (%u,%u)", x, y); + return; + } + + if (data->gotoxy) + putstr (term, grub_terminfo_tparm (data->gotoxy, y, x)); + else + { + if ((y == data->ypos) && (x == data->xpos - 1)) + data->put (term, '\b'); + } + + data->xpos = x; + data->ypos = y; +} + +/* Clear the screen. */ +void +grub_terminfo_cls (struct grub_term_output *term) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + putstr (term, grub_terminfo_tparm (data->cls)); + + data->xpos = data->ypos = 0; +} + +void +grub_terminfo_setcolorstate (struct grub_term_output *term, + const grub_term_color_state state) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + if (data->setcolor) + { + int fg; + int bg; + /* Map from VGA to terminal colors. */ + const int colormap[8] + = { 0, /* Black. */ + 4, /* Blue. */ + 2, /* Green. */ + 6, /* Cyan. */ + 1, /* Red. */ + 5, /* Magenta. */ + 3, /* Yellow. */ + 7, /* White. */ + }; + + switch (state) + { + case GRUB_TERM_COLOR_STANDARD: + case GRUB_TERM_COLOR_NORMAL: + fg = term->normal_color & 0x0f; + bg = term->normal_color >> 4; + break; + case GRUB_TERM_COLOR_HIGHLIGHT: + fg = term->highlight_color & 0x0f; + bg = term->highlight_color >> 4; + break; + default: + return; + } + + putstr (term, grub_terminfo_tparm (data->setcolor, colormap[fg & 7], + colormap[bg & 7])); + return; + } + + switch (state) + { + case GRUB_TERM_COLOR_STANDARD: + case GRUB_TERM_COLOR_NORMAL: + putstr (term, grub_terminfo_tparm (data->reverse_video_off)); + break; + case GRUB_TERM_COLOR_HIGHLIGHT: + putstr (term, grub_terminfo_tparm (data->reverse_video_on)); + break; + default: + break; + } +} + +void +grub_terminfo_setcursor (struct grub_term_output *term, const int on) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + if (on) + putstr (term, grub_terminfo_tparm (data->cursor_on)); + else + putstr (term, grub_terminfo_tparm (data->cursor_off)); +} + +/* The terminfo version of putchar. */ +void +grub_terminfo_putchar (struct grub_term_output *term, + const struct grub_unicode_glyph *c) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + /* Keep track of the cursor. */ + switch (c->base) + { + case '\a': + break; + + case '\b': + case 127: + if (data->xpos > 0) + data->xpos--; + break; + + case '\n': + if (data->ypos < grub_term_height (term) - 1) + data->ypos++; + break; + + case '\r': + data->xpos = 0; + break; + + default: + if (data->xpos + c->estimated_width >= grub_term_width (term) + 1) + { + data->xpos = 0; + if (data->ypos < grub_term_height (term) - 1) + data->ypos++; + data->put (term, '\r'); + data->put (term, '\n'); + } + data->xpos += c->estimated_width; + break; + } + + data->put (term, c->base); +} + +grub_uint16_t +grub_terminfo_getwh (struct grub_term_output *term) +{ + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) term->data; + + return (data->width << 8) | data->height; +} + +#define ANSI_C0 0x9b + +static void +grub_terminfo_readkey (struct grub_term_input *term, int *keys, int *len, + int (*readkey) (struct grub_term_input *term)) +{ + int c; + +#define CONTINUE_READ \ + { \ + grub_uint64_t start; \ + /* On 9600 we have to wait up to 12 milliseconds. */ \ + start = grub_get_time_ms (); \ + do \ + c = readkey (term); \ + while (c == -1 && grub_get_time_ms () - start < 12); \ + if (c == -1) \ + return; \ + \ + keys[*len] = c; \ + (*len)++; \ + } + + c = readkey (term); + if (c < 0) + { + *len = 0; + return; + } + *len = 1; + keys[0] = c; + if (c != ANSI_C0 && c != '\e') + { + /* Backspace: Ctrl-h. */ + if (c == 0x7f) + c = '\b'; + if (c < 0x20 && c != '\t' && c!= '\b' && c != '\n' && c != '\r') + c = GRUB_TERM_CTRL | (c - 1 + 'a'); + *len = 1; + keys[0] = c; + return; + } + + { + static struct + { + char key; + unsigned ascii; + } + three_code_table[] = + { + {'4', GRUB_TERM_KEY_DC}, + {'A', GRUB_TERM_KEY_UP}, + {'B', GRUB_TERM_KEY_DOWN}, + {'C', GRUB_TERM_KEY_RIGHT}, + {'D', GRUB_TERM_KEY_LEFT}, + {'F', GRUB_TERM_KEY_END}, + {'H', GRUB_TERM_KEY_HOME}, + {'K', GRUB_TERM_KEY_END}, + {'P', GRUB_TERM_KEY_DC}, + {'?', GRUB_TERM_KEY_PPAGE}, + {'/', GRUB_TERM_KEY_NPAGE} + }; + + static struct + { + char key; + unsigned ascii; + } + four_code_table[] = + { + {'1', GRUB_TERM_KEY_HOME}, + {'3', GRUB_TERM_KEY_DC}, + {'5', GRUB_TERM_KEY_PPAGE}, + {'6', GRUB_TERM_KEY_NPAGE} + }; + unsigned i; + + if (c == '\e') + { + CONTINUE_READ; + + if (c != '[') + return; + } + + CONTINUE_READ; + + for (i = 0; i < ARRAY_SIZE (three_code_table); i++) + if (three_code_table[i].key == c) + { + keys[0] = three_code_table[i].ascii; + *len = 1; + return; + } + + for (i = 0; i < ARRAY_SIZE (four_code_table); i++) + if (four_code_table[i].key == c) + { + CONTINUE_READ; + if (c != '~') + return; + keys[0] = three_code_table[i].ascii; + *len = 1; + return; + } + return; + } +#undef CONTINUE_READ +} + +/* The terminfo version of getkey. */ +int +grub_terminfo_getkey (struct grub_term_input *termi) +{ + struct grub_terminfo_input_state *data + = (struct grub_terminfo_input_state *) (termi->data); + if (data->npending) + { + data->npending--; + grub_memmove (data->input_buf, data->input_buf + 1, data->npending); + return data->input_buf[0]; + } + + grub_terminfo_readkey (termi, data->input_buf, + &data->npending, data->readkey); + + if (data->npending) + { + data->npending--; + grub_memmove (data->input_buf, data->input_buf + 1, data->npending); + return data->input_buf[0]; + } + + return GRUB_TERM_NO_KEY; +} + +grub_err_t +grub_terminfo_input_init (struct grub_term_input *termi) +{ + struct grub_terminfo_input_state *data + = (struct grub_terminfo_input_state *) (termi->data); + data->npending = 0; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_terminfo_output_init (struct grub_term_output *term) +{ + grub_terminfo_cls (term); + return GRUB_ERR_NONE; +} + +/* GRUB Command. */ + +static grub_err_t +print_terminfo (void) +{ + const char *encoding_names[(GRUB_TERM_CODE_TYPE_MASK + >> GRUB_TERM_CODE_TYPE_SHIFT) + 1] + = { + /* VGA and glyph descriptor types are just for completeness, + they are not used on terminfo terminals. + */ + [GRUB_TERM_CODE_TYPE_ASCII >> GRUB_TERM_CODE_TYPE_SHIFT] = _("ASCII"), + [GRUB_TERM_CODE_TYPE_CP437 >> GRUB_TERM_CODE_TYPE_SHIFT] = "CP-437", + [GRUB_TERM_CODE_TYPE_UTF8_LOGICAL >> GRUB_TERM_CODE_TYPE_SHIFT] + = _("UTF-8"), + [GRUB_TERM_CODE_TYPE_UTF8_VISUAL >> GRUB_TERM_CODE_TYPE_SHIFT] + = _("UTF-8 visual"), + [GRUB_TERM_CODE_TYPE_VISUAL_GLYPHS >> GRUB_TERM_CODE_TYPE_SHIFT] + = "Glyph descriptors", + _("Unknown"), _("Unknown"), _("Unknown") + }; + struct grub_term_output *cur; + + grub_printf ("Current terminfo types: \n"); + for (cur = terminfo_outputs; cur; + cur = ((struct grub_terminfo_output_state *) cur->data)->next) + grub_printf ("%s: %s\t%s\n", cur->name, + grub_terminfo_get_current(cur), + encoding_names[(cur->flags & GRUB_TERM_CODE_TYPE_MASK) + >> GRUB_TERM_CODE_TYPE_SHIFT]); + + return GRUB_ERR_NONE; +} + +static const struct grub_arg_option options[] = +{ + {"ascii", 'a', 0, N_("Terminal is ASCII-only [default]."), 0, ARG_TYPE_NONE}, + {"utf8", 'u', 0, N_("Terminal is logical-ordered UTF-8."), 0, ARG_TYPE_NONE}, + {"visual-utf8", 'v', 0, N_("Terminal is visually-ordered UTF-8."), 0, + ARG_TYPE_NONE}, + {"geometry", 'g', 0, N_("Terminal has given geometry."), + N_("WIDTHxHEIGHT."), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} +}; + +enum + { + OPTION_ASCII, + OPTION_UTF8, + OPTION_VISUAL_UTF8, + OPTION_GEOMETRY + }; + +static grub_err_t +grub_cmd_terminfo (grub_extcmd_context_t ctxt, int argc, char **args) +{ + struct grub_term_output *cur; + int encoding = GRUB_TERM_CODE_TYPE_ASCII; + struct grub_arg_list *state = ctxt->state; + int w = 0, h = 0; + + if (argc == 0) + return print_terminfo (); + + if (state[OPTION_ASCII].set) + encoding = GRUB_TERM_CODE_TYPE_ASCII; + + if (state[OPTION_UTF8].set) + encoding = GRUB_TERM_CODE_TYPE_UTF8_LOGICAL; + + if (state[OPTION_VISUAL_UTF8].set) + encoding = GRUB_TERM_CODE_TYPE_UTF8_VISUAL; + + if (state[OPTION_GEOMETRY].set) + { + char *ptr = state[OPTION_GEOMETRY].arg; + w = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + if (*ptr != 'x') + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "incorrect geometry specification"); + ptr++; + h = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + } + + for (cur = terminfo_outputs; cur; + cur = ((struct grub_terminfo_output_state *) cur->data)->next) + if (grub_strcmp (args[0], cur->name) == 0) + { + cur->flags = (cur->flags & ~GRUB_TERM_CODE_TYPE_MASK) | encoding; + + if (w && h) + { + struct grub_terminfo_output_state *data + = (struct grub_terminfo_output_state *) cur->data; + data->width = w; + data->height = h; + } + + if (argc == 1) + return GRUB_ERR_NONE; + + return grub_terminfo_set_current (cur, args[1]); + } + + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "no terminal %s found or it's not handled by terminfo", + args[0]); +} + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT(terminfo) +{ + cmd = grub_register_extcmd ("terminfo", grub_cmd_terminfo, 0, + N_("[[-a|-u|-v] [-g WxH] TERM [TYPE]]"), + N_("Set terminfo type of TERM to TYPE.\n"), + options); +} + +GRUB_MOD_FINI(terminfo) +{ + grub_unregister_extcmd (cmd); +} diff --git a/grub-core/term/tparm.c b/grub-core/term/tparm.c new file mode 100644 index 0000000..076a192 --- /dev/null +++ b/grub-core/term/tparm.c @@ -0,0 +1,761 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1998-2003,2004,2005 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +/********************************************************************** + * This code is a modification of lib_tparm.c found in ncurses-5.2. The + * modification are for use in grub by replacing all libc function through + * special grub functions. This also meant to delete all dynamic memory + * allocation and replace it by a number of fixed buffers. + * + * Modifications by Tilmann Bubeck 2002 + * + * Resync with ncurses-5.4 by Omniflux 2005 + **********************************************************************/ + +/**************************************************************************** + * Author: Zeyd M. Ben-Halim 1992,1995 * + * and: Eric S. Raymond * + * and: Thomas E. Dickey, 1996 on * + ****************************************************************************/ + +/* + * tparm.c + * + */ + +#include +#include +#include +#include + +/* + * Common/troublesome character definitions + */ +typedef char grub_bool_t; +#ifndef FALSE +# define FALSE (0) +#endif +#ifndef TRUE +# define TRUE (!FALSE) +#endif + +#define NUM_PARM 9 +#define NUM_VARS 26 +#define STACKSIZE 20 +#define MAX_FORMAT_LEN 256 + +#define max(a,b) ((a) > (b) ? (a) : (b)) +#define isdigit(c) ((c) >= '0' && (c) <= '9') +#define isUPPER(c) ((c) >= 'A' && (c) <= 'Z') +#define isLOWER(c) ((c) >= 'a' && (c) <= 'z') + +#define UChar(c) ((unsigned char)(c)) + +//MODULE_ID("$Id$") + +/* + * char * + * tparm(string, ...) + * + * Substitute the given parameters into the given string by the following + * rules (taken from terminfo(5)): + * + * Cursor addressing and other strings requiring parame- + * ters in the terminal are described by a parameterized string + * capability, with like escapes %x in it. For example, to + * address the cursor, the cup capability is given, using two + * parameters: the row and column to address to. (Rows and + * columns are numbered from zero and refer to the physical + * screen visible to the user, not to any unseen memory.) If + * the terminal has memory relative cursor addressing, that can + * be indicated by + * + * The parameter mechanism uses a stack and special % + * codes to manipulate it. Typically a sequence will push one + * of the parameters onto the stack and then print it in some + * format. Often more complex operations are necessary. + * + * The % encodings have the following meanings: + * + * %% outputs `%' + * %c print pop() like %c in printf() + * %s print pop() like %s in printf() + * %[[:]flags][width[.precision]][doxXs] + * as in printf, flags are [-+#] and space + * The ':' is used to avoid making %+ or %- + * patterns (see below). + * + * %p[1-9] push ith parm + * %P[a-z] set dynamic variable [a-z] to pop() + * %g[a-z] get dynamic variable [a-z] and push it + * %P[A-Z] set static variable [A-Z] to pop() + * %g[A-Z] get static variable [A-Z] and push it + * %l push strlen(pop) + * %'c' push char constant c + * %{nn} push integer constant nn + * + * %+ %- %* %/ %m + * arithmetic (%m is mod): push(pop() op pop()) + * %& %| %^ bit operations: push(pop() op pop()) + * %= %> %< logical operations: push(pop() op pop()) + * %A %O logical and & or operations for conditionals + * %! %~ unary operations push(op pop()) + * %i add 1 to first two parms (for ANSI terminals) + * + * %? expr %t thenpart %e elsepart %; + * if-then-else, %e elsepart is optional. + * else-if's are possible ala Algol 68: + * %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %; + * + * For those of the above operators which are binary and not commutative, + * the stack works in the usual way, with + * %gx %gy %m + * resulting in x mod y, not the reverse. + */ + +typedef struct { + union { + int num; + char *str; + } data; + grub_bool_t num_type; +} stack_frame; + +static stack_frame stack[STACKSIZE]; +static int stack_ptr; +static const char *tparam_base = ""; + +static char *out_buff; +static grub_size_t out_size; +static grub_size_t out_used; + +static char *fmt_buff; +static grub_size_t fmt_size; + +static inline void +get_space(grub_size_t need) +{ + need += out_used; + if (need > out_size) { + out_size = need * 2; + out_buff = grub_realloc(out_buff, out_size*sizeof(char)); + /* FIX ME! handle out_buff == 0. */ + } +} + +static inline void +save_text(const char *fmt, const char *s, int len) +{ + grub_size_t s_len = grub_strlen(s); + if (len > (int) s_len) + s_len = len; + + get_space(s_len + 1); + + (void) grub_snprintf(out_buff + out_used, s_len + 1, fmt, s); + out_used += grub_strlen(out_buff + out_used); +} + +static inline void +save_number(const char *fmt, int number, int len) +{ + if (len < 30) + len = 30; /* actually log10(MAX_INT)+1 */ + + get_space((unsigned) len + 1); + + (void) grub_snprintf(out_buff + out_used, len + 1, fmt, number); + out_used += grub_strlen(out_buff + out_used); +} + +static inline void +save_char(int c) +{ + if (c == 0) + c = 0200; + get_space(1); + out_buff[out_used++] = c; +} + +static inline void +npush(int x) +{ + if (stack_ptr < STACKSIZE) { + stack[stack_ptr].num_type = TRUE; + stack[stack_ptr].data.num = x; + stack_ptr++; + } +} + +static inline int +npop(void) +{ + int result = 0; + if (stack_ptr > 0) { + stack_ptr--; + if (stack[stack_ptr].num_type) + result = stack[stack_ptr].data.num; + } + return result; +} + +static inline void +spush(char *x) +{ + if (stack_ptr < STACKSIZE) { + stack[stack_ptr].num_type = FALSE; + stack[stack_ptr].data.str = x; + stack_ptr++; + } +} + +static inline char * +spop(void) +{ + static char dummy[] = ""; /* avoid const-cast */ + char *result = dummy; + if (stack_ptr > 0) { + stack_ptr--; + if (!stack[stack_ptr].num_type && stack[stack_ptr].data.str != 0) + result = stack[stack_ptr].data.str; + } + return result; +} + +static inline const char * +parse_format(const char *s, char *format, int *len) +{ + *len = 0; + if (format != 0) { + grub_bool_t done = FALSE; + grub_bool_t allowminus = FALSE; + grub_bool_t dot = FALSE; + grub_bool_t err = FALSE; + char *fmt = format; + int my_width = 0; + int my_prec = 0; + int value = 0; + + *len = 0; + *format++ = '%'; + while (*s != '\0' && !done) { + switch (*s) { + case 'c': /* FALLTHRU */ + case 'd': /* FALLTHRU */ + case 'o': /* FALLTHRU */ + case 'x': /* FALLTHRU */ + case 'X': /* FALLTHRU */ + case 's': + *format++ = *s; + done = TRUE; + break; + case '.': + *format++ = *s++; + if (dot) { + err = TRUE; + } else { /* value before '.' is the width */ + dot = TRUE; + my_width = value; + } + value = 0; + break; + case '#': + *format++ = *s++; + break; + case ' ': + *format++ = *s++; + break; + case ':': + s++; + allowminus = TRUE; + break; + case '-': + if (allowminus) { + *format++ = *s++; + } else { + done = TRUE; + } + break; + default: + if (isdigit(UChar(*s))) { + value = (value * 10) + (*s - '0'); + if (value > 10000) + err = TRUE; + *format++ = *s++; + } else { + done = TRUE; + } + } + } + + /* + * If we found an error, ignore (and remove) the flags. + */ + if (err) { + my_width = my_prec = value = 0; + format = fmt; + *format++ = '%'; + *format++ = *s; + } + + /* + * Any value after '.' is the precision. If we did not see '.', then + * the value is the width. + */ + if (dot) + my_prec = value; + else + my_width = value; + + *format = '\0'; + /* return maximum string length in print */ + *len = (my_width > my_prec) ? my_width : my_prec; + } + return s; +} + +/* + * Analyze the string to see how many parameters we need from the varargs list, + * and what their types are. We will only accept string parameters if they + * appear as a %l or %s format following an explicit parameter reference (e.g., + * %p2%s). All other parameters are numbers. + * + * 'number' counts coarsely the number of pop's we see in the string, and + * 'popcount' shows the highest parameter number in the string. We would like + * to simply use the latter count, but if we are reading termcap strings, there + * may be cases that we cannot see the explicit parameter numbers. + */ +static inline int +analyze(const char *string, char *p_is_s[NUM_PARM], int *popcount) +{ + grub_size_t len2; + int i; + int lastpop = -1; + int len; + int number = 0; + const char *cp = string; + static char dummy[] = ""; + + *popcount = 0; + + if (cp == 0) + return 0; + + if ((len2 = grub_strlen(cp)) > fmt_size) { + fmt_size = len2 + fmt_size + 2; + if ((fmt_buff = grub_realloc(fmt_buff, fmt_size*sizeof(char))) == 0) + return 0; + } + + grub_memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM); + + while ((cp - string) < (int) len2) { + if (*cp == '%') { + cp++; + cp = parse_format(cp, fmt_buff, &len); + switch (*cp) { + default: + break; + + case 'd': /* FALLTHRU */ + case 'o': /* FALLTHRU */ + case 'x': /* FALLTHRU */ + case 'X': /* FALLTHRU */ + case 'c': /* FALLTHRU */ + if (lastpop <= 0) + number++; + lastpop = -1; + break; + + case 'l': + case 's': + if (lastpop > 0) + p_is_s[lastpop - 1] = dummy; + ++number; + break; + + case 'p': + cp++; + i = (UChar(*cp) - '0'); + if (i >= 0 && i <= NUM_PARM) { + lastpop = i; + if (lastpop > *popcount) + *popcount = lastpop; + } + break; + + case 'P': + ++number; + ++cp; + break; + + case 'g': + cp++; + break; + + case '\'': + cp += 2; + lastpop = -1; + break; + + case '{': + cp++; + while (isdigit(UChar(*cp))) { + cp++; + } + break; + + case '+': + case '-': + case '*': + case '/': + case 'm': + case 'A': + case 'O': + case '&': + case '|': + case '^': + case '=': + case '<': + case '>': + lastpop = -1; + number += 2; + break; + + case '!': + case '~': + lastpop = -1; + ++number; + break; + + case 'i': + /* will add 1 to first (usually two) parameters */ + break; + } + } + if (*cp != '\0') + cp++; + } + + if (number > NUM_PARM) + number = NUM_PARM; + return number; +} + +static inline char * +tparam_internal(const char *string, va_list ap) +{ + char *p_is_s[NUM_PARM]; + long param[NUM_PARM]; + int popcount; + int number; + int len; + int level; + int x, y; + int i; + const char *cp = string; + grub_size_t len2; + static int dynamic_var[NUM_VARS]; + static int static_vars[NUM_VARS]; + + if (cp == 0) + return 0; + + out_used = out_size = fmt_size = 0; + + len2 = (int) grub_strlen(cp); + + /* + * Find the highest parameter-number referred to in the format string. + * Use this value to limit the number of arguments copied from the + * variable-length argument list. + */ + number = analyze(cp, p_is_s, &popcount); + if (fmt_buff == 0) + return 0; + + for (i = 0; i < max(popcount, number); i++) { + /* + * A few caps (such as plab_norm) have string-valued parms. + * We'll have to assume that the caller knows the difference, since + * a char* and an int may not be the same size on the stack. + */ + if (p_is_s[i] != 0) { + p_is_s[i] = va_arg(ap, char *); + } else { + param[i] = va_arg(ap, long int); + } + } + + /* + * This is a termcap compatibility hack. If there are no explicit pop + * operations in the string, load the stack in such a way that + * successive pops will grab successive parameters. That will make + * the expansion of (for example) \E[%d;%dH work correctly in termcap + * style, which means tparam() will expand termcap strings OK. + */ + stack_ptr = 0; + if (popcount == 0) { + popcount = number; + for (i = number - 1; i >= 0; i--) + npush(param[i]); + } + + while ((cp - string) < (int) len2) { + if (*cp != '%') { + save_char(UChar(*cp)); + } else { + tparam_base = cp++; + cp = parse_format(cp, fmt_buff, &len); + switch (*cp) { + default: + break; + case '%': + save_char('%'); + break; + + case 'd': /* FALLTHRU */ + case 'o': /* FALLTHRU */ + case 'x': /* FALLTHRU */ + case 'X': /* FALLTHRU */ + save_number(fmt_buff, npop(), len); + break; + + case 'c': /* FALLTHRU */ + save_char(npop()); + break; + + case 'l': + save_number("%d", (int) grub_strlen(spop()), 0); + break; + + case 's': + save_text(fmt_buff, spop(), len); + break; + + case 'p': + cp++; + i = (UChar(*cp) - '1'); + if (i >= 0 && i < NUM_PARM) { + if (p_is_s[i]) + spush(p_is_s[i]); + else + npush(param[i]); + } + break; + + case 'P': + cp++; + if (isUPPER(*cp)) { + i = (UChar(*cp) - 'A'); + static_vars[i] = npop(); + } else if (isLOWER(*cp)) { + i = (UChar(*cp) - 'a'); + dynamic_var[i] = npop(); + } + break; + + case 'g': + cp++; + if (isUPPER(*cp)) { + i = (UChar(*cp) - 'A'); + npush(static_vars[i]); + } else if (isLOWER(*cp)) { + i = (UChar(*cp) - 'a'); + npush(dynamic_var[i]); + } + break; + + case '\'': + cp++; + npush(UChar(*cp)); + cp++; + break; + + case '{': + number = 0; + cp++; + while (isdigit(UChar(*cp))) { + number = (number * 10) + (UChar(*cp) - '0'); + cp++; + } + npush(number); + break; + + case '+': + npush(npop() + npop()); + break; + + case '-': + y = npop(); + x = npop(); + npush(x - y); + break; + + case '*': + npush(npop() * npop()); + break; + + case '/': + y = npop(); + x = npop(); + npush(y ? (x / y) : 0); + break; + + case 'm': + y = npop(); + x = npop(); + npush(y ? (x % y) : 0); + break; + + case 'A': + npush(npop() && npop()); + break; + + case 'O': + npush(npop() || npop()); + break; + + case '&': + npush(npop() & npop()); + break; + + case '|': + npush(npop() | npop()); + break; + + case '^': + npush(npop() ^ npop()); + break; + + case '=': + y = npop(); + x = npop(); + npush(x == y); + break; + + case '<': + y = npop(); + x = npop(); + npush(x < y); + break; + + case '>': + y = npop(); + x = npop(); + npush(x > y); + break; + + case '!': + npush(!npop()); + break; + + case '~': + npush(~npop()); + break; + + case 'i': + if (p_is_s[0] == 0) + param[0]++; + if (p_is_s[1] == 0) + param[1]++; + break; + + case '?': + break; + + case 't': + x = npop(); + if (!x) { + /* scan forward for %e or %; at level zero */ + cp++; + level = 0; + while (*cp) { + if (*cp == '%') { + cp++; + if (*cp == '?') + level++; + else if (*cp == ';') { + if (level > 0) + level--; + else + break; + } else if (*cp == 'e' && level == 0) + break; + } + + if (*cp) + cp++; + } + } + break; + + case 'e': + /* scan forward for a %; at level zero */ + cp++; + level = 0; + while (*cp) { + if (*cp == '%') { + cp++; + if (*cp == '?') + level++; + else if (*cp == ';') { + if (level > 0) + level--; + else + break; + } + } + + if (*cp) + cp++; + } + break; + + case ';': + break; + + } /* endswitch (*cp) */ + } /* endelse (*cp == '%') */ + + if (*cp == '\0') + break; + + cp++; + } /* endwhile (*cp) */ + + get_space(1); + out_buff[out_used] = '\0'; + + return (out_buff); +} + +char * +grub_terminfo_tparm (const char *string, ...) +{ + va_list ap; + char *result; + + if (!string) + return ""; + + va_start (ap, string); + result = tparam_internal (string, ap); + va_end (ap); + return result; +} diff --git a/grub-core/term/usb_keyboard.c b/grub-core/term/usb_keyboard.c new file mode 100644 index 0000000..ae00936 --- /dev/null +++ b/grub-core/term/usb_keyboard.c @@ -0,0 +1,472 @@ +/* Support for the HID Boot Protocol. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008, 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + + + +enum + { + KEY_NO_KEY = 0x00, + KEY_ERR_BUFFER = 0x01, + KEY_ERR_POST = 0x02, + KEY_ERR_UNDEF = 0x03, + KEY_CAPS_LOCK = 0x39, + KEY_NUM_LOCK = 0x53, + }; + +enum + { + LED_NUM_LOCK = 0x01, + LED_CAPS_LOCK = 0x02 + }; + +/* Valid values for bRequest. See HID definition version 1.11 section 7.2. */ +#define USB_HID_GET_REPORT 0x01 +#define USB_HID_GET_IDLE 0x02 +#define USB_HID_GET_PROTOCOL 0x03 +#define USB_HID_SET_REPORT 0x09 +#define USB_HID_SET_IDLE 0x0A +#define USB_HID_SET_PROTOCOL 0x0B + +#define USB_HID_BOOT_SUBCLASS 0x01 +#define USB_HID_KBD_PROTOCOL 0x01 + +#define GRUB_USB_KEYBOARD_LEFT_CTRL 0x01 +#define GRUB_USB_KEYBOARD_LEFT_SHIFT 0x02 +#define GRUB_USB_KEYBOARD_LEFT_ALT 0x04 +#define GRUB_USB_KEYBOARD_RIGHT_CTRL 0x10 +#define GRUB_USB_KEYBOARD_RIGHT_SHIFT 0x20 +#define GRUB_USB_KEYBOARD_RIGHT_ALT 0x40 + +struct grub_usb_keyboard_data +{ + grub_usb_device_t usbdev; + grub_uint8_t status; + grub_uint16_t mods; + int interfno; + struct grub_usb_desc_endp *endp; + grub_usb_transfer_t transfer; + grub_uint8_t report[8]; + int dead; + int last_key; + grub_uint64_t repeat_time; + grub_uint8_t current_report[8]; + grub_uint8_t last_report[8]; + int index; + int max_index; +}; + +static int grub_usb_keyboard_getkey (struct grub_term_input *term); +static int grub_usb_keyboard_getkeystatus (struct grub_term_input *term); + +static struct grub_term_input grub_usb_keyboard_term = + { + .getkey = grub_usb_keyboard_getkey, + .getkeystatus = grub_usb_keyboard_getkeystatus, + .next = 0 + }; + +static struct grub_term_input grub_usb_keyboards[16]; + +static int +interpret_status (grub_uint8_t data0) +{ + int mods = 0; + + /* Check Shift, Control, and Alt status. */ + if (data0 & GRUB_USB_KEYBOARD_LEFT_SHIFT) + mods |= GRUB_TERM_STATUS_LSHIFT; + if (data0 & GRUB_USB_KEYBOARD_RIGHT_SHIFT) + mods |= GRUB_TERM_STATUS_RSHIFT; + if (data0 & GRUB_USB_KEYBOARD_LEFT_CTRL) + mods |= GRUB_TERM_STATUS_LCTRL; + if (data0 & GRUB_USB_KEYBOARD_RIGHT_CTRL) + mods |= GRUB_TERM_STATUS_RCTRL; + if (data0 & GRUB_USB_KEYBOARD_LEFT_ALT) + mods |= GRUB_TERM_STATUS_LALT; + if (data0 & GRUB_USB_KEYBOARD_RIGHT_ALT) + mods |= GRUB_TERM_STATUS_RALT; + + return mods; +} + +static void +grub_usb_keyboard_detach (grub_usb_device_t usbdev, + int config __attribute__ ((unused)), + int interface __attribute__ ((unused))) +{ + unsigned i; + for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++) + { + struct grub_usb_keyboard_data *data = grub_usb_keyboards[i].data; + + if (!data) + continue; + + if (data->usbdev != usbdev) + continue; + + if (data->transfer) + grub_usb_cancel_transfer (data->transfer); + + grub_term_unregister_input (&grub_usb_keyboards[i]); + grub_free ((char *) grub_usb_keyboards[i].name); + grub_usb_keyboards[i].name = NULL; + grub_free (grub_usb_keyboards[i].data); + grub_usb_keyboards[i].data = 0; + } +} + +static int +grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno) +{ + unsigned curnum; + struct grub_usb_keyboard_data *data; + struct grub_usb_desc_endp *endp = NULL; + int j; + + grub_dprintf ("usb_keyboard", "%x %x %x %d %d\n", + usbdev->descdev.class, usbdev->descdev.subclass, + usbdev->descdev.protocol, configno, interfno); + + for (curnum = 0; curnum < ARRAY_SIZE (grub_usb_keyboards); curnum++) + if (!grub_usb_keyboards[curnum].data) + break; + + if (curnum == ARRAY_SIZE (grub_usb_keyboards)) + return 0; + + if (usbdev->descdev.class != 0 + || usbdev->descdev.subclass != 0 || usbdev->descdev.protocol != 0) + return 0; + + if (usbdev->config[configno].interf[interfno].descif->subclass + != USB_HID_BOOT_SUBCLASS + || usbdev->config[configno].interf[interfno].descif->protocol + != USB_HID_KBD_PROTOCOL) + return 0; + + for (j = 0; j < usbdev->config[configno].interf[interfno].descif->endpointcnt; + j++) + { + endp = &usbdev->config[configno].interf[interfno].descendp[j]; + + if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp) + == GRUB_USB_EP_INTERRUPT) + break; + } + if (j == usbdev->config[configno].interf[interfno].descif->endpointcnt) + return 0; + + grub_dprintf ("usb_keyboard", "HID found!\n"); + + data = grub_malloc (sizeof (*data)); + if (!data) + { + grub_print_error (); + return 0; + } + + data->usbdev = usbdev; + data->interfno = interfno; + data->endp = endp; + + /* Configure device */ + grub_usb_set_configuration (usbdev, configno + 1); + + /* Place the device in boot mode. */ + grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + USB_HID_SET_PROTOCOL, 0, interfno, 0, 0); + + /* Reports every time an event occurs and not more often than that. */ + grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + USB_HID_SET_IDLE, 0<<8, interfno, 0, 0); + + grub_memcpy (&grub_usb_keyboards[curnum], &grub_usb_keyboard_term, + sizeof (grub_usb_keyboards[curnum])); + grub_usb_keyboards[curnum].data = data; + usbdev->config[configno].interf[interfno].detach_hook + = grub_usb_keyboard_detach; + grub_usb_keyboards[curnum].name = grub_xasprintf ("usb_keyboard%d", curnum); + if (!grub_usb_keyboards[curnum].name) + { + grub_print_error (); + return 0; + } + + /* Test showed that getting report may make the keyboard go nuts. + Moreover since we're reattaching keyboard it usually sends + an initial message on interrupt pipe and so we retrieve + the same keystatus. + */ +#if 0 + { + grub_uint8_t report[8]; + grub_usb_err_t err; + grub_memset (report, 0, sizeof (report)); + err = grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_IN, + USB_HID_GET_REPORT, 0x0100, interfno, + sizeof (report), (char *) report); + if (err) + data->status = 0; + else + data->status = report[0]; + } +#else + data->status = 0; +#endif + + data->transfer = grub_usb_bulk_read_background (usbdev, + data->endp->endp_addr, + sizeof (data->report), + (char *) data->report); + if (!data->transfer) + { + grub_print_error (); + return 0; + } + + data->last_key = -1; + data->mods = 0; + data->dead = 0; + + grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]); + + return 1; +} + + + +static void +send_leds (struct grub_usb_keyboard_data *termdata) +{ + char report[1]; + report[0] = 0; + if (termdata->mods & GRUB_TERM_STATUS_CAPS) + report[0] |= LED_CAPS_LOCK; + if (termdata->mods & GRUB_TERM_STATUS_NUM) + report[0] |= LED_NUM_LOCK; + grub_usb_control_msg (termdata->usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + USB_HID_SET_REPORT, 0x0200, termdata->interfno, + sizeof (report), (char *) report); + grub_errno = GRUB_ERR_NONE; +} + +static int +parse_keycode (struct grub_usb_keyboard_data *termdata) +{ + int index = termdata->index; + int i, keycode; + + /* Sanity check */ + if (index < 2) + index = 2; + + for ( ; index < termdata->max_index; index++) + { + keycode = termdata->current_report[index]; + + if (keycode == KEY_NO_KEY + || keycode == KEY_ERR_BUFFER + || keycode == KEY_ERR_POST + || keycode == KEY_ERR_UNDEF) + { + /* Don't parse (rest of) this report */ + termdata->index = 0; + if (keycode != KEY_NO_KEY) + /* Don't replace last report with current faulty report + * in future ! */ + grub_memcpy (termdata->current_report, + termdata->last_report, + sizeof (termdata->report)); + return GRUB_TERM_NO_KEY; + } + + /* Try to find current keycode in last report. */ + for (i = 2; i < 8; i++) + if (keycode == termdata->last_report[i]) + break; + if (i < 8) + /* Keycode is in last report, it means it was not released, + * ignore it. */ + continue; + + if (keycode == KEY_CAPS_LOCK) + { + termdata->mods ^= GRUB_TERM_STATUS_CAPS; + send_leds (termdata); + continue; + } + + if (keycode == KEY_NUM_LOCK) + { + termdata->mods ^= GRUB_TERM_STATUS_NUM; + send_leds (termdata); + continue; + } + + termdata->last_key = grub_term_map_key (keycode, + interpret_status (termdata->current_report[0]) + | termdata->mods); + termdata->repeat_time = grub_get_time_ms () + GRUB_TERM_REPEAT_PRE_INTERVAL; + + grub_errno = GRUB_ERR_NONE; + + index++; + if (index >= termdata->max_index) + termdata->index = 0; + else + termdata->index = index; + + return termdata->last_key; + } + + /* All keycodes parsed */ + termdata->index = 0; + return GRUB_TERM_NO_KEY; +} + +static int +grub_usb_keyboard_getkey (struct grub_term_input *term) +{ + grub_usb_err_t err; + struct grub_usb_keyboard_data *termdata = term->data; + grub_size_t actual; + int keycode = GRUB_TERM_NO_KEY; + + if (termdata->dead) + return GRUB_TERM_NO_KEY; + + if (termdata->index) + keycode = parse_keycode (termdata); + if (keycode != GRUB_TERM_NO_KEY) + return keycode; + + /* Poll interrupt pipe. */ + err = grub_usb_check_transfer (termdata->transfer, &actual); + + if (err == GRUB_USB_ERR_WAIT) + { + if (termdata->last_key != -1 + && grub_get_time_ms () > termdata->repeat_time) + { + termdata->repeat_time = grub_get_time_ms () + + GRUB_TERM_REPEAT_INTERVAL; + return termdata->last_key; + } + return GRUB_TERM_NO_KEY; + } + + if (!err && (actual >= 3)) + grub_memcpy (termdata->last_report, + termdata->current_report, + sizeof (termdata->report)); + + grub_memcpy (termdata->current_report, + termdata->report, + sizeof (termdata->report)); + + termdata->transfer = grub_usb_bulk_read_background (termdata->usbdev, + termdata->endp->endp_addr, + sizeof (termdata->report), + (char *) termdata->report); + if (!termdata->transfer) + { + grub_printf ("%s failed. Stopped\n", term->name); + termdata->dead = 1; + } + + termdata->last_key = -1; + + grub_dprintf ("usb_keyboard", + "err = %d, actual = %" PRIuGRUB_SIZE + " report: 0x%02x 0x%02x 0x%02x 0x%02x" + " 0x%02x 0x%02x 0x%02x 0x%02x\n", + err, actual, + termdata->current_report[0], termdata->current_report[1], + termdata->current_report[2], termdata->current_report[3], + termdata->current_report[4], termdata->current_report[5], + termdata->current_report[6], termdata->current_report[7]); + + if (err || actual < 1) + return GRUB_TERM_NO_KEY; + + termdata->status = termdata->current_report[0]; + + if (actual < 3) + return GRUB_TERM_NO_KEY; + + termdata->index = 2; /* New data received. */ + termdata->max_index = actual; + + return parse_keycode (termdata); +} + +static int +grub_usb_keyboard_getkeystatus (struct grub_term_input *term) +{ + struct grub_usb_keyboard_data *termdata = term->data; + + return interpret_status (termdata->status) | termdata->mods; +} + +static struct grub_usb_attach_desc attach_hook = +{ + .class = GRUB_USB_CLASS_HID, + .hook = grub_usb_keyboard_attach +}; + +GRUB_MOD_INIT(usb_keyboard) +{ + grub_usb_register_attach_hook_class (&attach_hook); +} + +GRUB_MOD_FINI(usb_keyboard) +{ + unsigned i; + for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++) + if (grub_usb_keyboards[i].data) + { + struct grub_usb_keyboard_data *data = grub_usb_keyboards[i].data; + + if (!data) + continue; + + if (data->transfer) + grub_usb_cancel_transfer (data->transfer); + + grub_term_unregister_input (&grub_usb_keyboards[i]); + grub_free ((char *) grub_usb_keyboards[i].name); + grub_usb_keyboards[i].name = NULL; + grub_free (grub_usb_keyboards[i].data); + grub_usb_keyboards[i].data = 0; + } + grub_usb_unregister_attach_hook_class (&attach_hook); +} -- cgit v1.2.3