/* 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 static int 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_KEY_HOME, GRUB_TERM_KEY_PPAGE, GRUB_TERM_KEY_DC, GRUB_TERM_KEY_END, GRUB_TERM_KEY_NPAGE, GRUB_TERM_KEY_RIGHT, GRUB_TERM_KEY_LEFT, GRUB_TERM_KEY_DOWN, GRUB_TERM_KEY_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; /* Valid values for bmRequestType. See HID definition version 1.11 section 7.2. */ #define USB_HID_HOST_TO_DEVICE 0x21 #define USB_HID_DEVICE_TO_HOST 0xA1 /* 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 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, USB_HID_HOST_TO_DEVICE, USB_HID_SET_PROTOCOL, 0, 0, 0, 0); /* Reports every time an event occurs and not more often than that. */ grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_IDLE, 0<<8, 0, 0, 0); } static grub_err_t grub_usb_keyboard_getreport (grub_usb_device_t dev, grub_uint8_t *report) { return grub_usb_control_msg (dev, USB_HID_DEVICE_TO_HOST, USB_HID_GET_REPORT, 0, 0, 8, (char *) report); } static int grub_usb_keyboard_checkkey (void) { grub_uint8_t data[8]; int key; grub_err_t err; grub_uint64_t currtime; int timeout = 50; data[2] = 0; currtime = grub_get_time_ms (); do { /* Get_Report. */ err = grub_usb_keyboard_getreport (usbdev, data); /* Implement a timeout. */ if (grub_get_time_ms () > currtime + timeout) break; } while (err || !data[2]); 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, USB_HID_DEVICE_TO_HOST, USB_HID_GET_REPORT, 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; grub_err_t err; grub_uint8_t 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_FIRST: timeout = 500; break; case GRUB_HIDBOOT_REPEAT: timeout = 50; break; default: timeout = 100; 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 int grub_usb_keyboard_getkeystatus (void) { grub_uint8_t data[8]; int mods = 0; grub_err_t err; grub_uint64_t currtime; int timeout = 50; /* Set idle time to the minimum offered by the spec (4 milliseconds) so that we can find out the current state. */ grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_IDLE, 0<<8, 0, 0, 0); currtime = grub_get_time_ms (); do { /* Get_Report. */ err = grub_usb_keyboard_getreport (usbdev, data); /* Implement a timeout. */ if (grub_get_time_ms () > currtime + timeout) break; } while (err || !data[0]); /* Go back to reporting every time an event occurs and not more often than that. */ grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_IDLE, 0<<8, 0, 0, 0); /* We allowed a while for modifiers to show up in the report, but it is not an error if they never did. */ if (err) 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 Shift, Control, and Alt status. */ if (data[0] & 0x02 || data[0] & 0x20) mods |= GRUB_TERM_STATUS_SHIFT; if (data[0] & 0x01 || data[0] & 0x10) mods |= GRUB_TERM_STATUS_CTRL; if (data[0] & 0x04 || data[0] & 0x40) mods |= GRUB_TERM_STATUS_ALT; grub_errno = GRUB_ERR_NONE; return mods; } static struct grub_term_input grub_usb_keyboard_term = { .name = "usb_keyboard", .checkkey = grub_usb_keyboard_checkkey, .getkey = grub_usb_keyboard_getkey, .getkeystatus = grub_usb_keyboard_getkeystatus, .next = 0 }; GRUB_MOD_INIT(usb_keyboard) { grub_usb_hid (); grub_term_register_input ("usb_keyboard", &grub_usb_keyboard_term); } GRUB_MOD_FINI(usb_keyboard) { grub_term_unregister_input (&grub_usb_keyboard_term); }