/* 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 grub_uint8_t usb_to_at_map[128] = { /* 0x00 */ 0x00, 0x00, /* 0x02 */ 0x00, 0x00, /* 0x04 */ 0x1e /* a */, 0x30 /* b */, /* 0x06 */ 0x2e /* c */, 0x20 /* d */, /* 0x08 */ 0x12 /* e */, 0x21 /* f */, /* 0x0a */ 0x22 /* g */, 0x23 /* h */, /* 0x0c */ 0x17 /* i */, 0x24 /* j */, /* 0x0e */ 0x25 /* k */, 0x26 /* l */, /* 0x10 */ 0x32 /* m */, 0x31 /* n */, /* 0x12 */ 0x18 /* o */, 0x19 /* p */, /* 0x14 */ 0x10 /* q */, 0x13 /* r */, /* 0x16 */ 0x1f /* s */, 0x14 /* t */, /* 0x18 */ 0x16 /* u */, 0x2f /* v */, /* 0x1a */ 0x11 /* w */, 0x2d /* x */, /* 0x1c */ 0x15 /* y */, 0x2c /* z */, /* 0x1e */ 0x02 /* 1 */, 0x03 /* 2 */, /* 0x20 */ 0x04 /* 3 */, 0x05 /* 4 */, /* 0x22 */ 0x06 /* 5 */, 0x07 /* 6 */, /* 0x24 */ 0x08 /* 7 */, 0x09 /* 8 */, /* 0x26 */ 0x0a /* 9 */, 0x0b /* 0 */, /* 0x28 */ 0x1c /* Enter */, 0x01 /* Escape */, /* 0x2a */ 0x0e /* \b */, 0x0f /* \t */, /* 0x2c */ 0x39 /* Space */, 0x0c /* - */, /* 0x2e */ 0x0d /* = */, 0x1a /* [ */, /* According to usage table 0x31 should be remapped to 0x2b but testing with real keyboard shows that 0x32 is remapped to 0x2b. */ /* 0x30 */ 0x1b /* ] */, 0x00, /* 0x32 */ 0x2b /* \ */, 0x27 /* ; */, /* 0x34 */ 0x28 /* " */, 0x29 /* ` */, /* 0x36 */ 0x33 /* , */, 0x34 /* . */, /* 0x38 */ 0x35 /* / */, 0x00, /* 0x3a */ 0x3b /* F1 */, 0x3c /* F2 */, /* 0x3c */ 0x3d /* F3 */, 0x3e /* F4 */, /* 0x3e */ 0x3f /* F5 */, 0x40 /* F6 */, /* 0x40 */ 0x41 /* F7 */, 0x42 /* F8 */, /* 0x42 */ 0x43 /* F9 */, 0x44 /* F10 */, /* 0x44 */ 0x57 /* F11 */, 0x58 /* F12 */, /* 0x46 */ 0x00, 0x00, /* 0x48 */ 0x00, 0x52 /* Insert */, /* 0x4a */ 0x47 /* HOME */, 0x51 /* PPAGE */, /* 0x4c */ 0x53 /* DC */, 0x4f /* END */, /* 0x4e */ 0x49 /* NPAGE */, 0x4d /* RIGHT */, /* 0x50 */ 0x4b /* LEFT */, 0x50 /* DOWN */, /* 0x52 */ 0x48 /* UP */, 0x00, /* 0x54 */ 0x00, 0x00, /* 0x56 */ 0x00, 0x00, /* 0x58 */ 0x00, 0x00, /* 0x5a */ 0x00, 0x00, /* 0x5c */ 0x00, 0x00, /* 0x5e */ 0x00, 0x00, /* 0x60 */ 0x00, 0x00, /* 0x62 */ 0x00, 0x00, /* 0x64 */ 0x56 /* 102nd key. */, 0x00, /* 0x66 */ 0x00, 0x00, /* 0x68 */ 0x00, 0x00, /* 0x6a */ 0x00, 0x00, /* 0x6c */ 0x00, 0x00, /* 0x6e */ 0x00, 0x00, /* 0x70 */ 0x00, 0x00, /* 0x72 */ 0x00, 0x00, /* 0x74 */ 0x00, 0x00, /* 0x76 */ 0x00, 0x00, /* 0x78 */ 0x00, 0x00, /* 0x7a */ 0x00, 0x00, /* 0x7c */ 0x00, 0x00, /* 0x7e */ 0x00, 0x00, }; /* 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 #define GRUB_USB_KEYBOARD_LEFT_CTRL 0x01 #define GRUB_USB_KEYBOARD_LEFT_SHIFT 0x02 #define GRUB_USB_KEYBOARD_LEFT_ALT 0x04 #define GRUB_USB_KEYBOARD_RIGHT_CTRL 0x10 #define GRUB_USB_KEYBOARD_RIGHT_SHIFT 0x20 #define GRUB_USB_KEYBOARD_RIGHT_ALT 0x40 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 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 }; static int interpret_status (grub_uint8_t data0) { int mods = 0; /* Check Shift, Control, and Alt status. */ if (data0 & GRUB_USB_KEYBOARD_LEFT_SHIFT) mods |= GRUB_TERM_STATUS_LSHIFT; if (data0 & GRUB_USB_KEYBOARD_RIGHT_SHIFT) mods |= GRUB_TERM_STATUS_RSHIFT; if (data0 & GRUB_USB_KEYBOARD_LEFT_CTRL) mods |= GRUB_TERM_STATUS_LCTRL; if (data0 & GRUB_USB_KEYBOARD_RIGHT_CTRL) mods |= GRUB_TERM_STATUS_RCTRL; if (data0 & GRUB_USB_KEYBOARD_LEFT_ALT) mods |= GRUB_TERM_STATUS_LALT; if (data0 & GRUB_USB_KEYBOARD_RIGHT_ALT) mods |= GRUB_TERM_STATUS_RALT; return mods; } 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]); if (usb_to_at_map[data[2]] == 0) grub_printf ("Unknown key 0x%x detected\n", data[2]); else termdata->key = grub_term_map_key (usb_to_at_map[data[2]], interpret_status (data[0])); 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; return interpret_status (termdata->status); } 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); }