Merge Aleš' latest patch

This commit is contained in:
Aleš Nesrsta 2010-06-01 02:10:19 +02:00 committed by Vladimir 'phcoder' Serbinenko
parent 778ff32476
commit 7d4873c26a
8 changed files with 206 additions and 57 deletions

View File

@ -116,6 +116,11 @@ typedef enum
#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)
{
@ -281,7 +286,7 @@ grub_ohci_pci_iter (grub_pci_device_t dev,
/* 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);
@ -396,8 +401,10 @@ grub_ohci_transaction (grub_ohci_td_t td,
break;
}
#if 0 /* Always generate interrupt */
/* Generate no interrupts. */
token |= 7 << 21;
#endif
/* Set the token. */
token |= toggle << 24;
@ -444,9 +451,12 @@ grub_ohci_transfer (grub_usb_controller_t dev,
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));
@ -479,11 +489,13 @@ grub_ohci_transfer (grub_usb_controller_t dev,
+ (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;
@ -582,9 +594,11 @@ grub_ohci_transfer (grub_usb_controller_t dev,
}
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;
@ -592,30 +606,34 @@ grub_ohci_transfer (grub_usb_controller_t dev,
/* 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)
{
@ -628,9 +646,45 @@ grub_ohci_transfer (grub_usb_controller_t dev,
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;
@ -640,20 +694,25 @@ grub_ohci_transfer (grub_usb_controller_t dev,
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);
@ -702,17 +761,17 @@ grub_ohci_transfer (grub_usb_controller_t dev,
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:
@ -764,7 +823,8 @@ grub_ohci_transfer (grub_usb_controller_t dev,
/* 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. */
@ -774,7 +834,29 @@ grub_ohci_transfer (grub_usb_controller_t dev,
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);

View File

@ -332,13 +332,20 @@ grub_free_td (struct grub_uhci *u, grub_uhci_td_t td)
}
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 @@ grub_uhci_transfer (grub_usb_controller_t dev,
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 @@ grub_uhci_transfer (grub_usb_controller_t dev,
/* 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_portstatus (grub_usb_controller_t dev,
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");

View File

@ -213,6 +213,7 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
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
@ -240,6 +241,13 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
}
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;

View File

@ -421,7 +421,7 @@ grub_scsi_open (const char *name, grub_disk_t disk)
/* 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 @@ grub_scsi_read (grub_disk_t disk, grub_disk_addr_t sector,
/* 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

View File

@ -253,6 +253,7 @@ grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
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;
@ -290,9 +291,8 @@ grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
{
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");
}
@ -302,7 +302,12 @@ grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
{
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)
@ -316,6 +321,12 @@ grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
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++)
@ -326,15 +337,16 @@ grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
/* 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);
@ -347,9 +359,11 @@ CheckCSW:
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);
@ -357,9 +371,13 @@ CheckCSW:
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;
}

View File

@ -35,7 +35,8 @@ typedef enum
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

View File

@ -58,6 +58,9 @@ struct grub_usb_transfer
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;

View File

@ -18,7 +18,6 @@
*/
#include <grub/term.h>
#include <grub/machine/console.h>
#include <grub/time.h>
#include <grub/cpu/io.h>
#include <grub/misc.h>