summaryrefslogtreecommitdiffstats
path: root/grub-core/term/at_keyboard.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/term/at_keyboard.c')
-rw-r--r--grub-core/term/at_keyboard.c332
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);
+}