#define GRUB_OHCI_PERIODIC_START 0x257f
#define GRUB_OHCI_FRAME_INTERVAL 0x2edf
+#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)
{
/* 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);
break;
}
+#if 0 /* Always generate interrupt */
/* Generate no interrupts. */
token |= 7 << 21;
+#endif
/* Set the token. */
token |= toggle << 24;
grub_uint32_t status;
grub_uint32_t control;
grub_usb_err_t err;
- int i, j;
+ int i;
grub_uint64_t maxtime;
int err_timeout = 0;
+ int err_unrec = 0;
+ grub_uint32_t intstatus;
+ grub_uint32_t tderr_addr = 0;
/* Allocate an Endpoint Descriptor. */
ed_chunk = grub_memalign_dma32 (256, sizeof (*ed));
+ (i + 1) * sizeof (td_list[0]));
}
+#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;
}
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));
/* Safety measure to avoid a hang. */
maxtime = grub_get_time_ms () + 1000;
/* Wait until the transfer is completed or STALLs. */
do
{
- grub_cpu_idle ();
+ /* Check transfer status */
+ intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
+ if ((intstatus & 0x2) != 0)
+ {
+ grub_dprintf ("ohci", "Current HccaDoneHead=0x%08x\n",
+ o->hcca->donehead);
+ /* Remember last successful TD */
+ tderr_addr = 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_addr == td_list_addr
+ + sizeof (td_list[0]) * (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;
-
- if ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x2) != 0)
- {
- if ((grub_le_to_cpu32 (o->hcca->donehead) & ~0xf)
- == td_list_addr + (transfer->transcnt - 1) * sizeof (td_list[0]))
- 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",
- o->hcca->donehead);
- o->hcca->donehead = 0;
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
- continue;
- }
/* Timeout ? */
if (grub_get_time_ms () > maxtime)
{
if ((ed->td_head & ~0xf) == (ed->td_tail & ~0xf))
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_addr)
+ {
+ /* 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_addr = 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 = (tderr_addr - td_list_addr) / sizeof (*td_list);
+
+ /* Check correct value in last_trans */
+ /* It could happen if timeout happens and no TD was retired */
+ if (transfer->last_trans >= transfer->transcnt || !tderr_addr)
+ {
+ grub_dprintf("ohci", "tder==0 or out of TDs range!\n");
+ grub_dprintf("ohci", "tderr_addr=0x%x, td_list=%p,\n\t\t last_trans=%d, transcnt=%d\n",
+ tderr_addr, td_list, transfer->last_trans, transfer->transcnt);
+ /* We should set something valid... */
+ transfer->last_trans = -1; /* Probably no TD done */
+ tderr_addr = td_list_addr;
+ }
+
+ /* In case of timeout do not detect error from TD */
if (err_timeout)
{
err = GRUB_ERR_TIMEOUT;
grub_le_to_cpu32(ed->td_tail),
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)
{
- grub_uint32_t td_err_addr;
grub_uint8_t errcode;
grub_ohci_td_t tderr = NULL;
- td_err_addr = (grub_ohci_readreg32 (o, GRUB_OHCI_REG_DONEHEAD) & ~0xf);
- if (td_err_addr == 0)
- /* If DONEHEAD==0 it means that correct address is in HCCA.
- * It should be always now! */
- td_err_addr = (grub_le_to_cpu32 (o->hcca->donehead) & ~0xf);
-
tderr = (grub_ohci_td_t) ((char *) td_list
- + (td_err_addr - td_list_addr));
+ + (tderr_addr - td_list_addr));
errcode = grub_le_to_cpu32 (tderr->token) >> 28;
grub_dprintf ("ohci", "OHCI errcode=0x%02x\n", errcode);
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:
/* 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. */
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);
-
+
+ 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\n", err);
grub_dma_free (td_list_chunk);
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;
{
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");
}
{
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)
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++)
/* 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);
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);
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;
}