123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- /* 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 <http://www.gnu.org/licenses/>.
- */
- #include <grub/term.h>
- #include <grub/time.h>
- #include <grub/misc.h>
- #include <grub/term.h>
- #include <grub/usb.h>
- #include <grub/dl.h>
- #include <grub/time.h>
- #include <grub/keyboard_layouts.h>
- GRUB_MOD_LICENSE ("GPLv3+");
- 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 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;
- grub_uint8_t current_report[8];
- grub_uint8_t last_report[8];
- int index;
- int max_index;
- };
- 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 =
- {
- .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;
- /* Configure device */
- grub_usb_set_configuration (usbdev, configno + 1);
- /* 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;
- else
- data->status = report[0];
- }
- #else
- data->status = 0;
- #endif
- data->transfer = grub_usb_bulk_read_background (usbdev,
- data->endp,
- 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
- parse_keycode (struct grub_usb_keyboard_data *termdata)
- {
- int index = termdata->index;
- int i, keycode;
- /* Sanity check */
- if (index < 2)
- index = 2;
- for ( ; index < termdata->max_index; index++)
- {
- keycode = termdata->current_report[index];
- if (keycode == KEY_NO_KEY
- || keycode == KEY_ERR_BUFFER
- || keycode == KEY_ERR_POST
- || keycode == KEY_ERR_UNDEF)
- {
- /* Don't parse (rest of) this report */
- termdata->index = 0;
- if (keycode != KEY_NO_KEY)
- /* Don't replace last report with current faulty report
- * in future ! */
- grub_memcpy (termdata->current_report,
- termdata->last_report,
- sizeof (termdata->report));
- return GRUB_TERM_NO_KEY;
- }
- /* Try to find current keycode in last report. */
- for (i = 2; i < 8; i++)
- if (keycode == termdata->last_report[i])
- break;
- if (i < 8)
- /* Keycode is in last report, it means it was not released,
- * ignore it. */
- continue;
- if (keycode == KEY_CAPS_LOCK)
- {
- termdata->mods ^= GRUB_TERM_STATUS_CAPS;
- send_leds (termdata);
- continue;
- }
- if (keycode == KEY_NUM_LOCK)
- {
- termdata->mods ^= GRUB_TERM_STATUS_NUM;
- send_leds (termdata);
- continue;
- }
- termdata->last_key = grub_term_map_key (keycode,
- interpret_status (termdata->current_report[0])
- | termdata->mods);
- termdata->repeat_time = grub_get_time_ms () + GRUB_TERM_REPEAT_PRE_INTERVAL;
- grub_errno = GRUB_ERR_NONE;
- index++;
- if (index >= termdata->max_index)
- termdata->index = 0;
- else
- termdata->index = index;
- return termdata->last_key;
- }
- /* All keycodes parsed */
- termdata->index = 0;
- return GRUB_TERM_NO_KEY;
- }
- static int
- grub_usb_keyboard_getkey (struct grub_term_input *term)
- {
- grub_usb_err_t err;
- struct grub_usb_keyboard_data *termdata = term->data;
- grub_size_t actual;
- int keycode = GRUB_TERM_NO_KEY;
- if (termdata->dead)
- return GRUB_TERM_NO_KEY;
- if (termdata->index)
- keycode = parse_keycode (termdata);
- if (keycode != GRUB_TERM_NO_KEY)
- return keycode;
- /* 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->repeat_time = grub_get_time_ms ()
- + GRUB_TERM_REPEAT_INTERVAL;
- return termdata->last_key;
- }
- return GRUB_TERM_NO_KEY;
- }
- if (!err && (actual >= 3))
- grub_memcpy (termdata->last_report,
- termdata->current_report,
- sizeof (termdata->report));
- grub_memcpy (termdata->current_report,
- termdata->report,
- sizeof (termdata->report));
- termdata->transfer = grub_usb_bulk_read_background (termdata->usbdev,
- termdata->endp,
- 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 = %" PRIuGRUB_SIZE
- " report: 0x%02x 0x%02x 0x%02x 0x%02x"
- " 0x%02x 0x%02x 0x%02x 0x%02x\n",
- err, actual,
- termdata->current_report[0], termdata->current_report[1],
- termdata->current_report[2], termdata->current_report[3],
- termdata->current_report[4], termdata->current_report[5],
- termdata->current_report[6], termdata->current_report[7]);
- if (err || actual < 1)
- return GRUB_TERM_NO_KEY;
- termdata->status = termdata->current_report[0];
- if (actual < 3)
- return GRUB_TERM_NO_KEY;
- termdata->index = 2; /* New data received. */
- termdata->max_index = actual;
- return parse_keycode (termdata);
- }
- static int
- grub_usb_keyboard_getkeystatus (struct grub_term_input *term)
- {
- struct grub_usb_keyboard_data *termdata = term->data;
- return interpret_status (termdata->status) | termdata->mods;
- }
- static 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);
- }
|