/* 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 enum { KEY_NO_KEY = 0x00, KEY_ERR_BUFFER = 0x01, KEY_ERR_POST = 0x02, KEY_ERR_UNDEF = 0x03, KEY_CAPS_LOCK = 0x39, KEY_NUM_LOCK = 0x53, }; enum { LED_NUM_LOCK = 0x01, LED_CAPS_LOCK = 0x02 }; /* 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; grub_uint16_t mods; int key; int interfno; struct grub_usb_desc_endp *endp; grub_usb_transfer_t transfer; grub_uint8_t report[8]; int dead; int last_key; grub_uint64_t repeat_time; }; 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 struct grub_term_input grub_usb_keyboards[16]; 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; if (data->transfer) grub_usb_cancel_transfer (data->transfer); 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->interfno = interfno; 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, interfno, 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, interfno, 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; } /* Test showed that getting report may make the keyboard go nuts. Moreover since we're reattaching keyboard it usually sends an initial message on interrupt pipe and so we retrieve the same keystatus. */ #if 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, 0x0100, interfno, sizeof (report), (char *) report); if (err) { data->status = 0; data->key = -1; } else { data->status = report[0]; data->key = report[2] ? : -1; } } #else data->status = 0; data->key = -1; #endif data->transfer = grub_usb_bulk_read_background (usbdev, data->endp->endp_addr, sizeof (data->report), (char *) data->report); if (!data->transfer) { grub_print_error (); return 0; } data->last_key = -1; data->mods = 0; data->dead = 0; grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]); return 1; } static void send_leds (struct grub_usb_keyboard_data *termdata) { char report[1]; report[0] = 0; if (termdata->mods & GRUB_TERM_STATUS_CAPS) report[0] |= LED_CAPS_LOCK; if (termdata->mods & GRUB_TERM_STATUS_NUM) report[0] |= LED_NUM_LOCK; grub_usb_control_msg (termdata->usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, USB_HID_SET_REPORT, 0x0200, termdata->interfno, sizeof (report), (char *) report); grub_errno = GRUB_ERR_NONE; } static int grub_usb_keyboard_checkkey (struct grub_term_input *term) { grub_usb_err_t err; struct grub_usb_keyboard_data *termdata = term->data; grub_uint8_t data[sizeof (termdata->report)]; grub_size_t actual; if (termdata->key != -1) return termdata->key; if (termdata->dead) return -1; /* Poll interrupt pipe. */ err = grub_usb_check_transfer (termdata->transfer, &actual); if (err == GRUB_USB_ERR_WAIT) { if (termdata->last_key != -1 && grub_get_time_ms () > termdata->repeat_time) { termdata->key = termdata->last_key; termdata->repeat_time = grub_get_time_ms () + GRUB_TERM_REPEAT_INTERVAL; } return termdata->key; } grub_memcpy (data, termdata->report, sizeof (data)); termdata->transfer = grub_usb_bulk_read_background (termdata->usbdev, termdata->endp->endp_addr, sizeof (termdata->report), (char *) termdata->report); if (!termdata->transfer) { grub_printf ("%s failed. Stopped\n", term->name); termdata->dead = 1; } termdata->last_key = -1; grub_dprintf ("usb_keyboard", "err = %d, actual = %d report: 0x%02x 0x%02x 0x%02x 0x%02x" " 0x%02x 0x%02x 0x%02x 0x%02x\n", err, actual, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); if (err || actual < 1) return -1; termdata->status = data[0]; if (actual < 3) return -1; if (data[2] == KEY_NO_KEY || data[2] == KEY_ERR_BUFFER || data[2] == KEY_ERR_POST || data[2] == KEY_ERR_UNDEF) return -1; if (data[2] == KEY_CAPS_LOCK) { termdata->mods ^= GRUB_TERM_STATUS_CAPS; send_leds (termdata); return -1; } if (data[2] == KEY_NUM_LOCK) { termdata->mods ^= GRUB_TERM_STATUS_NUM; send_leds (termdata); return -1; } termdata->last_key = termdata->key = grub_term_map_key (data[2], interpret_status (data[0]) | termdata->mods); termdata->repeat_time = grub_get_time_ms () + GRUB_TERM_REPEAT_PRE_INTERVAL; 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; grub_usb_keyboard_checkkey (term); return interpret_status (termdata->status) | termdata->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) { struct grub_usb_keyboard_data *data = grub_usb_keyboards[i].data; if (!data) continue; if (data->transfer) grub_usb_cancel_transfer (data->transfer); 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; } grub_usb_unregister_attach_hook_class (&attach_hook); }