]> git.proxmox.com Git - grub2.git/blobdiff - grub-core/term/usb_keyboard.c
* grub-core/kern/mm.c (grub_memalign): Disable auto-unloadding of
[grub2.git] / grub-core / term / usb_keyboard.c
index 5d76c5e02a760ef7b3448dd8f0bea77b1d6dfcb6..ae00936b8039df05194447200453b0d69df8f8a3 100644 (file)
@@ -18,7 +18,6 @@
  */
 
 #include <grub/term.h>
-#include <grub/machine/console.h>
 #include <grub/time.h>
 #include <grub/cpu/io.h>
 #include <grub/misc.h>
 #include <grub/usb.h>
 #include <grub/dl.h>
 #include <grub/time.h>
+#include <grub/keyboard_layouts.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
 
 \f
-static char keyboard_map[128] =
+
+enum
   {
-    '\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
+    KEY_NO_KEY = 0x00,
+    KEY_ERR_BUFFER = 0x01,
+    KEY_ERR_POST  = 0x02,
+    KEY_ERR_UNDEF = 0x03,
+    KEY_CAPS_LOCK = 0x39,
+    KEY_NUM_LOCK  = 0x53,
   };
 
-static char keyboard_map_shift[128] =
+enum
   {
-    '\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', ' ', '_', '+', '{',
-    '}', '|', '#', ':', '"', '`', '<', '>',
-    '?'
+    LED_NUM_LOCK = 0x01,
+    LED_CAPS_LOCK = 0x02
   };
 
-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
@@ -70,260 +55,418 @@ static grub_usb_device_t usbdev;
 #define USB_HID_SET_IDLE       0x0A
 #define USB_HID_SET_PROTOCOL   0x0B
 
-static void
-grub_usb_hid (void)
+#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
 {
-  struct grub_usb_desc_device *descdev;
+  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);
 
-  auto int usb_iterate (grub_usb_device_t dev);
-  int usb_iterate (grub_usb_device_t dev)
-    {
-      descdev = &dev->descdev;
+static struct grub_term_input grub_usb_keyboard_term =
+  {
+    .getkey = grub_usb_keyboard_getkey,
+    .getkeystatus = grub_usb_keyboard_getkeystatus,
+    .next = 0
+  };
 
-      grub_dprintf ("usb_keyboard", "%x %x %x\n",
-                  descdev->class, descdev->subclass, descdev->protocol);
+static struct grub_term_input grub_usb_keyboards[16];
 
-#if 0
-      if (descdev->class != 0x09
-         || descdev->subclass == 0x01
-         || descdev->protocol != 0x02)
-       return 0;
-#endif
+static int
+interpret_status (grub_uint8_t data0)
+{
+  int mods = 0;
 
-      if (descdev->class != 0 || descdev->subclass != 0 || descdev->protocol != 0)
-       return 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;
 
-      grub_printf ("HID found!\n");
+  return mods;
+}
 
-      usbdev = dev;
+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;
 
-      return 1;
-    }
-  grub_usb_iterate (usb_iterate);
+      if (!data)
+       continue;
 
-  /* Place the device in boot mode.  */
-  grub_usb_control_msg (usbdev, USB_HID_HOST_TO_DEVICE, USB_HID_SET_PROTOCOL,
-                       0, 0, 0, 0);
+      if (data->usbdev != usbdev)
+       continue;
 
-  /* 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);
-}
+      if (data->transfer)
+       grub_usb_cancel_transfer (data->transfer);
 
-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);
+      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;
+    }
 }
 
-\f
-
 static int
-grub_usb_keyboard_checkkey (void)
+grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno)
 {
-  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);
+  unsigned curnum;
+  struct grub_usb_keyboard_data *data;
+  struct grub_usb_desc_endp *endp = NULL;
+  int j;
 
-      /* Implement a timeout.  */
-      if (grub_get_time_ms () > currtime + timeout)
-       break;
-    }
-  while (err || !data[2]);
+  grub_dprintf ("usb_keyboard", "%x %x %x %d %d\n",
+               usbdev->descdev.class, usbdev->descdev.subclass,
+               usbdev->descdev.protocol, configno, interfno);
 
-  if (err || !data[2])
-    return -1;
+  for (curnum = 0; curnum < ARRAY_SIZE (grub_usb_keyboards); curnum++)
+    if (!grub_usb_keyboards[curnum].data)
+      break;
 
-  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 (curnum == ARRAY_SIZE (grub_usb_keyboards))
+    return 0;
 
-  /* 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 (usbdev->descdev.class != 0 
+      || usbdev->descdev.subclass != 0 || usbdev->descdev.protocol != 0)
+    return 0;
 
-  if (key == 0)
-    grub_printf ("Unknown key 0x%x detected\n", data[2]);
+  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;
 
-#if 0
-  /* Wait until the key is released.  */
-  while (!err && data[2])
+  for (j = 0; j < usbdev->config[configno].interf[interfno].descif->endpointcnt;
+       j++)
     {
-      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
+      endp = &usbdev->config[configno].interf[interfno].descendp[j];
 
-  grub_errno = GRUB_ERR_NONE;
+      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;
 
-  return key;
-}
+  grub_dprintf ("usb_keyboard", "HID found!\n");
 
-typedef enum
-{
-  GRUB_HIDBOOT_REPEAT_NONE,
-  GRUB_HIDBOOT_REPEAT_FIRST,
-  GRUB_HIDBOOT_REPEAT
-} grub_usb_keyboard_repeat_t;
+  data = grub_malloc (sizeof (*data));
+  if (!data)
+    {
+      grub_print_error ();
+      return 0;
+    }
 
-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;
+  data->usbdev = usbdev;
+  data->interfno = interfno;
+  data->endp = endp;
 
- again:
+  /* 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);
 
-  do
+  /* 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)
     {
-      key = grub_usb_keyboard_checkkey ();
-    } while (key == -1);
+      grub_print_error ();
+      return 0;
+    }
 
-  data[2] = !0; /* Or whatever.  */
-  err = 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
 
-  switch (repeat)
+  data->transfer = grub_usb_bulk_read_background (usbdev,
+                                                 data->endp->endp_addr,
+                                                 sizeof (data->report),
+                                                 (char *) data->report);
+  if (!data->transfer)
     {
-    case GRUB_HIDBOOT_REPEAT_FIRST:
-      timeout = 500;
-      break;
-    case GRUB_HIDBOOT_REPEAT:
-      timeout = 50;
-      break;
-    default:
-      timeout = 100;
-      break;
+      grub_print_error ();
+      return 0;
     }
 
-  /* 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;
+  data->last_key = -1;
+  data->mods = 0;
+  data->dead = 0;
 
-         grub_errno = GRUB_ERR_NONE;
-         return key;
-       }
+  grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]);
 
-      err = grub_usb_keyboard_getreport (usbdev, data);
-    }
-
-  if (repeat)
-    {
-      repeat = 0;
-      goto again;
-    }
+  return 1;
+}
 
-  repeat = 0;
+\f
 
+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;
-
-  return key;
 }
 
 static int
-grub_usb_keyboard_getkeystatus (void)
+parse_keycode (struct grub_usb_keyboard_data *termdata)
 {
-  grub_uint8_t data[8];
-  int mods = 0;
-  grub_err_t err;
-  grub_uint64_t currtime;
-  int timeout = 50;
+  int index = termdata->index;
+  int i, keycode;
 
-  /* 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);
+  /* Sanity check */
+  if (index < 2)
+    index = 2;
 
-  currtime = grub_get_time_ms ();
-  do
+  for ( ; index < termdata->max_index; index++)
     {
-      /* Get_Report.  */
-      err = grub_usb_keyboard_getreport (usbdev, data);
+      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;
+    }
 
-      /* Implement a timeout.  */
-      if (grub_get_time_ms () > currtime + timeout)
-       break;
+  /* 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;
     }
-  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);
+  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->endp_addr,
+                                                     sizeof (termdata->report),
+                                                     (char *) termdata->report);
+  if (!termdata->transfer)
+    {
+      grub_printf ("%s failed. Stopped\n", term->name);
+      termdata->dead = 1;
+    }
 
-  /* 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;
+  termdata->last_key = -1;
 
   grub_dprintf ("usb_keyboard",
-               "report: 0x%02x 0x%02x 0x%02x 0x%02x"
+               "err = %d, actual = %" PRIuGRUB_SIZE
+               " 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]);
+               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]);
 
-  /* 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;
+  if (err || actual < 1)
+    return GRUB_TERM_NO_KEY;
 
-  grub_errno = GRUB_ERR_NONE;
+  termdata->status = termdata->current_report[0];
 
-  return mods;
+  if (actual < 3)
+    return GRUB_TERM_NO_KEY;
+
+  termdata->index = 2; /* New data received. */
+  termdata->max_index = actual;
+  
+  return parse_keycode (termdata);
 }
 
-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
-  };
+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_hid ();
-  grub_term_register_input ("usb_keyboard", &grub_usb_keyboard_term);
+  grub_usb_register_attach_hook_class (&attach_hook);
 }
 
 GRUB_MOD_FINI(usb_keyboard)
 {
-  grub_term_unregister_input (&grub_usb_keyboard_term);
+  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);
 }