diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
commit | 485f6ecd453d8a2fd8b9b9fadea03159d8b50797 (patch) | |
tree | 32451fa3cdd9321fb2591fada9891b2cb70a9cd1 /grub-core/term/at_keyboard.c | |
parent | Initial commit. (diff) | |
download | grub2-upstream.tar.xz grub2-upstream.zip |
Adding upstream version 2.06.upstream/2.06upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'grub-core/term/at_keyboard.c')
-rw-r--r-- | grub-core/term/at_keyboard.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/grub-core/term/at_keyboard.c b/grub-core/term/at_keyboard.c new file mode 100644 index 0000000..5971110 --- /dev/null +++ b/grub-core/term/at_keyboard.c @@ -0,0 +1,332 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/dl.h> +#include <grub/at_keyboard.h> +#include <grub/cpu/at_keyboard.h> +#include <grub/cpu/io.h> +#include <grub/misc.h> +#include <grub/term.h> +#include <grub/time.h> +#include <grub/loader.h> +#include <grub/ps2.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_uint8_t grub_keyboard_controller_orig; +static grub_uint8_t grub_keyboard_orig_set; +struct grub_ps2_state ps2_state; + +static int ping_sent; + +static void +grub_keyboard_controller_init (void); + +static void +keyboard_controller_wait_until_ready (void) +{ + /* 50 us would be enough but our current time resolution is 1ms. */ + grub_millisleep (1); + 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 { + keyboard_controller_wait_until_ready (); + 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_LOONGSON) || defined (GRUB_MACHINE_QEMU) || defined (GRUB_MACHINE_COREBOOT) || defined (GRUB_MACHINE_MIPS_QEMU_MIPS) +#define USE_SCANCODE_SET 1 +#else +#define USE_SCANCODE_SET 0 +#endif + +#if !USE_SCANCODE_SET + +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; + + do { + keyboard_controller_wait_until_ready (); + 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"); + ps2_state.current_set = 1; + return; + } + +#if !USE_SCANCODE_SET + ps2_state.current_set = 1; + return; +#else + + grub_keyboard_controller_write (grub_keyboard_controller_orig + & ~KEYBOARD_AT_TRANSLATE + & ~KEYBOARD_AT_DISABLE); + + keyboard_controller_wait_until_ready (); + grub_outb (KEYBOARD_COMMAND_ENABLE, KEYBOARD_REG_DATA); + + write_mode (2); + ps2_state.current_set = query_mode (); + grub_dprintf ("atkeyb", "returned set %d\n", ps2_state.current_set); + if (ps2_state.current_set == 2) + return; + + write_mode (1); + ps2_state.current_set = query_mode (); + grub_dprintf ("atkeyb", "returned set %d\n", ps2_state.current_set); + if (ps2_state.current_set == 1) + return; + grub_dprintf ("atkeyb", "no supported scancode set found\n"); +#endif +} + +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); +} + +int +grub_at_keyboard_is_alive (void) +{ + if (ps2_state.current_set != 0) + return 1; + if (ping_sent + && KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS)) + && grub_inb (KEYBOARD_REG_DATA) == 0x55) + { + grub_keyboard_controller_init (); + return 1; + } + + if (KEYBOARD_COMMAND_ISREADY (grub_inb (KEYBOARD_REG_STATUS))) + { + grub_outb (0xaa, KEYBOARD_REG_STATUS); + ping_sent = 1; + } + return 0; +} + +/* 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))) +{ + grub_uint8_t at_key; + int ret; + grub_uint8_t old_led; + + if (!grub_at_keyboard_is_alive ()) + return GRUB_TERM_NO_KEY; + + if (! KEYBOARD_ISREADY (grub_inb (KEYBOARD_REG_STATUS))) + return GRUB_TERM_NO_KEY; + at_key = grub_inb (KEYBOARD_REG_DATA); + old_led = ps2_state.led_status; + + ret = grub_ps2_process_incoming_byte (&ps2_state, at_key); + if (old_led != ps2_state.led_status) + keyboard_controller_led (ps2_state.led_status); + return ret; +} + +static void +grub_keyboard_controller_init (void) +{ + ps2_state.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_LOONGSON) || defined (GRUB_MACHINE_MIPS_QEMU_MIPS) + grub_keyboard_controller_orig = 0; + grub_keyboard_orig_set = 2; +#elif defined (GRUB_MACHINE_QEMU) || defined (GRUB_MACHINE_COREBOOT) + /* *BSD relies on those settings. */ + grub_keyboard_controller_orig = KEYBOARD_AT_TRANSLATE; + 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 (ps2_state.led_status); +} + +static grub_err_t +grub_keyboard_controller_fini (struct grub_term_input *term __attribute__ ((unused))) +{ + if (ps2_state.current_set == 0) + return GRUB_ERR_NONE; + 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) +{ + if (ps2_state.current_set == 0) + return GRUB_ERR_NONE; + + /* 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 (ps2_state.led_status); + + return GRUB_ERR_NONE; +} + + +static struct grub_term_input grub_at_keyboard_term = + { + .name = "at_keyboard", + .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); +} |