]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Prepare infrastructure for background USB transfers
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 21 Aug 2010 14:09:43 +0000 (16:09 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 21 Aug 2010 14:09:43 +0000 (16:09 +0200)
bus/usb/ohci.c
bus/usb/uhci.c
bus/usb/usb.c
bus/usb/usbtrans.c
include/grub/usb.h
include/grub/usbtrans.h

index 7f757485c4a3781cd69ed4a9e18053d8c2e94276..ba723996a96f1f0fdcbd51434d65ab53779533dd 100644 (file)
@@ -652,36 +652,32 @@ grub_ohci_transaction (grub_ohci_td_t td,
   td->next_td = 0;
 }
 
+struct grub_ohci_transfer_controller_data
+{
+  grub_uint32_t tderr_phys;
+  grub_uint32_t td_last_phys;
+  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
-grub_ohci_transfer (grub_usb_controller_t dev,
-                   grub_usb_transfer_t transfer, int timeout,
-                   grub_size_t *actual)
+grub_ohci_setup_transfer (grub_usb_controller_t dev,
+                         grub_usb_transfer_t transfer)
 {
   struct grub_ohci *o = (struct grub_ohci *) dev->data;
-  grub_ohci_ed_t ed_virt;
   int bulk = 0;
-  grub_ohci_td_t td_head_virt;
-  grub_ohci_td_t td_current_virt;
   grub_ohci_td_t td_next_virt;
-  grub_ohci_td_t tderr_virt = NULL;
   grub_uint32_t target;
   grub_uint32_t td_head_phys;
   grub_uint32_t td_tail_phys;
-  grub_uint32_t td_last_phys;
-  grub_uint32_t tderr_phys = 0;
-  grub_uint32_t status;
-  grub_uint32_t control;
-  grub_uint8_t errcode = 0;
-  grub_usb_err_t err = GRUB_USB_ERR_NONE;
   int i;
-  grub_uint64_t maxtime;
-  grub_uint64_t bad_OHCI_delay = 0;
-  int err_halt = 0;
-  int err_timeout = 0;
-  int err_unrec = 0;
-  grub_uint32_t intstatus;
+  struct grub_ohci_transfer_controller_data *cdata;
 
-  *actual = 0;
+  cdata = grub_zalloc (sizeof (*cdata));
+  if (!cdata)
+    return GRUB_USB_ERR_INTERNAL;
 
   /* Pre-set target for ED - we need it to find proper ED */
   /* Set the device address.  */
@@ -703,21 +699,23 @@ grub_ohci_transfer (grub_usb_controller_t dev,
       case GRUB_USB_TRANSACTION_TYPE_CONTROL:
         break;
 
-      default :
+      default:
+       grub_free (cdata);
         return GRUB_USB_ERR_INTERNAL;
     }
 
   /* Find proper ED or add new ED */
-  ed_virt = grub_ohci_find_ed (o, bulk, target);
-  if (!ed_virt)
+  cdata->ed_virt = grub_ohci_find_ed (o, bulk, target);
+  if (!cdata->ed_virt)
     {
       grub_dprintf ("ohci","Fatal: No free ED !\n");
+      grub_free (cdata);
       return GRUB_USB_ERR_INTERNAL;
     }
   
   /* Take pointer to first TD from ED */
-  td_head_phys = grub_le_to_cpu32 (ed_virt->td_head) & ~0xf;
-  td_tail_phys = grub_le_to_cpu32 (ed_virt->td_tail) & ~0xf;
+  td_head_phys = grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf;
+  td_tail_phys = grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf;
 
   /* Sanity check - td_head should be equal to td_tail */
   if (td_head_phys != td_tail_phys) /* Should never happen ! */
@@ -726,6 +724,7 @@ grub_ohci_transfer (grub_usb_controller_t dev,
       grub_dprintf ("ohci", "HEAD = 0x%02x, TAIL = 0x%02x\n",
                     td_head_phys, td_tail_phys);
       /* XXX: Fix: What to do ? */
+      grub_free (cdata);
       return GRUB_USB_ERR_INTERNAL;
     }
   
@@ -733,65 +732,64 @@ grub_ohci_transfer (grub_usb_controller_t dev,
    * we must allocate the first TD. */
   if (!td_head_phys)
     {
-      td_head_virt = grub_ohci_alloc_td (o);
-      if (!td_head_virt)
+      cdata->td_head_virt = grub_ohci_alloc_td (o);
+      if (!cdata->td_head_virt)
         return GRUB_USB_ERR_INTERNAL; /* We don't need de-allocate ED */
       /* We can set td_head only when ED is not active, i.e.
        * when it is newly allocated. */
-      ed_virt->td_head = grub_cpu_to_le32 ( grub_ohci_td_virt2phys (o,
-                                              td_head_virt) );
-      ed_virt->td_tail = ed_virt->td_head;
+      cdata->ed_virt->td_head
+       = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_head_virt));
+      cdata->ed_virt->td_tail = cdata->ed_virt->td_head;
     }
   else
-    td_head_virt = grub_ohci_td_phys2virt ( o, td_head_phys );
+    cdata->td_head_virt = grub_ohci_td_phys2virt ( o, td_head_phys );
     
   /* Set TDs */
-  td_last_phys = td_head_phys; /* initial value to make compiler happy... */
-  for (i = 0, td_current_virt = td_head_virt;
+  cdata->td_last_phys = td_head_phys; /* initial value to make compiler happy... */
+  for (i = 0, cdata->td_current_virt = cdata->td_head_virt;
        i < transfer->transcnt; i++)
     {
       grub_usb_transaction_t tr = &transfer->transactions[i];
 
-      grub_ohci_transaction (td_current_virt, tr->pid, tr->toggle,
+      grub_ohci_transaction (cdata->td_current_virt, tr->pid, tr->toggle,
                             tr->size, tr->data);
 
       /* Set index of TD in transfer */
-      td_current_virt->tr_index = (grub_uint32_t) i;
+      cdata->td_current_virt->tr_index = (grub_uint32_t) i;
 
       /* No IRQ request in TD if bad_OHCI set */
       if (o->bad_OHCI)
-        td_current_virt->token |= grub_cpu_to_le32 ( 7 << 21);
+        cdata->td_current_virt->token |= grub_cpu_to_le32 ( 7 << 21);
       
       /* Remember last used (processed) TD phys. addr. */
-      td_last_phys = grub_ohci_td_virt2phys (o, td_current_virt);
+      cdata->td_last_phys = grub_ohci_td_virt2phys (o, cdata->td_current_virt);
       
       /* Allocate next TD */
       td_next_virt = grub_ohci_alloc_td (o);
       if (!td_next_virt) /* No free TD, cancel transfer and free TDs except head TD */
         {
           if (i) /* if i==0 we have nothing to free... */
-            grub_ohci_free_tds (o,
-              grub_ohci_td_phys2virt(o,
-                grub_le_to_cpu32 (td_head_virt->next_td) ) );
+            grub_ohci_free_tds (o, grub_ohci_td_phys2virt(o,
+                                                         grub_le_to_cpu32 (cdata->td_head_virt->next_td)));
           /* Reset head TD */
-          grub_memset ( (void*)td_head_virt, 0,
+          grub_memset ( (void*)cdata->td_head_virt, 0,
                         sizeof(struct grub_ohci_td) );
           grub_dprintf ("ohci", "Fatal: No free TD !");
+         grub_free (cdata);
           return GRUB_USB_ERR_INTERNAL;
         }
 
       /* Chain TDs */
-      td_current_virt->link_td = (grub_uint32_t) td_next_virt;
-      td_current_virt->next_td = grub_cpu_to_le32 (
-                                   grub_ohci_td_virt2phys (o,
-                                     td_next_virt) );
-      td_next_virt->prev_td_phys = grub_ohci_td_virt2phys (o,
-                                td_current_virt);
-      td_current_virt = td_next_virt;
+      cdata->td_current_virt->link_td = (grub_uint32_t) td_next_virt;
+      cdata->td_current_virt->next_td
+       = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, td_next_virt));
+      td_next_virt->prev_td_phys
+       = grub_ohci_td_virt2phys (o, cdata->td_current_virt);
+      cdata->td_current_virt = td_next_virt;
     }
 
   grub_dprintf ("ohci", "Tail TD (not processed) = %p\n",
-                td_current_virt);
+                cdata->td_current_virt);
   
   /* Setup the Endpoint Descriptor for transfer.  */
   /* First set necessary fields in TARGET but keep (or set) skip bit */
@@ -799,12 +797,12 @@ grub_ohci_transfer (grub_usb_controller_t dev,
    * size never change after first allocation of ED.
    * But unfortunately max. packet size may change during initial
    * setup sequence and we must handle it. */
-  ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
+  cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
   /* Set td_tail */
-  ed_virt->td_tail
-    = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, td_current_virt));
+  cdata->ed_virt->td_tail
+    = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_current_virt));
   /* Now reset skip bit */
-  ed_virt->target = grub_cpu_to_le32 (target);
+  cdata->ed_virt->target = grub_cpu_to_le32 (target);
   /* ed_virt->td_head = grub_cpu_to_le32 (td_head); Must not be changed, it is maintained by OHCI */
   /* ed_virt->next_ed = grub_cpu_to_le32 (0); Handled by grub_ohci_find_ed, do not change ! */
 
@@ -834,93 +832,19 @@ grub_ohci_transfer (grub_usb_controller_t dev,
       }
     }
 
-  /* Safety measure to avoid a hang. */
-  maxtime = grub_get_time_ms () + timeout;
-       
-  /* Wait until the transfer is completed or STALLs.  */
-  do
-    {
-      /* Check transfer status */
-      intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
-      if (!o->bad_OHCI && (intstatus & 0x2) != 0)
-        {
-          /* Remember last successful TD */
-          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 (tderr_phys == td_last_phys)
-            {
-              if (grub_le_to_cpu32 (ed_virt->td_head) & 1)
-                err_halt = 1;
-              break;
-            }
-         continue;
-        }
-
-      if ((intstatus & 0x10) != 0)
-        { /* Unrecoverable error - only reset can help...! */
-          err_unrec = 1;
-          break;
-        }
-
-      /* Detected a HALT.  */
-      if (err_halt || (grub_le_to_cpu32 (ed_virt->td_head) & 1))
-        {
-          err_halt = 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)
-            /* Don't break loop now, first do donehead action(s) */
-            continue;
-          break;
-        }
+  return GRUB_USB_ERR_NONE;
+}
 
-      /* bad OHCI handling */
-      if ( (grub_le_to_cpu32 (ed_virt->td_head) & ~0xf) ==
-           (grub_le_to_cpu32 (ed_virt->td_tail) & ~0xf) ) /* Empty ED */
-        {
-          if (o->bad_OHCI) /* Bad OHCI detected previously */
-            {
-              /* Try get last successful TD. */
-              tderr_phys = grub_le_to_cpu32 (o->hcca->donehead) & ~0xf;
-              if (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 (ed_virt->td_head) & 1)
-                err_halt = 1;
-              break;
-            }
-          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 (!bad_OHCI_delay) /* Set delay time */
-              bad_OHCI_delay = grub_get_time_ms () + 2;
-            else if (grub_get_time_ms () >= bad_OHCI_delay)
-              o->bad_OHCI = 1;
-          continue;
-        }
-        
-      /* Timeout ? */
-      if (grub_get_time_ms () > maxtime)
-       {
-         err_timeout = 1;
-         break;
-       }
-
-      grub_cpu_idle ();
-    }
-  while (1);
+static void
+pre_finish_transfer (grub_usb_controller_t dev,
+                    grub_usb_transfer_t transfer)
+{
+  struct grub_ohci *o = dev->data;
+  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+  grub_uint32_t target;
+  grub_uint32_t status;
+  grub_uint32_t control;
+  grub_uint32_t intstatus;
 
   /* There are many ways how the loop above can finish:
    * - normally without any error via INTSTATUS WDH bit
@@ -952,8 +876,8 @@ grub_ohci_transfer (grub_usb_controller_t dev,
   /* Remember target for debug and set skip flag in ED */
   /* It should be normaly not necessary but we need it at least
    * in case of timeout */
-  target = grub_le_to_cpu32 ( ed_virt->target );
-  ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
+  target = grub_le_to_cpu32 ( cdata->ed_virt->target );
+  cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
   /* Read registers for debug - they should be read now because
    * debug prints case unwanted delays, so something can happen
    * in the meantime... */
@@ -964,252 +888,396 @@ grub_ohci_transfer (grub_usb_controller_t dev,
   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, tderr_phys, td_last_phys);
-  grub_dprintf ("ohci", "err_unrec=%d, err_timeout=%d \n\t\t err_halt=%d, bad_OHCI=%d\n",
-               err_unrec, err_timeout, err_halt, o->bad_OHCI);
+               intstatus, cdata->tderr_phys, cdata->td_last_phys);
   grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n",
                 target,
-                grub_le_to_cpu32 (ed_virt->td_head),
-                grub_le_to_cpu32 (ed_virt->td_tail) );
+                grub_le_to_cpu32 (cdata->ed_virt->td_head),
+                grub_le_to_cpu32 (cdata->ed_virt->td_tail) );
+
+}
+
+static void
+finish_transfer (grub_usb_controller_t dev,
+                grub_usb_transfer_t transfer)
+{
+  struct grub_ohci *o = dev->data;
+  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+
+  /* Set empty ED - set HEAD = TAIL = last (not processed) TD */
+  cdata->ed_virt->td_head = grub_cpu_to_le32 (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf); 
+
+  /* At this point always should be:
+   * ED has skip bit set and halted or empty or after next SOF,
+   *    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);
 
-  if (!err_halt && !err_unrec && !err_timeout) /* normal finish */
+  /* Un-chainig of last TD */
+  if (cdata->td_current_virt->prev_td_phys)
     {
-      /* Simple workaround if donehead is not working */
-      if (o->bad_OHCI &&
-           ( !tderr_phys || (tderr_phys != td_last_phys) ) )
-        {
-          grub_dprintf ("ohci", "normal finish, but tderr_phys corrected\n");
-          tderr_phys = td_last_phys;
-          /* I hope we can do it as transfer (most probably) finished OK */
-        }
-      /* Prepare pointer to last processed TD */
-      tderr_virt = grub_ohci_td_phys2virt (o, tderr_phys);
-      /* Set index of last processed TD */
-      if (tderr_virt)
-        transfer->last_trans = tderr_virt->tr_index;
-      else
-        transfer->last_trans = -1;
-      *actual = transfer->size + 1;
+      grub_ohci_td_t td_prev_virt
+       = grub_ohci_td_phys2virt (o, cdata->td_current_virt->prev_td_phys);
+      
+      if (cdata->td_current_virt == (grub_ohci_td_t) td_prev_virt->link_td)
+        td_prev_virt->link_td = 0;
     }
 
-  else if (err_halt) /* error, ED is halted by OHCI, i.e. can be modified */
-    {
-      /* First we must get proper tderr_phys value */
-      if (o->bad_OHCI) /* In case of bad_OHCI tderr_phys can be wrong */
-        {
-          if ( tderr_phys ) /* check if tderr_phys points to TD with error */
-            errcode = grub_le_to_cpu32 ( grub_ohci_td_phys2virt (o,
-                                           tderr_phys)->token )
-                      >> 28;
-          if ( !tderr_phys || !errcode ) /* tderr_phys not valid or points to wrong TD */
-            { /* Retired TD with error should be previous TD to ED->td_head */
-              tderr_phys = grub_ohci_td_phys2virt (o,
-                             grub_le_to_cpu32 ( ed_virt->td_head) & ~0xf )
-                           ->prev_td_phys;
-            }
-        }
+  grub_dprintf ("ohci", "OHCI finished, freeing\n");
+  grub_ohci_free_tds (o, cdata->td_head_virt);
+}
 
-      /* Even if we have "good" OHCI, in some cases
-       * tderr_phys can be zero, check it */
-      else if ( !tderr_phys )
-        { /* Retired TD with error should be previous TD to ED->td_head */
-          tderr_phys = grub_ohci_td_phys2virt (o,
-                         grub_le_to_cpu32 ( ed_virt->td_head) & ~0xf )
-                       ->prev_td_phys;
-        }
+static grub_usb_err_t
+parse_halt (grub_usb_controller_t dev,
+           grub_usb_transfer_t transfer,
+           grub_size_t *actual)
+{
+  struct grub_ohci *o = dev->data;
+  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+  grub_uint8_t errcode = 0;
+  grub_usb_err_t err = GRUB_USB_ERR_NAK;
+  grub_ohci_td_t tderr_virt = NULL;
 
-      /* Prepare pointer to last processed TD and get error code */
-      tderr_virt = grub_ohci_td_phys2virt (o, tderr_phys);
-      /* Set index of last processed TD */
-      if (tderr_virt)
-        {
-          errcode = grub_le_to_cpu32 ( tderr_virt->token ) >> 28;
-          transfer->last_trans = tderr_virt->tr_index;
-        }
-      else
-        transfer->last_trans = -1;
+  *actual = 0;
 
-      /* Evaluation of error code */
-      grub_dprintf ("ohci", "OHCI tderr_phys=0x%02x, errcode=0x%02x\n",
-                    tderr_phys, errcode);
-      switch (errcode)
-       {
-       case 0:
-         /* XXX: Should not happen!  */
-         grub_error (GRUB_ERR_IO, "OHCI failed without reporting the reason");
-         err = GRUB_USB_ERR_INTERNAL;
-         break;
-
-       case 1:
-         /* XXX: CRC error.  */
-         err = GRUB_USB_ERR_TIMEOUT;
-         break;
-
-       case 2:
-         err = GRUB_USB_ERR_BITSTUFF;
-         break;
-
-       case 3:
-         /* XXX: Data Toggle error.  */
-         err = GRUB_USB_ERR_DATA;
-         break;
-
-       case 4:
-         err = GRUB_USB_ERR_STALL;
-         break;
-
-       case 5:
-         /* XXX: Not responding.  */
-         err = GRUB_USB_ERR_TIMEOUT;
-         break;
-
-       case 6:
-         /* XXX: PID Check bits failed.  */
-         err = GRUB_USB_ERR_BABBLE;
-         break;
-
-       case 7:
-         /* XXX: PID unexpected failed.  */
-         err = GRUB_USB_ERR_BABBLE;
-         break;
-
-       case 8:
-         /* XXX: Data overrun error.  */
-         err = GRUB_USB_ERR_DATA;
-         grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n",
-                       tderr_virt, tderr_virt->tr_index);
-         break;
-
-       case 9:
-         /* XXX: Data underrun error.  */
-         grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n",
-                       tderr_virt, tderr_virt->tr_index);
-         if (transfer->last_trans == -1)
-           break;
-         *actual = transfer->transactions[transfer->last_trans].size
-           - (grub_le_to_cpu32 (tderr_virt->buffer_end)
-              - grub_le_to_cpu32 (tderr_virt->buffer))
-           + transfer->transactions[transfer->last_trans].preceding;
-         break;
-
-       case 10:
-         /* XXX: Reserved.  */
-         err = GRUB_USB_ERR_NAK;
-         break;
-
-       case 11:
-         /* XXX: Reserved.  */
-         err = GRUB_USB_ERR_NAK;
-         break;
-
-       case 12:
-         /* XXX: Buffer overrun.  */
-         err = GRUB_USB_ERR_DATA;
-         break;
-
-       case 13:
-         /* XXX: Buffer underrun.  */
-         err = GRUB_USB_ERR_DATA;
-         break;
-
-       default:
-         err = GRUB_USB_ERR_NAK;
-         break;
+  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;
+    
 
+  /* Prepare pointer to last processed TD and get error code */
+  tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
+  /* Set index of last processed TD */
+  if (tderr_virt)
+    {
+      errcode = grub_le_to_cpu32 (tderr_virt->token) >> 28;
+      transfer->last_trans = tderr_virt->tr_index;
     }
-        
-  else if (err_unrec)      
+  else
+    transfer->last_trans = -1;
+  
+  /* Evaluation of error code */
+  grub_dprintf ("ohci", "OHCI tderr_phys=0x%02x, errcode=0x%02x\n",
+               cdata->tderr_phys, errcode);
+  switch (errcode)
     {
-      /* Don't try to get error code and last processed TD for proper
-       * toggle bit value - anything can be invalid */
-      err = GRUB_USB_ERR_UNRECOVERABLE;
-      grub_dprintf("ohci", "Unrecoverable error!");
+    case 0:
+      /* XXX: Should not happen!  */
+      grub_error (GRUB_ERR_IO, "OHCI failed without reporting the reason");
+      err = GRUB_USB_ERR_INTERNAL;
+      break;
 
-      /* Do OHCI reset in case of unrecoverable error - maybe we will need
-       * do more - re-enumerate bus etc. (?) */
+    case 1:
+      /* XXX: CRC error.  */
+      err = GRUB_USB_ERR_TIMEOUT;
+      break;
 
-      /* Suspend the OHCI by issuing a reset.  */
-      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic.  */
-      /* Read back of register should ensure it is really written */
-      grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
-      grub_millisleep (1);
-      grub_dprintf ("ohci", "Unrecoverable error - OHCI reset\n");
+    case 2:
+      err = GRUB_USB_ERR_BITSTUFF;
+      break;
 
-      /* 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, o->ed_ctrl_addr);
-      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
-      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
-      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
-      /* Read back of register should ensure it is really written */
-      grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+    case 3:
+      /* XXX: Data Toggle error.  */
+      err = GRUB_USB_ERR_DATA;
+      break;
 
-      /* Enable the OHCI.  */
-      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
-                            (2 << 6)
-                            | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
-                            | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
-    } 
+    case 4:
+      err = GRUB_USB_ERR_STALL;
+      break;
 
-  else if (err_timeout)
-    {
-      /* In case of timeout do not detect error from TD */    
-      err = GRUB_ERR_TIMEOUT;
-      grub_dprintf("ohci", "Timeout !\n");
+    case 5:
+      /* XXX: Not responding.  */
+      err = GRUB_USB_ERR_TIMEOUT;
+      break;
 
-      /* We should wait for next SOF to be sure that ED is unaccessed
-       * by OHCI */
-      /* 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);
+    case 6:
+      /* XXX: PID Check bits failed.  */
+      err = GRUB_USB_ERR_BABBLE;
+      break;
 
-      /* 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 */
-          tderr_phys = grub_ohci_td_phys2virt (o,
-                         grub_le_to_cpu32 ( ed_virt->td_head) & ~0xf)
-                       ->prev_td_phys;
-        }
-      tderr_virt = grub_ohci_td_phys2virt (o, tderr_phys);
-      if (tderr_virt)
-        transfer->last_trans = tderr_virt->tr_index;
-      else
-        transfer->last_trans = -1;
+    case 7:
+      /* XXX: PID unexpected failed.  */
+      err = GRUB_USB_ERR_BABBLE;
+      break;
+
+    case 8:
+      /* XXX: Data overrun error.  */
+      err = GRUB_USB_ERR_DATA;
+      grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n",
+                   tderr_virt, tderr_virt->tr_index);
+      break;
+
+    case 9:
+      /* XXX: Data underrun error.  */
+      grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n",
+                   tderr_virt, tderr_virt->tr_index);
+      if (transfer->last_trans == -1)
+       break;
+      *actual = transfer->transactions[transfer->last_trans].size
+       - (grub_le_to_cpu32 (tderr_virt->buffer_end)
+          - grub_le_to_cpu32 (tderr_virt->buffer))
+       + transfer->transactions[transfer->last_trans].preceding;
+      err = GRUB_USB_ERR_NONE;
+      break;
+
+    case 10:
+      /* XXX: Reserved.  */
+      err = GRUB_USB_ERR_NAK;
+      break;
+
+    case 11:
+      /* XXX: Reserved.  */
+      err = GRUB_USB_ERR_NAK;
+      break;
+
+    case 12:
+      /* XXX: Buffer overrun.  */
+      err = GRUB_USB_ERR_DATA;
+      break;
+
+    case 13:
+      /* XXX: Buffer underrun.  */
+      err = GRUB_USB_ERR_DATA;
+      break;
+
+    default:
+      err = GRUB_USB_ERR_NAK;
+      break;
     }
 
-  /* Set empty ED - set HEAD = TAIL = last (not processed) TD */
-  ed_virt->td_head = grub_cpu_to_le32 (grub_le_to_cpu32 (ed_virt->td_tail) & ~0xf); 
+  finish_transfer (dev, transfer);
 
-  /* At this point always should be:
-   * ED has skip bit set and halted or empty or after next SOF,
-   *    i.e. it is safe to free all TDs except last not processed
-   * ED HEAD == TAIL == phys. addr. of td_current_virt */
+  return err;
+}
 
-  /* Reset DoneHead - sanity cleanup */
+static grub_usb_err_t
+parse_success (grub_usb_controller_t dev,
+              grub_usb_transfer_t transfer,
+              grub_size_t *actual)
+{
+  struct grub_ohci *o = dev->data;
+  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+  grub_ohci_td_t tderr_virt = NULL;
+
+  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 */
+    }
+  /* 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;
+  else
+    transfer->last_trans = -1;
+  *actual = transfer->size + 1;
+
+  finish_transfer (dev, transfer);
+
+  return GRUB_USB_ERR_NONE;
+}
+
+static grub_usb_err_t
+parse_unrec (grub_usb_controller_t dev,
+            grub_usb_transfer_t transfer,
+            grub_size_t *actual)
+{
+  struct grub_ohci *o = dev->data;
+
+  *actual = 0;
+
+  pre_finish_transfer (dev, transfer);
+
+  /* Don't try to get error code and last processed TD for proper
+   * toggle bit value - anything can be invalid */
+  grub_dprintf("ohci", "Unrecoverable error!");
+
+  /* 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.  */
+  /* Read back of register should ensure it is really written */
+  grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
+  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, (1 << 1));
+  grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
+  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
+  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
+  grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
+  grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
   /* Read back of register should ensure it is really written */
   grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
 
-  /* Un-chainig of last TD */
-  if (td_current_virt->prev_td_phys)
+  /* Enable the OHCI.  */
+  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
+                       (2 << 6)
+                       | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
+                       | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
+  finish_transfer (dev, transfer);
+
+  return GRUB_USB_ERR_UNRECOVERABLE;
+}
+
+static grub_usb_err_t
+grub_ohci_check_transfer (grub_usb_controller_t dev,
+                         grub_usb_transfer_t transfer,
+                         grub_size_t *actual)
+{
+  struct grub_ohci *o = dev->data;
+  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+  grub_uint32_t intstatus;
+
+  /* Check transfer status */
+  intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+  if (!o->bad_OHCI && (intstatus & 0x2) != 0)
     {
-      grub_ohci_td_t td_prev_virt
-                = grub_ohci_td_phys2virt (o, td_current_virt->prev_td_phys);
+      /* 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;
+    }
 
-      td_next_virt = (grub_ohci_td_t) td_prev_virt->link_td;
-      if (td_current_virt == td_next_virt)
-        td_prev_virt->link_td = 0;
+  if ((intstatus & 0x10) != 0)
+    /* Unrecoverable error - only reset can help...! */
+    return parse_unrec (dev, transfer, actual);
+
+  /* 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);
     }
 
-  grub_dprintf ("ohci", "OHCI finished, freeing, err=0x%02x, errcode=0x%02x\n",
-                err, errcode);
-  grub_ohci_free_tds (o, td_head_virt);
+  /* bad OHCI handling */
+  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;
+    }
 
-  return err;
+  return GRUB_USB_ERR_WAIT;
+}
+
+static grub_usb_err_t
+grub_ohci_cancel_transfer (grub_usb_controller_t dev,
+                          grub_usb_transfer_t transfer)
+{
+  struct grub_ohci *o = dev->data;
+  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
+  grub_ohci_td_t tderr_virt = NULL;
+
+  pre_finish_transfer (dev, transfer);
+
+  grub_dprintf("ohci", "Timeout !\n");
+
+  /* We should wait for next SOF to be sure that ED is unaccessed
+   * by OHCI */
+  /* 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);
+
+  /* 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;
+    
+  tderr_virt = grub_ohci_td_phys2virt (o,cdata-> tderr_phys);
+  if (tderr_virt)
+    transfer->last_trans = tderr_virt->tr_index;
+  else
+    transfer->last_trans = -1;
+
+  finish_transfer (dev, transfer);
+
+  return GRUB_USB_ERR_NONE;
 }
 
 static grub_err_t
@@ -1398,7 +1466,9 @@ static struct grub_usb_controller_dev usb_controller =
 {
   .name = "ohci",
   .iterate = grub_ohci_iterate,
-  .transfer = grub_ohci_transfer,
+  .setup_transfer = grub_ohci_setup_transfer,
+  .check_transfer = grub_ohci_check_transfer,
+  .cancel_transfer = grub_ohci_cancel_transfer,
   .hubports = grub_ohci_hubports,
   .portstatus = grub_ohci_portstatus,
   .detect_dev = grub_ohci_detect_dev
index d0416d7e23d9940078cfd74489f9386ba34de60a..4792f961aae9a5e68d00eac9c7ad5653862973fe 100644 (file)
@@ -438,26 +438,35 @@ grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
   return td;
 }
 
+struct grub_uhci_transfer_controller_data
+{
+  grub_uhci_qh_t qh;
+  grub_uhci_td_t td_first;
+};
+
 static grub_usb_err_t
-grub_uhci_transfer (grub_usb_controller_t dev,
-                   grub_usb_transfer_t transfer,
-                   int timeout, grub_size_t *actual)
+grub_uhci_setup_transfer (grub_usb_controller_t dev,
+                         grub_usb_transfer_t transfer)
 {
   struct grub_uhci *u = (struct grub_uhci *) dev->data;
-  grub_uhci_qh_t qh;
   grub_uhci_td_t td;
-  grub_uhci_td_t td_first = NULL;
   grub_uhci_td_t td_prev = NULL;
-  grub_usb_err_t err = GRUB_USB_ERR_NONE;
   int i;
-  grub_uint64_t endtime;
+  struct grub_uhci_transfer_controller_data *cdata;
 
-  *actual = 0;
+  cdata = grub_malloc (sizeof (*cdata));
+  if (!cdata)
+    return GRUB_USB_ERR_INTERNAL;
+
+  cdata->td_first = NULL;
 
   /* Allocate a queue head for the transfer queue.  */
-  qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL);
-  if (! qh)
-    return GRUB_USB_ERR_INTERNAL;
+  cdata->qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL);
+  if (! cdata->qh)
+    {
+      grub_free (cdata);
+      return GRUB_USB_ERR_INTERNAL;
+    }
 
   grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase);
   
@@ -470,18 +479,20 @@ grub_uhci_transfer (grub_usb_controller_t dev,
                                  tr->size, tr->data);
       if (! td)
        {
+         grub_size_t actual = 0;
          /* Terminate and free.  */
          td_prev->linkptr2 = 0;
          td_prev->linkptr = 1;
 
-         if (td_first)
-           grub_free_queue (u, td_first, NULL, actual);
+         if (cdata->td_first)
+           grub_free_queue (u, cdata->td_first, NULL, &actual);
 
+         grub_free (cdata);
          return GRUB_USB_ERR_INTERNAL;
        }
 
-      if (! td_first)
-       td_first = td;
+      if (! cdata->td_first)
+       cdata->td_first = td;
       else
        {
          td_prev->linkptr2 = (grub_uint32_t) td;
@@ -497,82 +508,112 @@ grub_uhci_transfer (grub_usb_controller_t dev,
 
   /* Link it into the queue and terminate.  Now the transaction can
      take place.  */
-  qh->elinkptr = (grub_uint32_t) td_first;
+  cdata->qh->elinkptr = (grub_uint32_t) cdata->td_first;
 
   grub_dprintf ("uhci", "initiate transaction\n");
 
-  /* Wait until either the transaction completed or an error
-     occurred.  */
-  endtime = grub_get_time_ms () + timeout;
-  for (;;)
-    {
-      grub_uhci_td_t errtd;
-
-      errtd = (grub_uhci_td_t) (qh->elinkptr & ~0x0f);
+  transfer->controller_data = cdata;
 
-      grub_dprintf ("uhci", ">t status=0x%02x data=0x%02x td=%p\n",
-                   errtd->ctrl_status, errtd->buffer & (~15), errtd);
+  return GRUB_USB_ERR_NONE;
+}
 
-      /* Check if the transaction completed.  */
-      if (qh->elinkptr & 1)
-       break;
+static grub_usb_err_t
+grub_uhci_check_transfer (grub_usb_controller_t dev,
+                         grub_usb_transfer_t transfer,
+                         grub_size_t *actual)
+{
+  struct grub_uhci *u = (struct grub_uhci *) dev->data;
+  grub_uhci_td_t errtd;
+  struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data;
 
-      grub_dprintf ("uhci", "t status=0x%02x\n", errtd->ctrl_status);
+  *actual = 0;
 
-      if (!(errtd->ctrl_status & (1 << 23)))
-       {
-         /* Check if the endpoint is stalled.  */
-         if (errtd->ctrl_status & (1 << 22))
-           err = GRUB_USB_ERR_STALL;
+  errtd = (grub_uhci_td_t) (cdata->qh->elinkptr & ~0x0f);
+  
+  grub_dprintf ("uhci", ">t status=0x%02x data=0x%02x td=%p\n",
+               errtd->ctrl_status, errtd->buffer & (~15), errtd);
 
-         /* Check if an error related to the data buffer occurred.  */
-         if (errtd->ctrl_status & (1 << 21))
-           err = GRUB_USB_ERR_DATA;
+  /* Check if the transaction completed.  */
+  if (cdata->qh->elinkptr & 1)
+    {
+      grub_dprintf ("uhci", "transaction complete\n");
+
+      /* Place the QH back in the free list and deallocate the associated
+        TDs.  */
+      cdata->qh->elinkptr = 1;
+      grub_free_queue (u, cdata->td_first, transfer, actual);
+      grub_free (cdata);
+      return GRUB_USB_ERR_NONE;
+    }
 
-         /* Check if a babble error occurred.  */
-         if (errtd->ctrl_status & (1 << 20))
-           err = GRUB_USB_ERR_BABBLE;
+  grub_dprintf ("uhci", "t status=0x%02x\n", errtd->ctrl_status);
 
-         /* Check if a NAK occurred.  */
-         if (errtd->ctrl_status & (1 << 19))
-           err = GRUB_USB_ERR_NAK;
+  if (!(errtd->ctrl_status & (1 << 23)))
+    {
+      grub_usb_err_t err = GRUB_USB_ERR_NONE;
 
-         /* Check if a timeout occurred.  */
-         if (errtd->ctrl_status & (1 << 18))
-           err = GRUB_USB_ERR_TIMEOUT;
+      /* Check if the endpoint is stalled.  */
+      if (errtd->ctrl_status & (1 << 22))
+       err = GRUB_USB_ERR_STALL;
+      
+      /* Check if an error related to the data buffer occurred.  */
+      if (errtd->ctrl_status & (1 << 21))
+       err = GRUB_USB_ERR_DATA;
+      
+      /* Check if a babble error occurred.  */
+      if (errtd->ctrl_status & (1 << 20))
+       err = GRUB_USB_ERR_BABBLE;
+      
+      /* Check if a NAK occurred.  */
+      if (errtd->ctrl_status & (1 << 19))
+       err = GRUB_USB_ERR_NAK;
+      
+      /* Check if a timeout occurred.  */
+      if (errtd->ctrl_status & (1 << 18))
+       err = GRUB_USB_ERR_TIMEOUT;
+      
+      /* Check if a bitstuff error occurred.  */
+      if (errtd->ctrl_status & (1 << 17))
+       err = GRUB_USB_ERR_BITSTUFF;
+      
+      if (err)
+       {
+         grub_dprintf ("uhci", "transaction failed\n");
 
-         /* Check if a bitstuff error occurred.  */
-         if (errtd->ctrl_status & (1 << 17))
-           err = GRUB_USB_ERR_BITSTUFF;
+         /* Place the QH back in the free list and deallocate the associated
+            TDs.  */
+         cdata->qh->elinkptr = 1;
+         grub_free_queue (u, cdata->td_first, transfer, actual);
+         grub_free (cdata);
 
-         if (err)
-           break;
+         return err;
        }
+    }
 
-      /* Fall through, no errors occurred, so the QH might be
-        updated.  */
-      grub_dprintf ("uhci", "transaction fallthrough\n");
+  /* Fall through, no errors occurred, so the QH might be
+     updated.  */
+  grub_dprintf ("uhci", "transaction fallthrough\n");
 
-      if (grub_get_time_ms () > endtime)
-       {
-         err = GRUB_USB_ERR_STALL;
-         grub_dprintf ("uhci", "transaction timed out\n");
-         break;
-       }
-      grub_cpu_idle ();
-    }
+  return GRUB_USB_ERR_WAIT;
+}
 
-  if (err != GRUB_USB_ERR_NONE)
-    grub_dprintf ("uhci", "transaction failed\n");
-  else
-    grub_dprintf ("uhci", "transaction complete\n");
+static grub_usb_err_t
+grub_uhci_cancel_transfer (grub_usb_controller_t dev,
+                          grub_usb_transfer_t transfer)
+{
+  struct grub_uhci *u = (struct grub_uhci *) dev->data;
+  grub_size_t actual;
+  struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data;
+
+  grub_dprintf ("uhci", "transaction cancel\n");
 
   /* Place the QH back in the free list and deallocate the associated
      TDs.  */
-  qh->elinkptr = 1;
-  grub_free_queue (u, td_first, transfer, actual);
+  cdata->qh->elinkptr = 1;
+  grub_free_queue (u, cdata->td_first, transfer, &actual);
+  grub_free (cdata);
 
-  return err;
+  return GRUB_USB_ERR_NONE;
 }
 
 static int
@@ -706,7 +747,9 @@ static struct grub_usb_controller_dev usb_controller =
 {
   .name = "uhci",
   .iterate = grub_uhci_iterate,
-  .transfer = grub_uhci_transfer,
+  .setup_transfer = grub_uhci_setup_transfer,
+  .check_transfer = grub_uhci_check_transfer,
+  .cancel_transfer = grub_uhci_cancel_transfer,
   .hubports = grub_uhci_hubports,
   .portstatus = grub_uhci_portstatus,
   .detect_dev = grub_uhci_detect_dev
index 2bd805ef24c673d6648e17dad280ef69b77e73ea..80d386c8d6d2714706c2d410af187933cf536cb2 100644 (file)
@@ -338,7 +338,7 @@ grub_usb_unregister_attach_hook_class (struct grub_usb_attach_desc *desc)
 
 GRUB_MOD_INIT(usb)
 {
-  grub_term_poll_usb = grub_usb_poll_devices;
+  //  grub_term_poll_usb = grub_usb_poll_devices;
 }
 
 GRUB_MOD_FINI(usb)
index 4a2e112bf2987d81ad31a5369b61f98c6f848467..27377a3a5b1dc761019f955d90286f27fbd828cf 100644 (file)
 #include <grub/misc.h>
 #include <grub/usb.h>
 #include <grub/usbtrans.h>
+#include <grub/time.h>
+
+static grub_usb_err_t
+grub_usb_execute_and_wait_transfer (grub_usb_device_t dev, 
+                                   grub_usb_transfer_t transfer,
+                                   int timeout, grub_size_t *actual)
+{
+  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;
+  while (1)
+    {
+      err = dev->controller.dev->check_transfer (&dev->controller, transfer,
+                                                actual);
+      if (!err)
+       return GRUB_USB_ERR_NONE;
+      if (err != GRUB_USB_ERR_WAIT)
+       return err;
+      if (grub_get_time_ms () > endtime)
+       {
+         err = dev->controller.dev->cancel_transfer (&dev->controller,
+                                                     transfer);
+         if (err)
+           return err;
+         return GRUB_USB_ERR_TIMEOUT;
+       }
+      grub_cpu_idle ();
+    }
+}
 
 grub_usb_err_t
 grub_usb_control_msg (grub_usb_device_t dev,
@@ -147,8 +180,8 @@ grub_usb_control_msg (grub_usb_device_t dev,
 
   transfer->transactions[datablocks + 1].toggle = 1;
 
-  err = dev->controller.dev->transfer (&dev->controller, transfer,
-                                      1000, &actual);
+  err = grub_usb_execute_and_wait_transfer (dev, transfer, 1000, &actual);
+
   grub_dprintf ("usb", "control: err=%d\n", err);
 
   grub_free (transfer->transactions);
@@ -248,8 +281,7 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
       size -= tr->size;
     }
 
-  err = dev->controller.dev->transfer (&dev->controller, transfer, timeout,
-                                      actual);
+  err = grub_usb_execute_and_wait_transfer (dev, transfer, timeout, actual);
   /* We must remember proper toggle value even if some transactions
    * were not processed - correct value should be inversion of last
    * processed transaction (TD). */
index bb3336580ef7cd88e10b348c35e8ae79202205c5..e7d119646719d3ec43131f8ea354cc4c3a100c9e 100644 (file)
@@ -30,6 +30,7 @@ typedef struct grub_usb_controller_dev *grub_usb_controller_dev_t;
 typedef enum
   {
     GRUB_USB_ERR_NONE,
+    GRUB_USB_ERR_WAIT,
     GRUB_USB_ERR_INTERNAL,
     GRUB_USB_ERR_STALL,
     GRUB_USB_ERR_DATA,
@@ -97,6 +98,7 @@ grub_usb_err_t
 grub_usb_root_hub (grub_usb_controller_t controller);
 
 \f
+
 /* XXX: All handled by libusb for now.  */
 struct grub_usb_controller_dev
 {
@@ -105,9 +107,15 @@ struct grub_usb_controller_dev
 
   int (*iterate) (int (*hook) (grub_usb_controller_t dev));
 
-  grub_usb_err_t (*transfer) (grub_usb_controller_t dev,
-                             grub_usb_transfer_t transfer,
-                             int timeout, grub_size_t *actual);
+  grub_usb_err_t (*setup_transfer) (grub_usb_controller_t dev,
+                                   grub_usb_transfer_t transfer);
+
+  grub_usb_err_t (*check_transfer) (grub_usb_controller_t dev,
+                                   grub_usb_transfer_t transfer,
+                                   grub_size_t *actual);
+
+  grub_usb_err_t (*cancel_transfer) (grub_usb_controller_t dev,
+                                    grub_usb_transfer_t transfer);
 
   int (*hubports) (grub_usb_controller_t dev);
 
index a5bb2e8b2b4ea2b44e3bcb62e814828e4f980e68..486e83f4098fba78541b64c5bda4a73a3c45211b 100644 (file)
@@ -62,6 +62,8 @@ struct grub_usb_transfer
   
   int last_trans;
   /* Index of last processed transaction in OHCI/UHCI driver. */
+
+  void *controller_data;
 };
 typedef struct grub_usb_transfer *grub_usb_transfer_t;