]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Fix multiple USB issues
authorAleš Nesrsta <starous@volny.cz>
Sat, 18 Sep 2010 11:49:15 +0000 (13:49 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 18 Sep 2010 11:49:15 +0000 (13:49 +0200)
grub-core/bus/usb/ohci.c
grub-core/bus/usb/uhci.c
grub-core/bus/usb/usb.c
grub-core/bus/usb/usbhub.c
grub-core/bus/usb/usbtrans.c
include/grub/usb.h

index 7c618a6143e87217e963b0696d66f3c4737ac138..b07e304030e31810291ae2a80105e613c261cb94 100644 (file)
@@ -98,7 +98,6 @@ struct grub_ohci
   struct grub_pci_dma_chunk *td_chunk;
   struct grub_ohci *next;
   grub_ohci_td_t td_free; /* Pointer to first free TD */
-  int bad_OHCI;
 };
 
 static struct grub_ohci *ohci;
@@ -149,8 +148,8 @@ typedef enum
 #define GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE (1 << 4)
 
 #define GRUB_OHCI_RESET_CONNECT_CHANGE (1 << 16)
-#define GRUB_OHCI_CTRL_EDS 16
-#define GRUB_OHCI_BULK_EDS 16
+#define GRUB_OHCI_CTRL_EDS 256
+#define GRUB_OHCI_BULK_EDS 510
 #define GRUB_OHCI_TDS 256
 
 #define GRUB_OHCI_ED_ADDR_MASK 0x7ff
@@ -442,8 +441,10 @@ grub_ohci_pci_iter (grub_pci_device_t dev,
                        (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA)
                         & ~GRUB_OHCI_RHUB_PORT_POWER_MASK)
                        | GRUB_OHCI_RHUB_PORT_ALL_POWERED);
+#if 0 /* We don't need it at all, handled via hotplugging */
   /* Now we have hot-plugging, we need to wait for stable power only */
   grub_millisleep (100);
+#endif
 
   /* Link to ohci now that initialisation is successful.  */
   o->next = ohci;
@@ -623,7 +624,8 @@ grub_ohci_transaction (grub_ohci_td_t td,
       break;
     }
 
-  /* Set the token (Always generate interrupt - bits 21-23 = 0).  */
+  /* Set the token */
+  token |= ( 7 << 21); /* Never generate interrupt */
   token |= toggle << 24;
   token |= 1 << 25;
 
@@ -659,7 +661,6 @@ struct grub_ohci_transfer_controller_data
   grub_ohci_ed_t ed_virt;
   grub_ohci_td_t td_current_virt;
   grub_ohci_td_t td_head_virt;
-  grub_uint64_t bad_OHCI_delay;
 };
 
 static grub_usb_err_t
@@ -756,10 +757,6 @@ grub_ohci_setup_transfer (grub_usb_controller_t dev,
 
       /* Set index of TD in transfer */
       cdata->td_current_virt->tr_index = (grub_uint32_t) i;
-
-      /* No IRQ request in TD if bad_OHCI set */
-      if (o->bad_OHCI)
-        cdata->td_current_virt->token |= grub_cpu_to_le32 ( 7 << 21);
       
       /* Remember last used (processed) TD phys. addr. */
       cdata->td_last_phys = grub_ohci_td_virt2phys (o, cdata->td_current_virt);
@@ -891,8 +888,8 @@ pre_finish_transfer (grub_usb_controller_t dev,
   /* Now print debug values - to have full info what happened */
   grub_dprintf ("ohci", "loop finished: control=0x%02x status=0x%02x\n",
                control, status);
-  grub_dprintf ("ohci", "intstatus=0x%02x \n\t\t tderr_phys=0x%02x, td_last_phys=0x%02x\n",
-               intstatus, cdata->tderr_phys, cdata->td_last_phys);
+  grub_dprintf ("ohci", "intstatus=0x%02x, td_last_phys=0x%02x\n",
+               intstatus, cdata->td_last_phys);
   grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n",
                 target,
                 grub_le_to_cpu32 (cdata->ed_virt->td_head),
@@ -915,12 +912,6 @@ finish_transfer (grub_usb_controller_t dev,
    *    i.e. it is safe to free all TDs except last not processed
    * ED HEAD == TAIL == phys. addr. of td_current_virt */
 
-  /* Reset DoneHead - sanity cleanup */
-  o->hcca->donehead = 0;
-  grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
-  /* Read back of register should ensure it is really written */
-  grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
-
   /* Un-chainig of last TD */
   if (cdata->td_current_virt->prev_td_phys)
     {
@@ -929,10 +920,13 @@ finish_transfer (grub_usb_controller_t dev,
       
       if (cdata->td_current_virt == (grub_ohci_td_t) td_prev_virt->link_td)
         td_prev_virt->link_td = 0;
+
+      cdata->td_current_virt->prev_td_phys = 0;
     }
 
   grub_dprintf ("ohci", "OHCI finished, freeing\n");
   grub_ohci_free_tds (o, cdata->td_head_virt);
+  grub_free (cdata);
 }
 
 static grub_usb_err_t
@@ -951,28 +945,10 @@ parse_halt (grub_usb_controller_t dev,
   pre_finish_transfer (dev, transfer);
 
   /* First we must get proper tderr_phys value */
-  if (o->bad_OHCI) /* In case of bad_OHCI tderr_phys can be wrong */
-    {
-      if (cdata->tderr_phys) /* check if tderr_phys points to TD with error */
-       errcode = grub_le_to_cpu32 (grub_ohci_td_phys2virt (o,
-                                                           cdata->tderr_phys)->token)
-         >> 28;
-      if ( !cdata->tderr_phys || !errcode ) /* tderr_phys not valid or points to wrong TD */
-       { /* Retired TD with error should be previous TD to ED->td_head */
-         cdata->tderr_phys = grub_ohci_td_phys2virt (o,
-                                                     grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf )
-           ->prev_td_phys;
-       }
-    }
-  /* Even if we have "good" OHCI, in some cases
-   * tderr_phys can be zero, check it */
-  else if (!cdata->tderr_phys)
-    /* Retired TD with error should be previous TD to ED->td_head */
-    cdata->tderr_phys 
-      = grub_ohci_td_phys2virt (o,
-                               grub_le_to_cpu32 (cdata->ed_virt->td_head)
-                               & ~0xf)->prev_td_phys;
-    
+  /* Retired TD with error should be previous TD to ED->td_head */
+  cdata->tderr_phys = grub_ohci_td_phys2virt (o,
+                                                grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf )
+                     ->prev_td_phys;
 
   /* Prepare pointer to last processed TD and get error code */
   tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
@@ -1090,16 +1066,12 @@ parse_success (grub_usb_controller_t dev,
 
   pre_finish_transfer (dev, transfer);
 
-  /* Simple workaround if donehead is not working */
-  if (o->bad_OHCI &&
-      (!cdata->tderr_phys || (cdata->tderr_phys != cdata->td_last_phys)))
-    {
-      grub_dprintf ("ohci", "normal finish, but tderr_phys corrected\n");
-      cdata->tderr_phys = cdata->td_last_phys;
-      /* I hope we can do it as transfer (most probably) finished OK */
-    }
+  /* I hope we can do it as transfer (most probably) finished OK */
+  cdata->tderr_phys = cdata->td_last_phys;
+
   /* Prepare pointer to last processed TD */
   tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
+  
   /* Set index of last processed TD */
   if (tderr_virt)
     transfer->last_trans = tderr_virt->tr_index;
@@ -1168,25 +1140,6 @@ grub_ohci_check_transfer (grub_usb_controller_t dev,
 
   /* Check transfer status */
   intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
-  if (!o->bad_OHCI && (intstatus & 0x2) != 0)
-    {
-      /* Remember last successful TD */
-      cdata->tderr_phys = grub_le_to_cpu32 (o->hcca->donehead) & ~0xf;
-      /* Reset DoneHead */
-      o->hcca->donehead = 0;
-      grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
-      /* Read back of register should ensure it is really written */
-      grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
-      /* if TD is last, finish */
-      if (cdata->tderr_phys == cdata->td_last_phys)
-       {
-         if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)
-           return parse_halt (dev, transfer, actual);
-         else
-           return parse_success (dev, transfer, actual);
-       }
-      return GRUB_USB_ERR_WAIT;
-    }
 
   if ((intstatus & 0x10) != 0)
     /* Unrecoverable error - only reset can help...! */
@@ -1194,54 +1147,20 @@ grub_ohci_check_transfer (grub_usb_controller_t dev,
 
   /* Detected a HALT.  */
   if ((grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1))
-    {
-      /* ED is halted, but donehead event can happened in the meantime */
-      intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
-      if (!o->bad_OHCI && (intstatus & 0x2) != 0)
-       {
-         /* Remember last successful TD */
-         cdata->tderr_phys = grub_le_to_cpu32 (o->hcca->donehead) & ~0xf;
-         /* Reset DoneHead */
-         o->hcca->donehead = 0;
-         grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
-         /* Read back of register should ensure it is really written */
-         grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
-         /* if TD is last, finish */
-       }
-      return parse_halt (dev, transfer, actual);
-    }
+    return parse_halt (dev, transfer, actual);
 
-  /* bad OHCI handling */
+  /* Finished ED detection */
   if ( (grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf) ==
        (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf) ) /* Empty ED */
     {
-      if (o->bad_OHCI) /* Bad OHCI detected previously */
-       {
-         /* Try get last successful TD. */
-         cdata->tderr_phys = grub_le_to_cpu32 (o->hcca->donehead) & ~0xf;
-         if (cdata->tderr_phys)/* Reset DoneHead if we were successful */
-           {
-             o->hcca->donehead = 0;
-             grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
-             /* Read back of register should ensure it is really written */
-             grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
-           }
-         /* Check the HALT bit */
-         if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)
-           return parse_halt (dev, transfer, actual);
-         else
-           return parse_success (dev, transfer, actual);
-       }
-      else /* Detection of bad OHCI */
-       /* We should wait short time (~2ms) before we say that
-        * it is bad OHCI to prevent some hazard -
-        * donehead can react in the meantime. This waiting is done
-        * only once per OHCI driver "live cycle". */
-       if (!cdata->bad_OHCI_delay) /* Set delay time */
-         cdata->bad_OHCI_delay = grub_get_time_ms () + 2;
-       else if (grub_get_time_ms () >= cdata->bad_OHCI_delay)
-         o->bad_OHCI = 1;
-      return GRUB_USB_ERR_WAIT;
+      /* Check the HALT bit */
+      /* It looks like nonsense - it was tested previously...
+       * but it can change because OHCI is working
+       * simultaneously via DMA... */
+      if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)
+       return parse_halt (dev, transfer, actual);
+      else
+        return parse_success (dev, transfer, actual);
     }
 
   return GRUB_USB_ERR_WAIT;
@@ -1266,14 +1185,16 @@ grub_ohci_cancel_transfer (grub_usb_controller_t dev,
   /* Wait for new SOF */
   while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0);
 
-  /* Now we must find last processed TD if bad_OHCI == TRUE */
-  if (o->bad_OHCI)
-    /* Retired TD with error should be previous TD to ED->td_head */
-    cdata->tderr_phys
-      = grub_ohci_td_phys2virt (o, grub_le_to_cpu32 (cdata->ed_virt->td_head)
-                               & ~0xf)->prev_td_phys;
+  /* Possible retired TD with error should be previous TD to ED->td_head */
+  cdata->tderr_phys
+    = grub_ohci_td_phys2virt (o, grub_le_to_cpu32 (cdata->ed_virt->td_head)
+                              & ~0xf)->prev_td_phys;
     
   tderr_virt = grub_ohci_td_phys2virt (o,cdata-> tderr_phys);
+
+  grub_dprintf ("ohci", "Cancel: tderr_phys=0x%08x, tderr_virt=0x%08x\n",
+                cdata->tderr_phys, (unsigned int)tderr_virt);
+
   if (tderr_virt)
     transfer->last_trans = tderr_virt->tr_index;
   else
@@ -1290,6 +1211,7 @@ grub_ohci_portstatus (grub_usb_controller_t dev,
 {
    struct grub_ohci *o = (struct grub_ohci *) dev->data;
    grub_uint64_t endtime;
+   int i;
 
    grub_dprintf ("ohci", "begin of portstatus=0x%02x\n",
                  grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
@@ -1310,31 +1232,47 @@ grub_ohci_portstatus (grub_usb_controller_t dev,
        return GRUB_ERR_NONE;
      }
      
-   /* Reset the port */
-   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
-                        GRUB_OHCI_SET_PORT_RESET);
-   grub_millisleep (50); /* For root hub should be nominaly 50ms */
-    /* End the reset signaling.  */
-   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
-                        GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE);
-   grub_millisleep (10);
+   /* OHCI does one reset signal 10ms long but USB spec.
+    * requests 50ms for root hub (no need to be continuous).
+    * So, we do reset 5 times... */
+   for (i = 0; i < 5; i++)
+     {
+       /* Reset the port - timing of reset is done by OHCI */
+       grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+                             GRUB_OHCI_SET_PORT_RESET);
+
+       /* Wait for reset completion */
+       endtime = grub_get_time_ms () + 1000;
+       while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
+               & GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE))
+         if (grub_get_time_ms () > endtime)
+           return grub_error (GRUB_ERR_IO, "OHCI Timed out - reset");
 
-   /* Enable the port and wait for it. */
+       /* End the reset signaling - reset the reset status change */
+       grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+                            GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE);
+       grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
+     }
+
+   /* Enable port */
    grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
                          GRUB_OHCI_SET_PORT_ENABLE);
+   grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
+   
+   /* Wait for signal enabled */
    endtime = grub_get_time_ms () + 1000;
    while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
            & (1 << 1)))
      if (grub_get_time_ms () > endtime)
        return grub_error (GRUB_ERR_IO, "OHCI Timed out - enable");
 
-   grub_millisleep (10);
-
    /* Reset bit Connect Status Change */
    grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
                          GRUB_OHCI_RESET_CONNECT_CHANGE);
 
+   /* "Reset recovery time" (USB spec.) */
+   grub_millisleep (10);
+   
    grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
                 grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
  
index d51aace8cf173ec5f5375023ac24ba2df3556083..2265ebed0629454821fc9f17e549000b3e795b7d 100644 (file)
@@ -50,9 +50,12 @@ enum
     GRUB_UHCI_REG_PORTSC_SUSPEND         = 0x1000,
     GRUB_UHCI_REG_PORTSC_RW = GRUB_UHCI_REG_PORTSC_PORT_ENABLED
     | GRUB_UHCI_REG_PORTSC_RESUME | GRUB_UHCI_REG_PORTSC_RESET
-    | GRUB_UHCI_REG_PORTSC_SUSPEND
+    | GRUB_UHCI_REG_PORTSC_SUSPEND,
+    /* These bits should not be written as 1 unless we really need it */
+    GRUB_UHCI_PORTSC_RWC = ((1 << 1) | (1 << 3) | (1 << 11) | (3 << 13))
   };
 
+#define 
 
 /* UHCI Queue Head.  */
 struct grub_uhci_qh
@@ -578,23 +581,23 @@ grub_uhci_check_transfer (grub_usb_controller_t dev,
        err = GRUB_USB_ERR_STALL;
       
       /* Check if an error related to the data buffer occurred.  */
-      if (errtd->ctrl_status & (1 << 21))
+      else if (errtd->ctrl_status & (1 << 21))
        err = GRUB_USB_ERR_DATA;
       
       /* Check if a babble error occurred.  */
-      if (errtd->ctrl_status & (1 << 20))
+      else if (errtd->ctrl_status & (1 << 20))
        err = GRUB_USB_ERR_BABBLE;
       
       /* Check if a NAK occurred.  */
-      if (errtd->ctrl_status & (1 << 19))
+      else if (errtd->ctrl_status & (1 << 19))
        err = GRUB_USB_ERR_NAK;
       
       /* Check if a timeout occurred.  */
-      if (errtd->ctrl_status & (1 << 18))
+      else if (errtd->ctrl_status & (1 << 18))
        err = GRUB_USB_ERR_TIMEOUT;
       
       /* Check if a bitstuff error occurred.  */
-      if (errtd->ctrl_status & (1 << 17))
+      else if (errtd->ctrl_status & (1 << 17))
        err = GRUB_USB_ERR_BITSTUFF;
       
       if (err)
@@ -685,7 +688,7 @@ grub_uhci_portstatus (grub_usb_controller_t dev,
       endtime = grub_get_time_ms () + 1000;
       while ((grub_uhci_readreg16 (u, reg) & (1 << 2)))
         if (grub_get_time_ms () > endtime)
-          return grub_error (GRUB_ERR_IO, "UHCI Timed out");
+          return grub_error (GRUB_ERR_IO, "UHCI Timed out - disable");
 
       status = grub_uhci_readreg16 (u, reg);
       grub_dprintf ("uhci", ">3detect=0x%02x\n", status);
@@ -693,28 +696,37 @@ grub_uhci_portstatus (grub_usb_controller_t dev,
     }
     
   /* Reset the port.  */
-  grub_uhci_writereg16 (u, reg, 1 << 9);
+  status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
+  grub_uhci_writereg16 (u, reg, status | (1 << 9));
+  grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 
   /* Wait for the reset to complete.  XXX: How long exactly?  */
   grub_millisleep (50); /* For root hub should be nominaly 50ms */
-  status = grub_uhci_readreg16 (u, reg);
+  status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
   grub_uhci_writereg16 (u, reg, status & ~(1 << 9));
-  grub_dprintf ("uhci", "reset completed\n");
-  grub_millisleep (10);
+  grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 
-  /* Enable the port.  */
-  grub_uhci_writereg16 (u, reg, 1 << 2);
-  grub_millisleep (10);
+  /* Note: some debug prints were removed because they affected reset/enable timing. */
 
-  grub_dprintf ("uhci", "waiting for the port to be enabled\n");
+  grub_millisleep (1); /* Probably not needed at all or only few microsecs. */
+
+  /* Reset bits Connect & Enable Status Change */
+  status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
+  grub_uhci_writereg16 (u, reg, status | (1 << 3) | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
+  grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
+
+  /* Enable the port.  */
+  status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
+  grub_uhci_writereg16 (u, reg, status | (1 << 2));
+  grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 
   endtime = grub_get_time_ms () + 1000;
   while (! ((status = grub_uhci_readreg16 (u, reg)) & (1 << 2)))
     if (grub_get_time_ms () > endtime)
-      return grub_error (GRUB_ERR_IO, "UHCI Timed out");
+      return grub_error (GRUB_ERR_IO, "UHCI Timed out - enable");
 
-  /* Reset bit Connect Status Change */
-  grub_uhci_writereg16 (u, reg, status | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
+  /* Reset recovery time */
+  grub_millisleep (10);
 
   /* Read final port status */
   status = grub_uhci_readreg16 (u, reg);
index 2bd805ef24c673d6648e17dad280ef69b77e73ea..e17bf0c9d28a289c1ba6206d693cc26d4840f20c 100644 (file)
@@ -262,7 +262,7 @@ void grub_usb_device_attach (grub_usb_device_t dev)
 
       if (dev->config[0].interf[i].attached)
        continue;
-
+       
       for (desc = attach_hooks; desc; desc = desc->next)
        if (interf->class == desc->class && desc->hook (dev, 0, i))
          dev->config[0].interf[i].attached = 1;
index 0ddba32559c3c8fca3431b5067bf5659346b9e12..73d233642ab0c33efabc64ae47cc61766569562b 100644 (file)
@@ -179,19 +179,45 @@ 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;
 
+#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
+  /* 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_dprintf ("usb", "total=%d\n", total);
+  if (total >= 2000)
+    return;
 
   /* Enable the port.  */
   err = hub->controller->dev->portstatus (hub->controller, portno, 1);
   if (err)
     return;
+  hub->controller->dev->pending_reset = grub_get_time_ms () + 5000;
 
   /* Enable the port and create a device.  */
   dev = grub_usb_hub_add_dev (hub->controller, speed);
+  hub->controller->dev->pending_reset = 0;
   if (! dev)
     return;
 
@@ -238,11 +264,14 @@ grub_usb_root_hub (grub_usb_controller_t controller)
   for (i = 0; i < hub->nports; i++)
     {
       grub_usb_speed_t speed;
-      speed = controller->dev->detect_dev (hub->controller, i,
-                                          &changed);
-
-      if (speed != GRUB_USB_SPEED_NONE)
-       attach_root_port (hub, i, 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);
+        }
     }
 
   return GRUB_USB_ERR_NONE;
@@ -284,6 +313,7 @@ poll_nonroot_hub (grub_usb_device_t dev)
   unsigned i;
   grub_uint8_t changed;
   grub_size_t actual;
+  int j, total;
 
   if (!dev->hub_transfer)
     return;
@@ -308,6 +338,7 @@ 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)))
        continue;
@@ -319,7 +350,8 @@ poll_nonroot_hub (grub_usb_device_t dev)
                                  GRUB_USB_REQ_GET_STATUS,
                                  0, i, sizeof (status), (char *) &status);
 
-      grub_printf ("i = %d, status = %08x\n", i, status);
+      grub_printf ("dev = 0x%0x, i = %d, status = %08x\n",
+                   (unsigned int) dev, i, status);
 
       if (err)
        continue;
@@ -346,7 +378,8 @@ poll_nonroot_hub (grub_usb_device_t dev)
                              GRUB_USB_REQ_CLEAR_FEATURE,
                              GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT, i, 0, 0);
 
-      if (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED)
+      if (!dev->controller.dev->pending_reset &&
+          (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED))
        {
          grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
                                      | GRUB_USB_REQTYPE_CLASS
@@ -360,8 +393,36 @@ poll_nonroot_hub (grub_usb_device_t dev)
          /* Connected and status of connection changed ? */
          if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)
            {
-             /* A device is actually connected to this port.
-              * Now do reset of port. */
+             /* A device is actually connected to this port. */
+  /* 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 = 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_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),
@@ -369,6 +430,15 @@ poll_nonroot_hub (grub_usb_device_t dev)
                                    GRUB_USB_HUB_FEATURE_PORT_RESET,
                                    i, 0, 0);
              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;
+              return;
            }
        }
 
@@ -401,6 +471,7 @@ poll_nonroot_hub (grub_usb_device_t dev)
 
              /* Add the device and assign a device address to it.  */
              next_dev = grub_usb_hub_add_dev (&dev->controller, speed);
+             dev->controller.dev->pending_reset = 0;
              if (! next_dev)
                continue;
 
@@ -426,12 +497,21 @@ grub_usb_poll_devices (void)
       /* No, it should be never changed, it should be constant. */
       for (i = 0; i < hub->nports; i++)
        {
-         grub_usb_speed_t speed;
+         grub_usb_speed_t speed = GRUB_USB_SPEED_NONE;
          int changed = 0;
 
-         speed = hub->controller->dev->detect_dev (hub->controller, i,
-                                                   &changed);
-
+          if (!hub->controller->dev->pending_reset)
+            {
+              /* Check for possible timeout */
+              if (grub_get_time_ms () > hub->controller->dev->pending_reset)
+                {
+                  /* Something went wrong, reset device was not
+                   * addressed properly, timeout happened */
+                 hub->controller->dev->pending_reset = 0;
+                 speed = hub->controller->dev->detect_dev (hub->controller,
+                                                            i, &changed);
+                }
+            }
          if (changed)
            {
              detach_device (hub->devices[i]);
index 6b9b1a5b9da8f075d8ede5fcbcad3357c15a281e..afd2eb0a5374f9e6259343a88a18aefbc313e039 100644 (file)
@@ -33,10 +33,12 @@ grub_usb_execute_and_wait_transfer (grub_usb_device_t dev,
   grub_usb_err_t err;
   grub_uint64_t endtime;
 
-  endtime = grub_get_time_ms () + timeout;
   err = dev->controller.dev->setup_transfer (&dev->controller, transfer);
   if (err)
     return err;
+  /* endtime moved behind setup transfer to prevent false timeouts
+   * while debugging... */
+  endtime = grub_get_time_ms () + timeout;
   while (1)
     {
       err = dev->controller.dev->check_transfer (&dev->controller, transfer,
index f9cdb27659fab583f6a19392ddaea7c935ae0d34..6f838e4f943a7d871e3b413bfafc55326b8eef84 100644 (file)
@@ -116,6 +116,9 @@ struct grub_usb_controller_dev
 
   grub_usb_speed_t (*detect_dev) (grub_usb_controller_t dev, int port, int *changed);
 
+  /* Per controller flag - port reset pending, don't do another reset */
+  grub_uint64_t pending_reset;
+  
   /* The next host controller.  */
   struct grub_usb_controller_dev *next;
 };