Hi Vladimir (and, of course, possibly others...),

there is patch which (perhaps) solves some bugs I found in kbdlayouts
branch.

It includes:
0. Previously sent patch for UHCI low speed problem.
1. UHCI transfer error evaluation
2. OHCI cdata de-allocation
3. OHCI proper previous TD unchaining
4. OHCI EDs quantity
5. "Simplified" OHCI driver
6. Non-root hub "multiple reset"
7. OHCI/UHCI port reset handling

Patch is done against kbdlayouts branch version 2424.
I did not commit changes into branch repository.

Please test it on Yeelong and possible other machines, it contains
significant changes (mainly points 5,6,7).


Details about points:

ad 0.
Unfortunately, I still don't have progress with low-speed keyboard -
interrupt (bulk) transfer is not working on UHCI nor OHCI. This patch
solves control transfer only and I still don't know why.

ad 1.
More than one bit can be set in register at the end of transfer and old
evaluation of error code was wrong. Now it is better but probably still
not totally exact - but GRUB does not have so much different USB error
codes currently, so it probably makes not sense.

ad 2.
I did not find de-allocation of cdata in OHCI, so I added it into
finish_transfer.

ad 3.
I found some small mistake in OHCI TD unchaining of last (not processed)
TD which sometimes resulted in bad toggle value.

ad 4.
Increased possible number of connected devices or unplug/plug cycles for
OHCI driver - I wrote approx. two/three months ago about such expected
simple change.

ad 5.
I removed interrupt-like processing of transfers from OHCI driver. I
decided it is unnecessarily complex to implement proper handling of
donehead when "non-single thread execution" of transfer is used
(different cdata).
Only "bad OHCI" code is now in driver (in fact it is your old original
idea/algorithm with some hazards removed and little bit improved).
It should work - I tested it on my OHCI machine and it works well.

ad 6.
There was one big trouble - if I connected into PC hub with two or more
devices connected, usually none was working.
It was caused by "multiple reset" - all devices connected on hub were
reset in the same time, i.e. all devices become into default state with
address 0 - USB specification does not allow such situation, only one
device on one controller can have address 0 at the same time.
I made some relative simple workaround via special flag in controller
structure - it looks working.

ad 7.
I had problems with some devices to be evaluated - hotplugging changed
timing of device detection and evaluation process, so I tried to change
it into more proper way, according USB1.x/2.0, OHCI and UHCI
specifications.
There are three main things which are important:
a. Wait for stable power and insertion procedure completion before issue
reset - at least 100ms, I found more safe it is 250ms
b. Port reset and enable procedure and its timing (50ms reset signal and
after very small delay enable request)
c. Reset recovery delay 10ms

Point a. is little bit complicated by possible not stable "connected"
signal - this problem I detected on my older computer with worse
contacts and also on some very small (usually cheap) devices.

Point b. is relative easy on OHCI and non-root hubs, because OHCI or hub
handles reset procedure timing by itself and port is automatically
enabled after reset. But control of such procedure was not good on OHCI,
I changed it to respect automatic functions of OHCI.
On UHCI reset procedure is handled only via SW and we did not it
properly - it was reason, why some my devices were working on non-root
hub port and not on UHCI root port. I changed more things there, I was
inspired by USB specification and uhci-hub.c from Linux (but code is
different, I did not copy it). I am not sure what was more important -
if timing or bit masking... But result is - now are working all my
devices also on UHCI root port! Of course, I am not sure if it is fully
compatible...

Point c. is easy and it was included in portstatus before.

The last thing is - I deleted disabling of port before resetting and
enabling it - USB specification does not say it is necessary. But if
there is really some reason to use it, why not - but in this case maybe
some delay should be inserted between disable and enable, I not tested
it yet.

Regards
Ales

diff -urB ./kbdlayouts/grub-core/bus/usb/ohci.c ./kbdlayouts_changed/grub-core/bus/usb/ohci.c
--- ./kbdlayouts/grub-core/bus/usb/ohci.c	2010-09-03 22:13:28.000000000 +0200
+++ ./kbdlayouts_changed/grub-core/bus/usb/ohci.c	2010-09-12 16:39:39.000000000 +0200
@@ -98,7 +98,6 @@
   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 @@
 #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_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 @@
       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 @@
   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 @@
 
       /* 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 @@
   /* 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 @@
    *    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 @@
       
       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 @@
   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 +1067,12 @@
 
   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 +1141,6 @@
 
   /* 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 +1148,20 @@
 
   /* 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 +1186,16 @@
   /* 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
@@ -1310,27 +1232,39 @@
        return GRUB_ERR_NONE;
      }
      
-   /* Reset the port */
+   /* Reset the port - timing of reset is done by OHCI */
    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);
 
-   /* Enable the port and wait for it. */
-   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
-                         GRUB_OHCI_SET_PORT_ENABLE);
+   /* Wait for reset completion */
    endtime = grub_get_time_ms () + 1000;
    while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
-           & (1 << 1)))
+           & GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE))
      if (grub_get_time_ms () > endtime)
-       return grub_error (GRUB_ERR_IO, "OHCI Timed out - enable");
+       return grub_error (GRUB_ERR_IO, "OHCI Timed out - reset");
 
-   grub_millisleep (10);
+   /* 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);
+
+   /* According to USB and OHCI specification, port should be enabled
+    * automaticaly now - but... */
+   if (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
+           & (1 << 1)))
+     {
+       /* Enable the port and wait for it. */
+       grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+                             GRUB_OHCI_SET_PORT_ENABLE);
+       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");
+     }
 
+   /* "Reset recovery time" (USB spec.) */
+   grub_millisleep (10);
+   
    /* Reset bit Connect Status Change */
    grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
                          GRUB_OHCI_RESET_CONNECT_CHANGE);
diff -urB ./kbdlayouts/grub-core/bus/usb/uhci.c ./kbdlayouts_changed/grub-core/bus/usb/uhci.c
--- ./kbdlayouts/grub-core/bus/usb/uhci.c	2010-09-03 22:13:28.000000000 +0200
+++ ./kbdlayouts_changed/grub-core/bus/usb/uhci.c	2010-09-12 15:56:42.000000000 +0200
@@ -53,6 +53,8 @@
     | GRUB_UHCI_REG_PORTSC_SUSPEND
   };
 
+/* These bits should not be written as 1 unless we really need it */
+#define GRUB_UHCI_PORTSC_ZERO ((1<<1) | (1<<3) | (1<<11) | (3<<13))
 
 /* UHCI Queue Head.  */
 struct grub_uhci_qh
@@ -414,7 +416,7 @@
 grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
 		       grub_transfer_type_t type, unsigned int addr,
 		       unsigned int toggle, grub_size_t size,
-		       grub_uint32_t data)
+		       grub_uint32_t data, grub_usb_speed_t speed)
 {
   grub_uhci_td_t td;
   static const unsigned int tf[] = { 0x69, 0xE1, 0x2D };
@@ -439,7 +441,8 @@
   td->linkptr = 1;
 
   /* Active!  Only retry a transfer 3 times.  */
-  td->ctrl_status = (1 << 23) | (3 << 27);
+  td->ctrl_status = (1 << 23) | (3 << 27) |
+                    ((speed == GRUB_USB_SPEED_LOW) ? (1 << 26) : 0);
 
   /* If zero bytes are transmitted, size is 0x7FF.  Otherwise size is
      size-1.  */
@@ -495,7 +498,8 @@
 
       td = grub_uhci_transaction (u, transfer->endpoint & 15, tr->pid,
 				  transfer->devaddr, tr->toggle,
-				  tr->size, tr->data);
+				  tr->size, tr->data,
+				  transfer->dev->speed);
       if (! td)
 	{
 	  grub_size_t actual = 0;
@@ -576,23 +580,23 @@
 	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)
@@ -683,7 +687,7 @@
       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);
@@ -691,28 +695,37 @@
     }
     
   /* Reset the port.  */
-  grub_uhci_writereg16 (u, reg, 1 << 9);
+  status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_ZERO;
+  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_ZERO;
   grub_uhci_writereg16 (u, reg, status & ~(1 << 9));
+  grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
   grub_dprintf ("uhci", "reset completed\n");
-  grub_millisleep (10);
+  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_ZERO;
+  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.  */
-  grub_uhci_writereg16 (u, reg, 1 << 2);
-  grub_millisleep (10);
+  status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_ZERO;
+  grub_uhci_writereg16 (u, reg, status | (1 << 2));
+  grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 
   grub_dprintf ("uhci", "waiting for the port to be enabled\n");
 
   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);
diff -urB ./kbdlayouts/grub-core/bus/usb/usb.c ./kbdlayouts_changed/grub-core/bus/usb/usb.c
--- ./kbdlayouts/grub-core/bus/usb/usb.c	2010-09-03 22:13:28.000000000 +0200
+++ ./kbdlayouts_changed/grub-core/bus/usb/usb.c	2010-09-12 16:40:41.000000000 +0200
@@ -262,7 +262,7 @@
 
       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;
diff -urB ./kbdlayouts/grub-core/bus/usb/usbhub.c ./kbdlayouts_changed/grub-core/bus/usb/usbhub.c
--- ./kbdlayouts/grub-core/bus/usb/usbhub.c	2010-09-03 22:13:28.000000000 +0200
+++ ./kbdlayouts_changed/grub-core/bus/usb/usbhub.c	2010-09-12 11:35:17.000000000 +0200
@@ -179,19 +179,45 @@
 {
   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 @@
   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 @@
   unsigned i;
   grub_uint8_t changed;
   grub_size_t actual;
+  int j, total;
 
   if (!dev->hub_transfer)
     return;
@@ -308,6 +338,7 @@
   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 @@
 				  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 @@
 			      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 @@
 	  /* 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 @@
 				    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 @@
 
 	      /* 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 @@
       /* 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]);
diff -urB ./kbdlayouts/grub-core/bus/usb/usbtrans.c ./kbdlayouts_changed/grub-core/bus/usb/usbtrans.c
--- ./kbdlayouts/grub-core/bus/usb/usbtrans.c	2010-09-03 22:13:28.000000000 +0200
+++ ./kbdlayouts_changed/grub-core/bus/usb/usbtrans.c	2010-09-10 23:10:27.000000000 +0200
@@ -33,10 +33,12 @@
   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,
diff -urB ./kbdlayouts/include/grub/usb.h ./kbdlayouts_changed/include/grub/usb.h
--- ./kbdlayouts/include/grub/usb.h	2010-09-03 22:13:28.000000000 +0200
+++ ./kbdlayouts_changed/include/grub/usb.h	2010-09-12 16:30:41.000000000 +0200
@@ -116,6 +116,9 @@
 
   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;
 };
_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to