/* 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 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', ' ', '_', '+', '{', '}', '|', '#', ':', '"', '`', '<', '>', '?' }; /* 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 #define USB_HID_BOOT_SUBCLASS 0x01 #define USB_HID_KBD_PROTOCOL 0x01 static int grub_usb_keyboard_checkkey (struct grub_term_input *term); static int grub_usb_keyboard_getkey (struct grub_term_input *term); static int grub_usb_keyboard_getkeystatus (struct grub_term_input *term); static struct grub_term_input grub_usb_keyboard_term = { .checkkey = grub_usb_keyboard_checkkey, .getkey = grub_usb_keyboard_getkey, .getkeystatus = grub_usb_keyboard_getkeystatus, .next = 0 }; struct grub_usb_keyboard_data { grub_usb_device_t usbdev; grub_uint8_t status; int key; struct grub_usb_desc_endp *endp; }; static struct grub_term_input grub_usb_keyboards[16]; static void grub_usb_keyboard_detach (grub_usb_device_t usbdev, int config __attribute__ ((unused)), int interface __attribute__ ((unused))) { unsigned i; for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++) { struct grub_usb_keyboard_data *data = grub_usb_keyboards[i].data; if (!data) continue; if (data->usbdev != usbdev) continue; grub_term_unregister_input (&grub_usb_keyboards[i]); grub_free ((char *) grub_usb_keyboards[i].name); grub_usb_keyboards[i].name = NULL; grub_free (grub_usb_keyboards[i].data); grub_usb_keyboards[i].data = 0; } } static int grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno) { unsigned curnum; struct grub_usb_keyboard_data *data; struct grub_usb_desc_endp *endp = NULL; int j; grub_dprintf ("usb_keyboard", "%x %x %x %d %d\n", usbdev->descdev.class, usbdev->descdev.subclass, usbdev->descdev.protocol, configno, interfno); for (curnum = 0; curnum < ARRAY_SIZE (grub_usb_keyboards); curnum++) if (!grub_usb_keyboards[curnum].data) break; if (curnum == ARRAY_SIZE (grub_usb_keyboards)) return 0; if (usbdev->descdev.class != 0 || usbdev->descdev.subclass != 0 || usbdev->descdev.protocol != 0) return 0; if (usbdev->config[configno].interf[interfno].descif->subclass != USB_HID_BOOT_SUBCLASS || usbdev->config[configno].interf[interfno].descif->protocol != USB_HID_KBD_PROTOCOL) return 0; for (j = 0; j < usbdev->config[configno].interf[interfno].descif->endpointcnt; j++) { endp = &usbdev->config[configno].interf[interfno].descendp[j]; if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp) == GRUB_USB_EP_INTERRUPT) break; } if (j == usbdev->config[configno].interf[interfno].descif->endpointcnt) return 0; grub_dprintf ("usb_keyboard", "HID found!\n"); data = grub_malloc (sizeof (*data)); if (!data) { grub_print_error (); return 0; } data->usbdev = usbdev; data->endp = endp; /* Place the device in boot mode. */ grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, 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, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, USB_HID_SET_IDLE, 0<<8, 0, 0, 0); grub_memcpy (&grub_usb_keyboards[curnum], &grub_usb_keyboard_term, sizeof (grub_usb_keyboards[curnum])); grub_usb_keyboards[curnum].data = data; usbdev->config[configno].interf[interfno].detach_hook = grub_usb_keyboard_detach; grub_usb_keyboards[curnum].name = grub_xasprintf ("usb_keyboard%d", curnum); if (!grub_usb_keyboards[curnum].name) { grub_print_error (); return 0; } { grub_uint8_t report[8]; grub_usb_err_t err; grub_memset (report, 0, sizeof (report)); err = grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_IN, USB_HID_GET_REPORT, 0x0000, interfno, sizeof (report), (char *) report); if (err) { data->status = 0; data->key = -1; } else { data->status = report[0]; data->key = report[2] ? : -1; } } grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]); return 1; } static int grub_usb_keyboard_checkkey (struct grub_term_input *term) { grub_uint8_t data[8]; grub_usb_err_t err; struct grub_usb_keyboard_data *termdata = term->data; grub_size_t actual; if (termdata->key != -1) return termdata->key; data[2] = 0; /* Poll interrupt pipe. */ err = grub_usb_bulk_read_extended (termdata->usbdev, termdata->endp->endp_addr, sizeof (data), (char *) data, 10, &actual); if (err || actual < 1) return -1; termdata->status = data[0]; if (actual < 3 || !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) termdata->key = keyboard_map[data[2]] - 'a' + 1; else if (data[0] & 0x02 || data[0] & 0x20) termdata->key = keyboard_map_shift[data[2]]; else termdata->key = keyboard_map[data[2]]; if (termdata->key == 0) grub_printf ("Unknown key 0x%x detected\n", data[2]); grub_errno = GRUB_ERR_NONE; return termdata->key; } static int grub_usb_keyboard_getkey (struct grub_term_input *term) { int ret; struct grub_usb_keyboard_data *termdata = term->data; while (termdata->key == -1) grub_usb_keyboard_checkkey (term); ret = termdata->key; termdata->key = -1; return ret; } static int grub_usb_keyboard_getkeystatus (struct grub_term_input *term) { struct grub_usb_keyboard_data *termdata = term->data; int mods = 0; /* Check Shift, Control, and Alt status. */ if (termdata->status & 0x02 || termdata->status & 0x20) mods |= GRUB_TERM_STATUS_SHIFT; if (termdata->status & 0x01 || termdata->status & 0x10) mods |= GRUB_TERM_STATUS_CTRL; if (termdata->status & 0x04 || termdata->status & 0x40) mods |= GRUB_TERM_STATUS_ALT; return mods; } struct grub_usb_attach_desc attach_hook = { .class = GRUB_USB_CLASS_HID, .hook = grub_usb_keyboard_attach }; GRUB_MOD_INIT(usb_keyboard) { grub_usb_register_attach_hook_class (&attach_hook); } GRUB_MOD_FINI(usb_keyboard) { unsigned i; for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++) if (grub_usb_keyboards[i].data) { grub_term_unregister_input (&grub_usb_keyboards[i]); grub_free ((char *) grub_usb_keyboards[i].name); grub_usb_keyboards[i].name = NULL; grub_usb_keyboards[i].data = 0; } grub_usb_unregister_attach_hook_class (&attach_hook); }