2009-02-08 Robert Millan
Implement USB keyboard support (based on patch by Marco Gerards)
* conf/i386-pc.rmk (pkglib_MODULES): Add `usb_keyboard.mod'.
(usb_keyboard_mod_SOURCES, usb_keyboard_mod_CFLAGS)
(usb_keyboard_mod_LDFLAGS): New variables.
* term/usb_keyboard.c: New file.
Index: conf/i386-pc.rmk
===================================================================
--- conf/i386-pc.rmk (revision 1982)
+++ conf/i386-pc.rmk (working copy)
@@ -172,7 +172,7 @@ pkglib_MODULES = biosdisk.mod _chain.mod
ata.mod vga.mod memdisk.mod pci.mod lspci.mod \
aout.mod _bsd.mod bsd.mod pxe.mod pxecmd.mod datetime.mod date.mod \
datehook.mod lsmmap.mod \
- usb.mod uhci.mod ohci.mod usbtest.mod usbms.mod
+ usb.mod uhci.mod ohci.mod usbtest.mod usbms.mod usb_keyboard.mod
# For biosdisk.mod.
biosdisk_mod_SOURCES = disk/i386/pc/biosdisk.c
@@ -333,6 +333,11 @@ usbms_mod_SOURCES = disk/usbms.c
usbms_mod_CFLAGS = $(COMMON_CFLAGS)
usbms_mod_LDFLAGS = $(COMMON_LDFLAGS)
+# For usb_keyboard.mod
+usb_keyboard_mod_SOURCES = term/usb_keyboard.c
+usb_keyboard_mod_CFLAGS = $(COMMON_CFLAGS)
+usb_keyboard_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
# For pxe.mod
pxe_mod_SOURCES = fs/i386/pc/pxe.c
pxe_mod_CFLAGS = $(COMMON_CFLAGS)
Index: term/usb_keyboard.c
===================================================================
--- term/usb_keyboard.c (revision 0)
+++ term/usb_keyboard.c (revision 0)
@@ -0,0 +1,257 @@
+/* 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
+#include
+
+
+static char keyboard_map[128] =
+ {
+ '\0', '\0', '\0', '\0', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z', '1', '2',
+ '3', '4', '5', '6', '7', '8', '9', '0',
+ '\n', GRUB_TERM_ESC, GRUB_TERM_BACKSPACE, GRUB_TERM_TAB, ' ', '-', '=', '[',
+ ']', '\\', '#', ';', '\'', '`', ',', '.',
+ '/', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', GRUB_TERM_HOME, GRUB_TERM_PPAGE, GRUB_TERM_DC, GRUB_TERM_END, GRUB_TERM_NPAGE, GRUB_TERM_RIGHT,
+ GRUB_TERM_LEFT, GRUB_TERM_DOWN, GRUB_TERM_UP
+ };
+
+static char keyboard_map_shift[128] =
+ {
+ '\0', '\0', '\0', '\0', 'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@',
+ '#', '$', '%', '^', '&', '*', '(', ')',
+ '\n', '\0', '\0', '\0', ' ', '_', '+', '{',
+ '}', '|', '#', ':', '"', '`', '<', '>',
+ '?'
+ };
+
+static grub_usb_device_t usbdev;
+
+static void
+grub_usb_hid (void)
+{
+ struct grub_usb_desc_device *descdev;
+
+ auto int usb_iterate (grub_usb_device_t dev);
+ int usb_iterate (grub_usb_device_t dev)
+ {
+ descdev = &dev->descdev;
+
+ grub_dprintf ("usb_keyboard", "%x %x %x\n",
+ descdev->class, descdev->subclass, descdev->protocol);
+
+#if 0
+ if (descdev->class != 0x09
+ || descdev->subclass == 0x01
+ || descdev->protocol != 0x02)
+ return 0;
+#endif
+
+ if (descdev->class != 0 || descdev->subclass != 0 || descdev->protocol != 0)
+ return 0;
+
+ grub_printf ("HID found!\n");
+
+ usbdev = dev;
+
+ return 1;
+ }
+ grub_usb_iterate (usb_iterate);
+
+ /* Place the device in boot mode. */
+ grub_usb_control_msg (usbdev, 0x21, 0x0B, 0, 0, 0, 0);
+
+ /* Reports everytime an event occurs and not more often than that. */
+ grub_usb_control_msg (usbdev, 0x21, 0x0A, 0<<8, 0, 0, 0);
+}
+
+static grub_err_t
+grub_usb_keyboard_getreport (grub_usb_device_t dev, unsigned char *report)
+{
+ return grub_usb_control_msg (dev, (1 << 7) | (1 << 5) | 1, 0x01, 0, 0,
+ 8, (char *) report);
+}
+
+
+
+static int
+grub_usb_keyboard_checkkey (void)
+{
+ unsigned char data[8];
+ int key;
+ int i;
+ grub_err_t err;
+
+ data[2] = 0;
+ for (i = 0; i < 50; i++)
+ {
+ /* Get_Report. */
+ err = grub_usb_keyboard_getreport (usbdev, data);
+
+ if (! err && data[2])
+ break;
+ }
+
+ if (err || !data[2])
+ return -1;
+
+ grub_dprintf ("usb_keyboard",
+ "report: 0x%02x 0x%02x 0x%02x 0x%02x"
+ " 0x%02x 0x%02x 0x%02x 0x%02x\n",
+ data[0], data[1], data[2], data[3],
+ data[4], data[5], data[6], data[7]);
+
+ /* Check if the Control or Shift key was pressed. */
+ if (data[0] & 0x01 || data[0] & 0x10)
+ key = keyboard_map[data[2]] - 'a' + 1;
+ else if (data[0] & 0x02 || data[0] & 0x20)
+ key = keyboard_map_shift[data[2]];
+ else
+ key = keyboard_map[data[2]];
+
+ if (key == 0)
+ grub_printf ("Unknown key 0x%x detected\n", data[2]);
+
+#if 0
+ /* Wait until the key is released. */
+ while (!err && data[2])
+ {
+ err = grub_usb_control_msg (usbdev, (1 << 7) | (1 << 5) | 1, 0x01, 0, 0,
+ sizeof (data), (char *) data);
+ grub_dprintf ("usb_keyboard",
+ "report2: 0x%02x 0x%02x 0x%02x 0x%02x"
+ " 0x%02x 0x%02x 0x%02x 0x%02x\n",
+ data[0], data[1], data[2], data[3],
+ data[4], data[5], data[6], data[7]);
+ }
+#endif
+
+ grub_errno = GRUB_ERR_NONE;
+
+ return key;
+}
+
+typedef enum
+{
+ GRUB_HIDBOOT_REPEAT_NONE,
+ GRUB_HIDBOOT_REPEAT_FIRST,
+ GRUB_HIDBOOT_REPEAT
+} grub_usb_keyboard_repeat_t;
+
+static int
+grub_usb_keyboard_getkey (void)
+{
+ int key;
+ int key_release;
+ grub_err_t err;
+ unsigned char data[8];
+ grub_uint64_t currtime;
+ int timeout;
+ static grub_usb_keyboard_repeat_t repeat = GRUB_HIDBOOT_REPEAT_NONE;
+
+ again:
+
+ do
+ {
+ key = grub_usb_keyboard_checkkey ();
+ } while (key == -1);
+
+ data[2] = !0; /* Or whatever. */
+ err = 0;
+
+ switch (repeat)
+ {
+ case GRUB_HIDBOOT_REPEAT_NONE:
+ timeout = 100;
+ break;
+ case GRUB_HIDBOOT_REPEAT_FIRST:
+ timeout = 500;
+ break;
+ case GRUB_HIDBOOT_REPEAT:
+ timeout = 50;
+ break;
+ }
+
+ /* Wait until the key is released. */
+ currtime = grub_get_time_ms ();
+ while (!err && data[2])
+ {
+ /* Implement a timeout. */
+ if (grub_get_time_ms () > currtime + timeout)
+ {
+ if (repeat == 0)
+ repeat = 1;
+ else
+ repeat = 2;
+
+ grub_errno = GRUB_ERR_NONE;
+ return key;
+ }
+
+ err = grub_usb_keyboard_getreport (usbdev, data);
+ }
+
+ if (repeat)
+ {
+ repeat = 0;
+ goto again;
+ }
+
+ repeat = 0;
+
+ grub_errno = GRUB_ERR_NONE;
+
+ return key;
+}
+
+static struct grub_term_input grub_usb_keyboard_term =
+ {
+ .name = "usb_keyboard",
+ .checkkey = grub_usb_keyboard_checkkey,
+ .getkey = grub_usb_keyboard_getkey,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(usb_keyboard)
+{
+ (void) mod; /* To stop warning. */
+
+ grub_usb_hid ();
+ grub_term_register_input (&grub_usb_keyboard_term);
+}
+
+GRUB_MOD_FINI(usb_keyboard)
+{
+ grub_term_unregister_input (&grub_usb_keyboard_term);
+}