Blob Blame Raw
From b528ebd33741511b054926e6076b23ab0637f6da Mon Sep 17 00:00:00 2001
From: Vladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Date: Tue, 19 Mar 2013 23:06:44 +0100
Subject: [PATCH 215/482] 	Initialize USB ports in parallel to speed-up
 boot.

---
 ChangeLog                  |   4 +
 grub-core/bus/usb/usb.c    |  38 ------
 grub-core/bus/usb/usbhub.c | 320 +++++++++++++++++++++++++++++----------------
 include/grub/usb.h         |  16 +++
 4 files changed, 228 insertions(+), 150 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 725fbe2..dd9c97a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2013-03-19  Vladimir Serbinenko  <phcoder@gmail.com>
 
+	Initialize USB ports in parallel to speed-up boot.
+
+2013-03-19  Vladimir Serbinenko  <phcoder@gmail.com>
+
 	Fix USB devices not being detected when requested
 	due to delayed attach.
 
diff --git a/grub-core/bus/usb/usb.c b/grub-core/bus/usb/usb.c
index 108c69b..024190e 100644
--- a/grub-core/bus/usb/usb.c
+++ b/grub-core/bus/usb/usb.c
@@ -26,46 +26,8 @@
 
 GRUB_MOD_LICENSE ("GPLv3+");
 
-static grub_usb_controller_dev_t grub_usb_list;
 static struct grub_usb_attach_desc *attach_hooks;
 
-/* Iterate over all controllers found by the driver.  */
-static int
-grub_usb_controller_dev_register_iter (grub_usb_controller_t dev, void *data)
-{
-  grub_usb_controller_dev_t usb = data;
-
-  dev->dev = usb;
-
-  /* Enable the ports of the USB Root Hub.  */
-  grub_usb_root_hub (dev);
-
-  return 0;
-}
-
-void
-grub_usb_controller_dev_register (grub_usb_controller_dev_t usb)
-{
-  usb->next = grub_usb_list;
-  grub_usb_list = usb;
-
-  if (usb->iterate)
-    usb->iterate (grub_usb_controller_dev_register_iter, usb);
-}
-
-void
-grub_usb_controller_dev_unregister (grub_usb_controller_dev_t usb)
-{
-  grub_usb_controller_dev_t *p, q;
-
-  for (p = &grub_usb_list, q = *p; q; p = &(q->next), q = q->next)
-    if (q == usb)
-      {
-	*p = q->next;
-	break;
-      }
-}
-
 #if 0
 /* Context for grub_usb_controller_iterate.  */
 struct grub_usb_controller_iterate_ctx
diff --git a/grub-core/bus/usb/usbhub.c b/grub-core/bus/usb/usbhub.c
index 253e49f..d0be80d 100644
--- a/grub-core/bus/usb/usbhub.c
+++ b/grub-core/bus/usb/usbhub.c
@@ -41,6 +41,7 @@ struct grub_usb_hub
 };
 
 static struct grub_usb_hub *hubs;
+static grub_usb_controller_dev_t grub_usb_list;
 
 /* Add a device that currently has device number 0 and resides on
    CONTROLLER, the Hub reported that the device speed is SPEED.  */
@@ -146,10 +147,15 @@ grub_usb_add_hub (grub_usb_device_t dev)
   grub_dprintf ("usb", "Hub set configuration\n");
   grub_usb_set_configuration (dev, 1);
 
-  dev->children = grub_zalloc (hubdesc.portcnt * sizeof (dev->children[0]));
-  if (!dev->children)
-    return GRUB_USB_ERR_INTERNAL;
   dev->nports = hubdesc.portcnt;
+  dev->children = grub_zalloc (hubdesc.portcnt * sizeof (dev->children[0]));
+  dev->ports = grub_zalloc (dev->nports * sizeof (dev->ports[0]));
+  if (!dev->children || !dev->ports)
+    {
+      grub_free (dev->children);
+      grub_free (dev->ports);
+      return GRUB_USB_ERR_INTERNAL;
+    }
 
   /* Power on all Hub ports.  */
   for (i = 1; i <= hubdesc.portcnt; i++)
@@ -197,46 +203,6 @@ attach_root_port (struct grub_usb_hub *hub, int portno,
 {
   grub_usb_device_t dev;
   grub_err_t err;
-  int total, i;
-  grub_usb_speed_t current_speed = GRUB_USB_SPEED_NONE;
-  int changed=0;
-
-  grub_boot_time ("detect_dev USB root portno=%d\n", portno);
-
-#if 0
-/* Specification does not say about disabling of port when device
- * connected. If disabling is really necessary for some devices,
- * delete this #if 0 and related #endif */
-  /* Disable the port. XXX: Why? */
-  err = hub->controller->dev->portstatus (hub->controller, portno, 0);
-  if (err)
-    return;
-#endif
-
-  grub_boot_time ("Before the stable power wait portno=%d", portno);
-
-  /* Wait for completion of insertion and stable power (USB spec.)
-   * Should be at least 100ms, some devices requires more...
-   * There is also another thing - some devices have worse contacts
-   * and connected signal is unstable for some time - we should handle
-   * it - but prevent deadlock in case when device is too faulty... */
-  for (total = i = 0; (i < 250) && (total < 2000); i++, total++)
-    {
-      grub_millisleep (1);
-      current_speed = hub->controller->dev->detect_dev
-                        (hub->controller, portno, &changed);
-      if (current_speed == GRUB_USB_SPEED_NONE)
-        i = 0;
-    }
-
-  grub_boot_time ("After the stable power wait portno=%d", portno);
-
-  grub_dprintf ("usb", "total=%d\n", total);
-  if (total >= 2000)
-    {
-      grub_boot_time ("Root port timeout");
-      return;
-    }
 
   grub_boot_time ("After detect_dev");
 
@@ -267,12 +233,14 @@ attach_root_port (struct grub_usb_hub *hub, int portno,
   grub_boot_time ("Attached root port");
 }
 
-grub_usb_err_t
-grub_usb_root_hub (grub_usb_controller_t controller)
+/* Iterate over all controllers found by the driver.  */
+static int
+grub_usb_controller_dev_register_iter (grub_usb_controller_t controller, void *data)
 {
-  int i;
+  grub_usb_controller_dev_t usb = data;
   struct grub_usb_hub *hub;
-  int changed=0;
+
+  controller->dev = usb;
 
   grub_boot_time ("Registering USB root hub");
 
@@ -295,29 +263,118 @@ grub_usb_root_hub (grub_usb_controller_t controller)
   /* Query the number of ports the root Hub has.  */
   hub->nports = controller->dev->hubports (controller);
   hub->devices = grub_zalloc (sizeof (hub->devices[0]) * hub->nports);
-  if (!hub->devices)
+  usb->ports = grub_zalloc (sizeof (usb->ports[0]) * hub->nports);
+  if (!hub->devices || !usb->ports)
     {
+      grub_free (hub->devices);
+      grub_free (usb->ports);
       grub_free (hub->controller);
       grub_free (hub);
-      return GRUB_USB_ERR_INTERNAL;
+      grub_print_error ();
+      return 0;
     }
 
-  for (i = 0; i < hub->nports; i++)
+  return 0;
+}
+
+void
+grub_usb_controller_dev_unregister (grub_usb_controller_dev_t usb)
+{
+  grub_usb_controller_dev_t *p, q;
+
+  for (p = &grub_usb_list, q = *p; q; p = &(q->next), q = q->next)
+    if (q == usb)
+      {
+	*p = q->next;
+	break;
+      }
+}
+
+void
+grub_usb_controller_dev_register (grub_usb_controller_dev_t usb)
+{
+  int portno;
+  int continue_waiting = 0;
+  struct grub_usb_hub *hub;
+
+  usb->next = grub_usb_list;
+  grub_usb_list = usb;
+
+  if (usb->iterate)
+    usb->iterate (grub_usb_controller_dev_register_iter, usb);
+
+  grub_boot_time ("waiting for stable power on USB root\n");
+
+  while (1)
     {
-      grub_usb_speed_t speed;
-      if (!controller->dev->pending_reset)
-        {
-          speed = controller->dev->detect_dev (hub->controller, i,
-					       &changed);
-
-          if (speed != GRUB_USB_SPEED_NONE)
-	    attach_root_port (hub, i, speed);
-        }
+      for (hub = hubs; hub; hub = hub->next)
+	if (hub->controller->dev == usb)
+	  {
+	    /* Wait for completion of insertion and stable power (USB spec.)
+	     * Should be at least 100ms, some devices requires more...
+	     * There is also another thing - some devices have worse contacts
+	     * and connected signal is unstable for some time - we should handle
+	     * it - but prevent deadlock in case when device is too faulty... */
+	    for (portno = 0; portno < hub->nports; portno++)
+	      {
+		grub_usb_speed_t speed;
+		int changed = 0;
+
+		speed = hub->controller->dev->detect_dev (hub->controller, portno,
+							  &changed);
+      
+		if (usb->ports[portno].state == PORT_STATE_NORMAL
+		    && speed != GRUB_USB_SPEED_NONE)
+		  {
+		    usb->ports[portno].soft_limit_time = grub_get_time_ms () + 250;
+		    usb->ports[portno].hard_limit_time = usb->ports[portno].soft_limit_time + 1750;
+		    usb->ports[portno].state = PORT_STATE_WAITING_FOR_STABLE_POWER;
+		    continue_waiting++;
+		    continue;
+		  }
+
+		if (usb->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER
+		    && speed == GRUB_USB_SPEED_NONE)
+		  {
+		    usb->ports[portno].soft_limit_time = grub_get_time_ms () + 250;
+		    continue;
+		  }
+		if (usb->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER
+		    && grub_get_time_ms () > usb->ports[portno].soft_limit_time)
+		  {
+		    usb->ports[portno].state = PORT_STATE_STABLE_POWER;
+		    continue_waiting--;
+		    continue;
+		  }
+		if (usb->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER
+		    && grub_get_time_ms () > usb->ports[portno].hard_limit_time)
+		  {
+		    usb->ports[portno].state = PORT_STATE_FAILED_DEVICE;
+		    continue_waiting--;
+		    continue;
+		  }
+	      }
+	  }
+      if (!continue_waiting)
+	break;
+      grub_millisleep (1);
     }
 
-  grub_boot_time ("USB root hub registered");
+  grub_boot_time ("After the stable power wait on USB root");
 
-  return GRUB_USB_ERR_NONE;
+  for (hub = hubs; hub; hub = hub->next)
+    if (hub->controller->dev == usb)
+      for (portno = 0; portno < hub->nports; portno++)
+	if (usb->ports[portno].state == PORT_STATE_STABLE_POWER)
+	  {
+	    grub_usb_speed_t speed;
+	    int changed = 0;
+	    usb->ports[portno].state = PORT_STATE_NORMAL;
+	    speed = hub->controller->dev->detect_dev (hub->controller, portno, &changed);
+	    attach_root_port (hub, portno, speed);
+	  }
+
+  grub_boot_time ("USB root hub registered");
 }
 
 static void detach_device (grub_usb_device_t dev);
@@ -349,6 +406,71 @@ detach_device (grub_usb_device_t dev)
   grub_usb_devs[dev->addr] = 0;
 }
 
+static int
+wait_power_nonroot_hub (grub_usb_device_t dev)
+{
+  grub_usb_err_t err;
+  int continue_waiting = 0;
+  unsigned i;
+  
+  for (i = 1; i <= dev->nports; i++)
+    if (dev->ports[i - 1].state == PORT_STATE_WAITING_FOR_STABLE_POWER)
+      {
+	grub_uint64_t tm;
+	grub_uint32_t current_status = 0;
+
+	/* 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 (current_status),
+				    (char *) &current_status);
+	if (err)
+	  {
+	    dev->ports[i - 1].state = PORT_STATE_FAILED_DEVICE;
+	    continue;
+	  }
+	tm = grub_get_time_ms ();
+	if (!(current_status & GRUB_USB_HUB_STATUS_PORT_CONNECTED))
+	  dev->ports[i - 1].soft_limit_time = tm + 250;
+	if (tm >= dev->ports[i - 1].soft_limit_time)
+	  {
+	    if (dev->controller.dev->pending_reset)
+	      continue;
+	    /* Now do reset of port. */
+	    grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
+					| GRUB_USB_REQTYPE_CLASS
+					| GRUB_USB_REQTYPE_TARGET_OTHER),
+				  GRUB_USB_REQ_SET_FEATURE,
+				  GRUB_USB_HUB_FEATURE_PORT_RESET,
+				  i, 0, 0);
+	    dev->ports[i - 1].state = PORT_STATE_NORMAL;
+	    grub_boot_time ("Resetting port %d", i);
+
+	    rescan = 1;
+	    /* We cannot reset more than one device at the same time !
+	     * Resetting more devices together results in very bad
+	     * situation: more than one device has default address 0
+	     * at the same time !!!
+	     * Additionaly, we cannot perform another reset
+	     * anywhere on the same OHCI controller until
+	     * we will finish addressing of reseted device ! */
+	    dev->controller.dev->pending_reset = grub_get_time_ms () + 5000;
+	    npending++;
+	    continue;
+	  }
+	if (tm >= dev->ports[i - 1].hard_limit_time)
+	  {
+	    dev->ports[i - 1].state = PORT_STATE_FAILED_DEVICE;
+	    continue;
+	  }
+	continue_waiting = 1;
+      }
+  return continue_waiting && dev->controller.dev->pending_reset == 0;
+}
+
 static void
 poll_nonroot_hub (grub_usb_device_t dev)
 {
@@ -356,7 +478,6 @@ poll_nonroot_hub (grub_usb_device_t dev)
   unsigned i;
   grub_uint8_t changed;
   grub_size_t actual, len;
-  int j, total;
 
   if (!dev->hub_transfer)
     return;
@@ -382,9 +503,9 @@ poll_nonroot_hub (grub_usb_device_t dev)
   for (i = 1; i <= dev->nports; i++)
     {
       grub_uint32_t status;
-      grub_uint32_t current_status = 0;
 
-      if (!(changed & (1 << i)))
+      if (!(changed & (1 << i))
+	  || dev->ports[i - 1].state == PORT_STATE_WAITING_FOR_STABLE_POWER)
 	continue;
 
       /* Get the port status.  */
@@ -444,52 +565,10 @@ poll_nonroot_hub (grub_usb_device_t dev)
 	       * There is also another thing - some devices have worse contacts
 	       * and connected signal is unstable for some time - we should handle
 	       * it - but prevent deadlock in case when device is too faulty... */
-              for (total = j = 0; (j < 250) && (total < 2000); j++, total++)
-                {
-                  grub_millisleep (1);
-                  /* 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 (current_status),
-				              (char *) &current_status);
-                  if (err)
-                    {
-                      total = 2000;
-	              break;
-                    }
-                  if (!(current_status & GRUB_USB_HUB_STATUS_PORT_CONNECTED))
-                    j = 0;
-                }
-
-	      grub_boot_time ("After the stable power wait portno=%d", i);
-
-              grub_dprintf ("usb", "(non-root) total=%d\n", total);
-              if (total >= 2000)
-                continue;
-
-              /* Now do reset of port. */
-	      grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
-					  | GRUB_USB_REQTYPE_CLASS
-					  | GRUB_USB_REQTYPE_TARGET_OTHER),
-				    GRUB_USB_REQ_SET_FEATURE,
-				    GRUB_USB_HUB_FEATURE_PORT_RESET,
-				    i, 0, 0);
-	      grub_boot_time ("Resetting port %d", i);
-
-	      rescan = 1;
-	      /* We cannot reset more than one device at the same time !
-	       * Resetting more devices together results in very bad
-	       * situation: more than one device has default address 0
-	       * at the same time !!!
-	       * Additionaly, we cannot perform another reset
-	       * anywhere on the same OHCI controller until
-	       * we will finish addressing of reseted device ! */
-              dev->controller.dev->pending_reset = grub_get_time_ms () + 5000;
-	      npending++;
-              return;
+	      dev->ports[i - 1].soft_limit_time = grub_get_time_ms () + 250;
+	      dev->ports[i - 1].hard_limit_time = dev->ports[i - 1].soft_limit_time + 1750;
+	      dev->ports[i - 1].state = PORT_STATE_WAITING_FOR_STABLE_POWER;
+	      continue;
 	    }
 	}
 
@@ -580,6 +659,8 @@ grub_usb_poll_devices (int wait_for_completion)
 	}
     }
 
+  grub_boot_time ("Probing USB device driver");
+
   while (1)
     {
       rescan = 0;
@@ -592,11 +673,26 @@ grub_usb_poll_devices (int wait_for_completion)
 	  if (dev && dev->descdev.class == 0x09)
 	    poll_nonroot_hub (dev);
 	}
+
+      while (1)
+	{
+	  int continue_waiting = 0;
+	  for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++)
+	    {
+	      grub_usb_device_t dev = grub_usb_devs[i];
+	    
+	      if (dev && dev->descdev.class == 0x09)
+		continue_waiting = continue_waiting || wait_power_nonroot_hub (dev);
+	    }
+	  if (!continue_waiting)
+	    break;
+	  grub_millisleep (1);
+	}
+
       if (!(rescan || (npending && wait_for_completion)))
 	break;
-      grub_millisleep (50);
+      grub_millisleep (25);
     }
-
 }
 
 int
diff --git a/include/grub/usb.h b/include/grub/usb.h
index 7164dd5..12a456b 100644
--- a/include/grub/usb.h
+++ b/include/grub/usb.h
@@ -121,6 +121,8 @@ struct grub_usb_controller_dev
 
   grub_usb_speed_t (*detect_dev) (grub_usb_controller_t dev, int port, int *changed);
 
+  struct grub_usb_hub_port *ports;
+
   /* Per controller flag - port reset pending, don't do another reset */
   grub_uint64_t pending_reset;
 
@@ -170,6 +172,18 @@ struct grub_usb_configuration
   struct grub_usb_interface interf[32];
 };
 
+struct grub_usb_hub_port
+{
+  grub_uint64_t soft_limit_time;
+  grub_uint64_t hard_limit_time;
+  enum { 
+    PORT_STATE_NORMAL = 0,
+    PORT_STATE_WAITING_FOR_STABLE_POWER = 1,
+    PORT_STATE_FAILED_DEVICE = 2,
+    PORT_STATE_STABLE_POWER = 3,
+  } state;
+};
+
 struct grub_usb_device
 {
   /* The device descriptor of this device.  */
@@ -204,6 +218,8 @@ struct grub_usb_device
   /* Number of hub ports.  */
   unsigned nports;
 
+  struct grub_usb_hub_port *ports;
+
   grub_usb_transfer_t hub_transfer;
 
   grub_uint32_t statuschange;
-- 
1.8.2.1