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); +}