]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Use status change pipe for hub hotplug detection
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 21 Aug 2010 21:09:37 +0000 (23:09 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 21 Aug 2010 21:09:37 +0000 (23:09 +0200)
bus/usb/usbhub.c
bus/usb/usbtrans.c
include/grub/usb.h
include/grub/usbtrans.h
term/usb_keyboard.c

index 7f2c8d24bc1e78a6e4356cbc4657079d2b0bb02a..111a2495e4641dde6689a1c28a8b54461db1355d 100644 (file)
@@ -177,16 +177,16 @@ grub_usb_add_hub (grub_usb_device_t dev)
       grub_dprintf ("usb", "Hub port %d status: 0x%02x\n", i, status);
 
       /* If connected, reset and enable the port.  */
-      if (status & GRUB_USB_HUB_STATUS_CONNECTED)
+      if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)
        {
          grub_usb_speed_t speed;
 
          /* Determine the device speed.  */
-         if (status & GRUB_USB_HUB_STATUS_LOWSPEED)
+         if (status & GRUB_USB_HUB_STATUS_PORT_LOWSPEED)
            speed = GRUB_USB_SPEED_LOW;
          else
            {
-             if (status & GRUB_USB_HUB_STATUS_HIGHSPEED)
+             if (status & GRUB_USB_HUB_STATUS_PORT_HIGHSPEED)
                speed = GRUB_USB_SPEED_HIGH;
              else
                speed = GRUB_USB_SPEED_FULL;
@@ -231,7 +231,7 @@ grub_usb_add_hub (grub_usb_device_t dev)
                                             | GRUB_USB_REQTYPE_CLASS
                                             | GRUB_USB_REQTYPE_TARGET_OTHER),
                                       GRUB_USB_REQ_CLEAR_FEATURE,
-                                     GRUB_USB_HUB_FEATURE_C_CONNECTED,
+                                     GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED,
                                      i, 0, 0);
           /* Just ignore the device if the Hub reports some error */
           if (err)
@@ -252,6 +252,25 @@ grub_usb_add_hub (grub_usb_device_t dev)
        }
     }
 
+  for (i = 0; i < dev->config[0].interf[0].descif->endpointcnt;
+       i++)
+    {
+      struct grub_usb_desc_endp *endp = NULL;
+      endp = &dev->config[0].interf[0].descendp[i];
+
+      if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp)
+         == GRUB_USB_EP_INTERRUPT)
+       {
+         dev->hub_endpoint = endp;
+         dev->hub_transfer
+           = grub_usb_bulk_read_background (dev, endp->endp_addr,
+                                            grub_min (endp->maxpacket,
+                                                      sizeof (dev->statuschange)),
+                                            (char *) &dev->statuschange);
+         break;
+       }
+    }
+
   return GRUB_ERR_NONE;
 }
 
@@ -341,6 +360,9 @@ detach_device (grub_usb_device_t dev)
     return;
   if (dev->descdev.class == GRUB_USB_CLASS_HUB)
     {
+      if (dev->hub_transfer)
+       grub_usb_cancel_transfer (dev->hub_transfer);
+
       for (i = 0; i < dev->nports; i++)
        detach_device (dev->children[i]);
       grub_free (dev->children);
@@ -364,41 +386,105 @@ poll_nonroot_hub (grub_usb_device_t dev)
   grub_uint64_t timeout;
   grub_usb_device_t next_dev;
   grub_usb_device_t *attached_devices = dev->children;
-      
+  grub_uint8_t changed;
+  grub_size_t actual;
+
+  if (!dev->hub_transfer)
+    return;
+
+  err = grub_usb_check_transfer (dev->hub_transfer, &actual);
+
+  if (err == GRUB_USB_ERR_WAIT)
+    return;
+
+  changed = dev->statuschange;
+
+  dev->hub_transfer
+    = grub_usb_bulk_read_background (dev, dev->hub_endpoint->endp_addr,
+                                    grub_min (dev->hub_endpoint->maxpacket,
+                                              sizeof (dev->statuschange)),
+                                    (char *) &dev->statuschange);
+
+  if (err || actual == 0 || changed == 0)
+    return;
+
+  grub_dprintf ("usb", "statuschanged = %02x, err = %d, actual = %d\n",
+               changed, err, actual);
+
   /* Iterate over the Hub ports.  */
   for (i = 1; i <= dev->nports; i++)
     {
       grub_uint32_t status;
 
+      if (!(changed & (1 << i)))
+       continue;
+
       /* Get the port status.  */
       err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN
                                        | GRUB_USB_REQTYPE_CLASS
                                        | GRUB_USB_REQTYPE_TARGET_OTHER),
                                  GRUB_USB_REQ_GET_STATUS,
                                  0, i, sizeof (status), (char *) &status);
-      /* Just ignore the device if the Hub does not report the
-        status.  */
+
       if (err)
        continue;
 
-      if (status & GRUB_USB_HUB_STATUS_C_CONNECTED)
+      /* FIXME: properly handle these conditions.  */
+      if (status & GRUB_USB_HUB_STATUS_C_PORT_RESET)
+       grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+                                   | GRUB_USB_REQTYPE_CLASS
+                                   | GRUB_USB_REQTYPE_TARGET_OTHER),
+                             GRUB_USB_REQ_CLEAR_FEATURE,
+                             GRUB_USB_HUB_FEATURE_C_PORT_RESET, i, 0, 0);
+
+      if (status & GRUB_USB_HUB_STATUS_C_PORT_ENABLED)
+       grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+                                   | GRUB_USB_REQTYPE_CLASS
+                                   | GRUB_USB_REQTYPE_TARGET_OTHER),
+                             GRUB_USB_REQ_CLEAR_FEATURE,
+                             GRUB_USB_HUB_FEATURE_C_PORT_ENABLED, i, 0, 0);
+
+      if (status & GRUB_USB_HUB_STATUS_C_PORT_SUSPEND)
+       grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+                                   | GRUB_USB_REQTYPE_CLASS
+                                   | GRUB_USB_REQTYPE_TARGET_OTHER),
+                             GRUB_USB_REQ_CLEAR_FEATURE,
+                             GRUB_USB_HUB_FEATURE_C_PORT_SUSPEND, i, 0, 0);
+
+      if (status & GRUB_USB_HUB_STATUS_C_PORT_OVERCURRENT)
+       grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+                                   | GRUB_USB_REQTYPE_CLASS
+                                   | GRUB_USB_REQTYPE_TARGET_OTHER),
+                             GRUB_USB_REQ_CLEAR_FEATURE,
+                             GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT, i, 0, 0);
+
+      if (!(status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED))
+       continue;
+      
+      grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+                                 | GRUB_USB_REQTYPE_CLASS
+                                 | GRUB_USB_REQTYPE_TARGET_OTHER),
+                                 GRUB_USB_REQ_CLEAR_FEATURE,
+                           GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED, i, 0, 0);
+
+      if (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED)
        {
          detach_device (attached_devices[i-1]);
          attached_devices[i - 1] = NULL;
        }
            
       /* Connected and status of connection changed ? */
-      if ((status & GRUB_USB_HUB_STATUS_CONNECTED)
-          && (status & GRUB_USB_HUB_STATUS_C_CONNECTED))
+      if ((status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)
+          && (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED))
        {
          grub_usb_speed_t speed;
 
          /* Determine the device speed.  */
-         if (status & GRUB_USB_HUB_STATUS_LOWSPEED)
+         if (status & GRUB_USB_HUB_STATUS_PORT_LOWSPEED)
            speed = GRUB_USB_SPEED_LOW;
          else
            {
-             if (status & GRUB_USB_HUB_STATUS_HIGHSPEED)
+             if (status & GRUB_USB_HUB_STATUS_PORT_HIGHSPEED)
                speed = GRUB_USB_SPEED_HIGH;
              else
                speed = GRUB_USB_SPEED_FULL;
@@ -442,7 +528,7 @@ poll_nonroot_hub (grub_usb_device_t dev)
                                             | GRUB_USB_REQTYPE_CLASS
                                             | GRUB_USB_REQTYPE_TARGET_OTHER),
                                       GRUB_USB_REQ_CLEAR_FEATURE,
-                                     GRUB_USB_HUB_FEATURE_C_CONNECTED,
+                                     GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED,
                                      i, 0, 0);
           /* Just ignore the device if the Hub reports some error */
           if (err)
index 7e6840083ad78283c2b6f98cf327ecf6c647aa9b..fe55114c764c28c15ffb087a0901bb02f978a55f 100644 (file)
@@ -392,6 +392,14 @@ grub_usb_bulk_read_background (grub_usb_device_t dev,
   return transfer;
 }
 
+void
+grub_usb_cancel_transfer (grub_usb_transfer_t transfer)
+{
+  grub_usb_device_t dev = transfer->dev;
+  dev->controller.dev->cancel_transfer (&dev->controller, transfer);
+  grub_errno = GRUB_ERR_NONE;
+}
+
 grub_usb_err_t
 grub_usb_bulk_read_extended (grub_usb_device_t dev,
                             int endpoint, grub_size_t size, char *data,
index 315ae9455d23a47e20f5e3684ee32df785b5426d..f9cdb27659fab583f6a19392ddaea7c935ae0d34 100644 (file)
@@ -181,11 +181,19 @@ struct grub_usb_device
   /* Used by libusb wrapper.  Schedulded for removal. */
   void *data;
 
+  /* Hub information.  */
+
   /* Array of children for a hub.  */
   grub_usb_device_t *children;
 
   /* Number of hub ports.  */
   unsigned nports;
+
+  grub_usb_transfer_t hub_transfer;
+
+  grub_uint32_t statuschange;
+
+  struct grub_usb_desc_endp *hub_endpoint;
 };
 
 \f
@@ -271,5 +279,7 @@ grub_usb_bulk_read_background (grub_usb_device_t dev,
                              int endpoint, grub_size_t size, void *data);
 grub_usb_err_t
 grub_usb_check_transfer (grub_usb_transfer_t trans, grub_size_t *actual);
+void
+grub_usb_cancel_transfer (grub_usb_transfer_t trans);
 
 #endif /* GRUB_USB_H */
index ae2fd1bc43c44c2978ec432fdfd202415c31dd65..9e9d75e5d90ebe014a052e95acef1092aa6bd40b 100644 (file)
@@ -94,31 +94,50 @@ enum
     GRUB_USB_REQTYPE_VENDOR_IN = GRUB_USB_REQTYPE_VENDOR | GRUB_USB_REQTYPE_IN
   };
 
-#define GRUB_USB_REQ_GET_STATUS                0x00
-#define GRUB_USB_REQ_CLEAR_FEATURE     0x01
-#define GRUB_USB_REQ_SET_FEATURE       0x03
-#define GRUB_USB_REQ_SET_ADDRESS       0x05
-#define GRUB_USB_REQ_GET_DESCRIPTOR    0x06
-#define GRUB_USB_REQ_SET_DESCRIPTOR    0x07
-#define GRUB_USB_REQ_GET_CONFIGURATION 0x08
-#define GRUB_USB_REQ_SET_CONFIGURATION 0x09
-#define GRUB_USB_REQ_GET_INTERFACE     0x0A
-#define GRUB_USB_REQ_SET_INTERFACE     0x0B
-#define GRUB_USB_REQ_SYNC_FRAME                0x0C
+enum
+  {
+    GRUB_USB_REQ_GET_STATUS = 0x00,
+    GRUB_USB_REQ_CLEAR_FEATURE = 0x01,
+    GRUB_USB_REQ_SET_FEATURE = 0x03,
+    GRUB_USB_REQ_SET_ADDRESS = 0x05,
+    GRUB_USB_REQ_GET_DESCRIPTOR = 0x06,
+    GRUB_USB_REQ_SET_DESCRIPTOR        = 0x07,
+    GRUB_USB_REQ_GET_CONFIGURATION = 0x08,
+    GRUB_USB_REQ_SET_CONFIGURATION = 0x09,
+    GRUB_USB_REQ_GET_INTERFACE = 0x0A,
+    GRUB_USB_REQ_SET_INTERFACE = 0x0B,
+    GRUB_USB_REQ_SYNC_FRAME = 0x0C
+  };
 
 #define GRUB_USB_FEATURE_ENDP_HALT     0x00
 #define GRUB_USB_FEATURE_DEV_REMOTE_WU 0x01
 #define GRUB_USB_FEATURE_TEST_MODE     0x02
 
-#define GRUB_USB_HUB_FEATURE_PORT_RESET   0x04
-#define GRUB_USB_HUB_FEATURE_PORT_POWER   0x08
-#define GRUB_USB_HUB_FEATURE_C_CONNECTED  0x10
+enum
+  {
+    GRUB_USB_HUB_FEATURE_PORT_RESET = 0x04,
+    GRUB_USB_HUB_FEATURE_PORT_POWER = 0x08,
+    GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED = 0x10,
+    GRUB_USB_HUB_FEATURE_C_PORT_ENABLED = 0x11,
+    GRUB_USB_HUB_FEATURE_C_PORT_SUSPEND = 0x12,
+    GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT = 0x13,
+    GRUB_USB_HUB_FEATURE_C_PORT_RESET = 0x14
+  };
 
-#define GRUB_USB_HUB_STATUS_CONNECTED   (1 << 0)
-#define GRUB_USB_HUB_STATUS_LOWSPEED    (1 << 9)
-#define GRUB_USB_HUB_STATUS_HIGHSPEED   (1 << 10)
-#define GRUB_USB_HUB_STATUS_C_CONNECTED  (1 << 16)
-#define GRUB_USB_HUB_STATUS_C_PORT_RESET (1 << 20)
+enum
+  {
+    GRUB_USB_HUB_STATUS_PORT_CONNECTED = (1 << 0),
+    GRUB_USB_HUB_STATUS_PORT_ENABLED = (1 << 1),
+    GRUB_USB_HUB_STATUS_PORT_SUSPEND = (1 << 2),
+    GRUB_USB_HUB_STATUS_PORT_OVERCURRENT = (1 << 3),
+    GRUB_USB_HUB_STATUS_PORT_LOWSPEED = (1 << 9),
+    GRUB_USB_HUB_STATUS_PORT_HIGHSPEED = (1 << 10),
+    GRUB_USB_HUB_STATUS_C_PORT_CONNECTED = (1 << 16),
+    GRUB_USB_HUB_STATUS_C_PORT_ENABLED = (1 << 17),
+    GRUB_USB_HUB_STATUS_C_PORT_SUSPEND = (1 << 18),
+    GRUB_USB_HUB_STATUS_C_PORT_OVERCURRENT = (1 << 19),
+    GRUB_USB_HUB_STATUS_C_PORT_RESET = (1 << 20)
+  };
 
 struct grub_usb_packet_setup
 {
index e9be331cf9101e63e00fb9c2a6c33c3ab67a0702..ea13418e0b66c75113e3cc26b7a7b9b6d7ecb7a6 100644 (file)
@@ -107,6 +107,9 @@ grub_usb_keyboard_detach (grub_usb_device_t usbdev,
       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;
@@ -351,9 +354,18 @@ GRUB_MOD_FINI(usb_keyboard)
   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);