Hi,

there is some new patch which includes:

- OHCI power and configuration counter check patch sent by Vladimir in
the meantime

- simple handling of unrecoverable OHCI error (not tested yet - I cannot
set OHCI into unrec. error state...)

- (I hope) proper toggle bit handling also in error states - ohci.c,
uhci.c, usbtrans.c, usbtrans.h

- some changes in OHCI DoneHead handling (it is partialy related to
toggle bit handling)

- some changes in bulk transfers error handling in usbms, should be more
close to specification now

- scsi.c simple workaround related to problem with device which cannot
transfer 4KB blocks - but it is disabled. It works but transfer is very
slow when this workaround is active - I don't know why. It is little bit
surprising - device works better without this workaround... So I
disabled it.

Patch is made against files taken from revision 2391 (should be the same
as in 1.98 release) patched by my previous patch usb_patch_100523_0.
Don't use my intermediate "toggle" patch from 25 May 2010.


Remaining problems:

1.
Some devices (at least my BUFFALO USB clip drive flash disk, more
precisely "ID 0ea0:2168 Ours Technology, Inc. Transcend JetFlash 2.0 /
Astone USB Drive") cannot transfer 4KB data blocks independent on
controller OHCI/UHCI - my workaround is not good.
But it is probably problem with low priority - device is working finally
but it is slower than another device.

2.
Some devices (at least my APACER cardreader "ID 058f:6366 Alcor Micro
Corp. Multi Flash Reader") are not working on UHCI. Such device does not
accept any control message and UHCI returns status 0x450007 - it means
STALL during sending SETUP packet.
It looks to be the same problem as described by Vladimir: "I have
somewhat similar issue with Geode OHCI controller right now: devices and
speeds are correctly seen but trying to send a message results in a halt
in first TD and error code 5.".
But I have problem on UHCI, not on OHCI - on computer with OHCI is this
device working well (it is normal USB Mass Storage Bulk-Only device with
SCSI subclass)! Maybe it depends on "combined controllers" UHCI-EHCI,
OHCI-EHCI (?) - device is working on computer with OHCI only computer
and it is not working on computer with UHCI-EHCI controller. But any
other device is working well on both computers... I don't understand, I
currently have no idea what can be wrong. Does anybody know...?

3.
There is not working USB hub support, GRUB does not see device connected
via USB hub - does anybody know some details or have some specification
of USB Hub class ? I cannot find it on USB site (maybe I have not
sufficient patience...).


I will probably focus in OHCI speed-up now, i.e. I try to do some other
handling of ED to prevent changes in OHCI registers which are slowing
down OHCI performance (OHCI is approx. 3 times slower than UHCI now from
this reason).


Best regards
Ales

diff -urB ./grub/bus/usb/ohci.c ./grub_patched/bus/usb/ohci.c
--- ./grub/bus/usb/ohci.c	2010-05-30 15:09:07.000000000 +0200
+++ ./grub_patched/bus/usb/ohci.c	2010-05-28 19:02:38.000000000 +0200
@@ -103,6 +103,11 @@
 #define GRUB_OHCI_RHUB_PORT_POWER_MASK 0x300
 #define GRUB_OHCI_RHUB_PORT_ALL_POWERED 0x200
 
+#define GRUB_OHCI_SET_PORT_ENABLE (1 << 1)
+#define GRUB_OHCI_CLEAR_PORT_ENABLE (1 << 0)
+#define GRUB_OHCI_SET_PORT_RESET (1 << 4)
+#define GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE (1 << 20)
+
 static grub_uint32_t
 grub_ohci_readreg32 (struct grub_ohci *o, grub_ohci_reg_t reg)
 {
@@ -234,7 +239,7 @@
 
   /* Misc. pre-sets. */
   o->hcca->donehead = 0;
-  grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1)); /* Clears WDH */
+  grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
@@ -351,8 +356,10 @@
       break;
     }
 
+#if 0 /* Always generate interrupt */
   /* Generate no interrupts.  */
   token |= 7 << 21;
+#endif
 
   /* Set the token.  */
   token |= toggle << 24;
@@ -395,9 +402,11 @@
   grub_usb_err_t err;
   grub_uint8_t errcode = 0;
   grub_ohci_td_t tderr = NULL;
-  int i, j;
+  int i;
   grub_uint64_t maxtime;
   int err_timeout = 0;
+  int err_unrec = 0;
+  grub_uint32_t intstatus;
 
   /* Allocate an Endpoint Descriptor.  */
   ed = grub_memalign (16, sizeof (*ed));
@@ -424,11 +433,13 @@
       td_list[i].next_td = grub_cpu_to_le32 (&td_list[i + 1]);
     }
 
+#if 0 /* Better will be enable interrupt on all TDs. */
   /* The last-1 TD token we should change to enable interrupt when TD finishes.
    * As OHCI interrupts are disabled, it does only setting of WDH bit in
    * HcInterruptStatus register - and that is what we want to safely detect
    * normal end of all transactions. */
   td_list[transfer->transcnt - 1].token &= ~(7 << 21);
+#endif
 
   td_list[transfer->transcnt].token = 0;
   td_list[transfer->transcnt].buffer = 0;
@@ -530,44 +541,49 @@
     }
 
   grub_dprintf ("ohci", "wait for completion\n");
-  grub_dprintf ("ohci", "control=0x%02x status=0x%02x\n",
+  grub_dprintf ("ohci", "begin: control=0x%02x status=0x%02x\n\t\t intstatus=0x%02x\n",
   grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL),
-  grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS));
+  grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS),
+  grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS));
 
   /* Starting time for timeout - feel free to change the value,
    * I have no idea about correct value ...
    * It is workaround only because it looks like my OHCI does not
-   * inicate properly STALL or NAK (or both...) - or there is
+   * indicate properly STALL or NAK (or both...) - or there is
    * some mistake somewhere... */
   maxtime = grub_get_time_ms () + 1000;
-	
+
   /* Wait until the transfer is completed or STALLs.  */
   do
     {
-      grub_cpu_idle ();
-
-      /* Detected a HALT.  */
-      if (grub_le_to_cpu32 (ed->td_head) & 1)
-        break;
-  
-      if ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x2) != 0)
+      /* Check transfer status */
+      intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+      if ((intstatus & 0x2) != 0)
         {
-          if ((grub_le_to_cpu32 (o->hcca->donehead) & ~0xf)
-               == (grub_uint32_t) &td_list[transfer->transcnt - 1])
-            break;
-
-          /* Done Head can be updated on some another place if ED is halted. */          
-          if (grub_le_to_cpu32 (ed->td_head) & 1)
-            break;
-
-          /* If there is not HALT in ED, it is not correct, so debug it, reset
-           * donehead and WDH and continue waiting. */
-          grub_dprintf ("ohci", "Incorrect HccaDoneHead=0x%08x\n",
+          grub_dprintf ("ohci", "Current HccaDoneHead=0x%08x\n",
                         o->hcca->donehead);
+          /* Remember last successful TD */
+          tderr = (grub_ohci_td_t)
+            (grub_le_to_cpu32 (o->hcca->donehead) & ~0xf);
+          /* Reset DoneHead */
           o->hcca->donehead = 0;
           grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
+          /* if TD is last, finish */
+          if (tderr == (grub_ohci_td_t) &td_list[transfer->transcnt - 1])
+            break;
           continue;
         }
+
+      if ((intstatus & 0x10) != 0)
+        { /* Unrecoverable error - only reset can help...! */
+          err_unrec = 1;
+          break;
+        }
+
+      /* Detected a HALT.  */
+      if (grub_le_to_cpu32 (ed->td_head) & 1)
+        break;
+
       /* Timeout ? */
       if (grub_get_time_ms () > maxtime)
       	{
@@ -577,9 +593,49 @@
       	  err_timeout = 1;
       	  break;
       	}
+
+      grub_cpu_idle ();
     }
   while (1);
+  
+  grub_dprintf ("ohci", "end: control=0x%02x status=0x%02x\n\t\t intstatus=0x%02x\n",
+    grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL),
+    grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS),
+    grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS));
+
+  if (!tderr)
+    {
+      /* It means that something wrong happened,
+       * it could be:
+       * - timeout and no TD processed
+       * - some or unrecoverable error and no TD processed
+       * - something unexpected... :-( */
+      /* Try look into DONEHEAD reg., but there should be also zero */
+      grub_dprintf("ohci", "HCCA DoneHead is zero, something is bad!\n");
+      tderr = (grub_ohci_td_t)
+                (grub_ohci_readreg32 (o, GRUB_OHCI_REG_DONEHEAD) & ~0xf);
+    }
+  
+  /* Remember last processed transaction (TD) - it is necessary for
+   * proper setting of toggle bit in next transaction. */
+  transfer->last_trans = ((grub_uint32_t)tderr - (grub_uint32_t)td_list) / sizeof (*td_list);
+  /* The expression above maybe will not work on Yeeloong because
+   * of DMA memory mapping. (?) I.e., maybe we will need to have some
+   * additional informations in TDs. */
+  
+  /* Check correct value in last_trans */
+  /* It could happen if timeout happens and no TD was retired */
+  if (transfer->last_trans >= transfer->transcnt || !tderr)
+    {
+      grub_dprintf("ohci", "tder==0 or out of TDs range!\n");
+      grub_dprintf("ohci", "tderr=%p, td_list=%p,\n\t\t last_trans=%d, transcnt=%d\n",
+                   tderr, td_list, transfer->last_trans, transfer->transcnt);
+      /* We should set something valid... */
+      transfer->last_trans = -1; /* Probably no TD done */
+      tderr = &td_list[0]; 
+    }
 
+  /* In case of timeout do not detect error from TD */    
   if (err_timeout)
     {
       err = GRUB_ERR_TIMEOUT;
@@ -590,15 +646,19 @@
       grub_le_to_cpu32(ed->next_ed));
     }
   		
+  /* In case of unrecoverable error do not detect error from TD */    
+  else if (err_unrec)
+    {
+      err = GRUB_USB_ERR_UNRECOVERABLE;
+      grub_dprintf("ohci", "Unrecoverable error, target=%08x, head=%08x\n\t\ttail=%08x, next=%08x\n",
+      grub_le_to_cpu32(ed->target),
+      grub_le_to_cpu32(ed->td_head),
+      grub_le_to_cpu32(ed->td_tail),
+      grub_le_to_cpu32(ed->next_ed));
+    }
+  		
   else if (grub_le_to_cpu32 (ed->td_head) & 1)
     {
-      tderr = (grub_ohci_td_t)
-                (grub_ohci_readreg32 (o, GRUB_OHCI_REG_DONEHEAD) & ~0xf);
-      if (tderr == 0)
-        /* If DONEHEAD==0 it means that correct address is in HCCA.
-         * It should be always now! */
-        tderr = (grub_ohci_td_t) (grub_le_to_cpu32 (o->hcca->donehead) & ~0xf);
-
       errcode = grub_le_to_cpu32 (tderr->token) >> 28;
       
       switch (errcode)
@@ -645,17 +705,17 @@
 	case 8:
 	  /* XXX: Data overrun error.  */
 	  err = GRUB_USB_ERR_DATA;
-	  j = ((grub_uint32_t)tderr - (grub_uint32_t)td_list) / sizeof (*td_list);
-	  grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n", tderr, j);
+	  grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n",
+	                tderr, transfer->last_trans);
 	  break;
 
 	case 9:
 	  /* XXX: Data underrun error.  */
 	  err = GRUB_USB_ERR_DATA;
+	  grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n",
+	                tderr, transfer->last_trans);
 	  grub_dprintf ("ohci", "Underrun, number of not transferred bytes: %d\n",
 	                1 + grub_le_to_cpu32 (tderr->buffer_end) - grub_le_to_cpu32 (tderr->buffer));
-	  j = ((grub_uint32_t)tderr - (grub_uint32_t)td_list) / sizeof (*td_list);
-	  grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n", tderr, j);
 	  break;
 
 	case 10:
@@ -707,7 +767,8 @@
   /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
   /* Wait for new SOF */
-  while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0);
+  while (((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0)
+         && !err_unrec);
   /* Now it should be safe to change CONTROL and BULK lists. */
   
   /* Important cleaning. */
@@ -718,6 +779,28 @@
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
 
+  if (err_unrec)
+    {
+      /* Do OHCI reset in case of unrecoverable error - maybe we will need
+       * do more - re-enumerate bus etc. (?) */
+
+      /* Suspend the OHCI by issuing a reset.  */
+      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic.  */
+      grub_millisleep (1);
+      grub_dprintf ("ohci", "Unrecoverable error - OHCI reset\n");
+
+      /* Misc. resets. */
+      o->hcca->donehead = 0;
+      grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
+      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
+      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
+      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
+      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
+
+      /* Enable the OHCI.  */
+      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, (2 << 6));
+    }
+  
   grub_dprintf ("ohci", "OHCI finished, freeing, err=0x%02x (OHCI errcode=0x%02x)\n",
                 err, errcode);
   
@@ -730,34 +813,32 @@
 
 static grub_err_t
 grub_ohci_portstatus (grub_usb_controller_t dev,
-		      unsigned int port, unsigned int enable)
+              unsigned int port, unsigned int enable)
 {
    struct grub_ohci *o = (struct grub_ohci *) dev->data;
-   grub_uint32_t status;
 
-   status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
    grub_dprintf ("ohci", "begin of portstatus=0x%02x\n",
                  grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
 
-   /* Enable the port.  */
-   status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
-   status |= (enable << 1); /* XXX: Magic.  */
-   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, status);
-
-   /* Reset the port.  */
-   status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
-   status |= (1 << 4); /* XXX: Magic.  */
-   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, status);
+   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.  */
-   status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
-   status |= (1 << 20); /* XXX: Magic.  */
-   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, status);
+   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+             GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE);
    grub_millisleep (10);
 
-   status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
-   grub_dprintf ("ohci", "end of portstatus=0x%02x\n", status);
+   if (enable)
+     grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+               GRUB_OHCI_SET_PORT_ENABLE);
+   else
+     grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
+               GRUB_OHCI_CLEAR_PORT_ENABLE);
+   grub_millisleep (10);
+
+   grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
+         grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
 
    return GRUB_ERR_NONE;
 }
diff -urB ./grub/bus/usb/uhci.c ./grub_patched/bus/usb/uhci.c
--- ./grub/bus/usb/uhci.c	2010-05-30 15:09:07.000000000 +0200
+++ ./grub_patched/bus/usb/uhci.c	2010-05-28 21:26:39.000000000 +0200
@@ -332,13 +332,20 @@
 }
 
 static void
-grub_free_queue (struct grub_uhci *u, grub_uhci_td_t td)
+grub_free_queue (struct grub_uhci *u, grub_uhci_td_t td,
+                 grub_usb_transfer_t transfer)
 {
-  /* Free the TDs in this queue.  */
-  while (td)
+  int i; /* Index of TD in transfer */
+  
+  /* Free the TDs in this queue and set last_trans.  */
+  for (i=0; td; i++)
     {
       grub_uhci_td_t tdprev;
 
+      /* Check state of TD and possibly set last_trans */
+      if (transfer && (td->linkptr & 1))
+        transfer->last_trans = i;
+      
       /* Unlink the queue.  */
       tdprev = td;
       td = (grub_uhci_td_t) td->linkptr2;
@@ -461,7 +468,7 @@
 	  td_prev->linkptr = 1;
 
 	  if (td_first)
-	    grub_free_queue (u, td_first);
+	    grub_free_queue (u, td_first, NULL);
 
 	  return GRUB_USB_ERR_INTERNAL;
 	}
@@ -560,7 +567,7 @@
   /* Place the QH back in the free list and deallocate the associated
      TDs.  */
   qh->elinkptr = 1;
-  grub_free_queue (u, td_first);
+  grub_free_queue (u, td_first, transfer);
 
   return err;
 }
@@ -609,7 +616,7 @@
   grub_uhci_writereg16 (u, reg, enable << 9);
 
   /* Wait for the reset to complete.  XXX: How long exactly?  */
-  grub_millisleep (10);
+  grub_millisleep (50); /* For root hub should be nominaly 50ms */
   status = grub_uhci_readreg16 (u, reg);
   grub_uhci_writereg16 (u, reg, status & ~(1 << 9));
   grub_dprintf ("uhci", "reset completed\n");
diff -urB ./grub/bus/usb/usbtrans.c ./grub_patched/bus/usb/usbtrans.c
--- ./grub/bus/usb/usbtrans.c	2010-05-30 15:09:07.000000000 +0200
+++ ./grub_patched/bus/usb/usbtrans.c	2010-05-28 16:05:04.000000000 +0200
@@ -167,6 +167,7 @@
   transfer->type = GRUB_USB_TRANSACTION_TYPE_BULK;
   transfer->max = max;
   transfer->dev = dev;
+  transfer->last_trans = -1; /* Reset index of last processed transaction (TD) */
 
   /* Allocate an array of transfer data structures.  */
   transfer->transactions = grub_malloc (transfer->transcnt
@@ -193,6 +194,13 @@
     }
 
   err = dev->controller.dev->transfer (&dev->controller, transfer);
+  /* We must remember proper toggle value even if some transactions
+   * were not processed - correct value should be inversion of last
+   * processed transaction (TD). */
+  if (transfer->last_trans >= 0)
+    toggle = transfer->transactions[transfer->last_trans].toggle ? 0 : 1;
+  else
+    toggle = dev->toggle[endpoint]; /* Nothing done, take original */
   grub_dprintf ("usb", "toggle=%d\n", toggle);
   dev->toggle[endpoint] = toggle;
 
diff -urB ./grub/disk/scsi.c ./grub_patched/disk/scsi.c
--- ./grub/disk/scsi.c	2010-05-30 15:09:07.000000000 +0200
+++ ./grub_patched/disk/scsi.c	2010-05-30 15:59:32.000000000 +0200
@@ -421,7 +421,7 @@
 
       /* According to USB MS tests specification, issue Test Unit Ready
        * until OK */
-      maxtime = grub_get_time_ms () + 1000;
+      maxtime = grub_get_time_ms () + 5000; /* It is safer value */
       do
         {
 	  /* Timeout is necessary - for example in case when we have
@@ -519,6 +519,37 @@
 
   /* XXX: Never reached.  */
   return GRUB_ERR_NONE;
+
+#if 0 /* Workaround - it works - but very slowly, from some reason
+       * unknown to me (specially on OHCI). Do not use it. */
+  /* Split transfer requests to device sector size because */
+  /* some devices are not able to transfer more than 512-1024 bytes */
+  grub_err_t err = GRUB_ERR_NONE;
+
+  for ( ; size; size--)
+    {
+      /* Depending on the type, select a read function.  */
+      switch (scsi->devtype)
+        {
+          case grub_scsi_devtype_direct:
+            err = grub_scsi_read10 (disk, sector, 1, buf);
+            break;
+
+          case grub_scsi_devtype_cdrom:
+            err = grub_scsi_read12 (disk, sector, 1, buf);
+            break;
+
+          default: /* This should not happen */
+            return GRUB_ERR_READ_ERROR;
+        }
+      if (err)
+        return err;
+      sector++;
+      buf += scsi->blocksize;
+    }
+
+  return err;
+#endif
 }
 
 static grub_err_t
diff -urB ./grub/disk/usbms.c ./grub_patched/disk/usbms.c
--- ./grub/disk/usbms.c	2010-05-30 15:09:07.000000000 +0200
+++ ./grub_patched/disk/usbms.c	2010-05-24 20:38:23.000000000 +0200
@@ -84,9 +84,10 @@
       struct grub_usb_desc_device *descdev = &usbdev->descdev;
       int i;
 
-      if (descdev->class != 0 || descdev->subclass || descdev->protocol != 0)
-	return 0;
-
+      if (descdev->class != 0 || descdev->subclass || descdev->protocol != 0
+         || descdev->configcnt == 0)
+         return 0;
+         
       /* XXX: Just check configuration 0 for now.  */
       for (i = 0; i < usbdev->config[0].descconf->numif; i++)
 	{
@@ -238,6 +239,7 @@
   struct grub_usbms_csw status;
   static grub_uint32_t tag = 0;
   grub_usb_err_t err = GRUB_USB_ERR_NONE;
+  grub_usb_err_t errCSW = GRUB_USB_ERR_NONE;
   int retrycnt = 3 + 1;
   grub_size_t i;
 
@@ -275,9 +277,8 @@
     {
       if (err == GRUB_USB_ERR_STALL)
 	{
-	  grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
 	  grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
-	  goto retry;
+	  goto CheckCSW;
 	}
       return grub_error (GRUB_ERR_IO, "USB Mass Storage request failed");
     }
@@ -287,7 +288,12 @@
     {
       err = grub_usb_bulk_read (dev->dev, dev->in->endp_addr, size, buf);
       grub_dprintf ("usb", "read: %d %d\n", err, GRUB_USB_ERR_STALL); 
-      if (err) goto CheckCSW;
+      if (err)
+        {
+          if (err == GRUB_USB_ERR_STALL)
+	    grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+          goto CheckCSW;
+        }
       /* Debug print of received data. */
       grub_dprintf ("usb", "buf:\n");
       if (size <= 64)
@@ -301,6 +307,12 @@
       err = grub_usb_bulk_write (dev->dev, dev->out->endp_addr, size, buf);
       grub_dprintf ("usb", "write: %d %d\n", err, GRUB_USB_ERR_STALL);
       grub_dprintf ("usb", "buf:\n");
+      if (err)
+        {
+          if (err == GRUB_USB_ERR_STALL)
+	    grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+          goto CheckCSW;
+        }
       /* Debug print of sent data. */
       if (size <= 256)
         for (i=0; i<size; i++)
@@ -311,15 +323,16 @@
 
   /* Read the status - (maybe) according to specification.  */
 CheckCSW:
-  err = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
+  errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
 		    sizeof (status), (char *) &status);
-  if (err)
+  if (errCSW)
     {
       grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
-      err = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
+      errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
 			        sizeof (status), (char *) &status);
-      if (err)
+      if (errCSW)
         { /* Bulk-only reset device. */
+          grub_dprintf ("usb", "Bulk-only reset device - errCSW\n");
           grub_usbms_reset (dev->dev, dev->interface);
           grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
           grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
@@ -332,9 +345,11 @@
   	status.signature, status.tag, status.residue);
   grub_dprintf ("usb", "CSW: status=0x%02x\n", status.status);
   
-  /* If phase error, do bulk-only reset device. */
-  if (status.status == 2)
-    {
+  /* If phase error or not valid signature, do bulk-only reset device. */
+  if ((status.status == 2) ||
+      (status.signature != grub_cpu_to_le32(0x53425355)))
+    { /* Bulk-only reset device. */
+      grub_dprintf ("usb", "Bulk-only reset device - bad status\n");
       grub_usbms_reset (dev->dev, dev->interface);
       grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
       grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
@@ -342,9 +356,13 @@
       goto retry;
     }
 
-  if (status.status)
+  /* If "command failed" status or data transfer failed -> error */
+  if ((status.status || err) && !read_write)
     return grub_error (GRUB_ERR_READ_ERROR,
 		       "error communication with USB Mass Storage device");
+  else if ((status.status || err) && read_write)
+    return grub_error (GRUB_ERR_WRITE_ERROR,
+		       "error communication with USB Mass Storage device");
 
   return GRUB_ERR_NONE;
 }
diff -urB ./grub/include/grub/usb.h ./grub_patched/include/grub/usb.h
--- ./grub/include/grub/usb.h	2010-05-30 15:09:07.000000000 +0200
+++ ./grub_patched/include/grub/usb.h	2010-05-24 19:49:11.000000000 +0200
@@ -35,7 +35,8 @@
     GRUB_USB_ERR_NAK,
     GRUB_USB_ERR_BABBLE,
     GRUB_USB_ERR_TIMEOUT,
-    GRUB_USB_ERR_BITSTUFF
+    GRUB_USB_ERR_BITSTUFF,
+    GRUB_USB_ERR_UNRECOVERABLE
   } grub_usb_err_t;
 
 typedef enum
diff -urB ./grub/include/grub/usbtrans.h ./grub_patched/include/grub/usbtrans.h
--- ./grub/include/grub/usbtrans.h	2010-05-30 15:09:07.000000000 +0200
+++ ./grub_patched/include/grub/usbtrans.h	2010-05-24 19:52:48.000000000 +0200
@@ -58,6 +58,9 @@
   struct grub_usb_device *dev;
 
   struct grub_usb_transaction *transactions;
+  
+  int last_trans;
+  /* Index of last processed transaction in OHCI/UHCI driver. */
 };
 typedef struct grub_usb_transfer *grub_usb_transfer_t;
 
_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to