merge usb into keylayouts

This commit is contained in:
Vladimir 'phcoder' Serbinenko 2010-08-21 23:17:44 +02:00
commit 524c2712ad
10 changed files with 1005 additions and 512 deletions

View file

@ -1,3 +1,17 @@
2010-08-21 Vladimir Serbinenko <phcoder@gmail.com>
* include/grub/usb.h (grub_usb_device): Add 'data' field back. It's
needed by libusb wrapper.
2010-08-21 Samuel Thibault <samuel.thibault@ens-lyon.org>
* docs/grub.texi (GNU/Hurd): Document booting GNU/Hurd.
2010-08-21 Vladimir Serbinenko <phcoder@gmail.com>
* loader/multiboot.c (grub_cmd_module): Don't unzip module if
--nounzip is passed.
2010-08-20 Vladimir Serbinenko <phcoder@gmail.com> 2010-08-20 Vladimir Serbinenko <phcoder@gmail.com>
USB hotunplugging and USB serial support. USB hotunplugging and USB serial support.

View file

@ -652,36 +652,32 @@ grub_ohci_transaction (grub_ohci_td_t td,
td->next_td = 0; 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 static grub_usb_err_t
grub_ohci_transfer (grub_usb_controller_t dev, grub_ohci_setup_transfer (grub_usb_controller_t dev,
grub_usb_transfer_t transfer, int timeout, grub_usb_transfer_t transfer)
grub_size_t *actual)
{ {
struct grub_ohci *o = (struct grub_ohci *) dev->data; struct grub_ohci *o = (struct grub_ohci *) dev->data;
grub_ohci_ed_t ed_virt;
int bulk = 0; 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 td_next_virt;
grub_ohci_td_t tderr_virt = NULL;
grub_uint32_t target; grub_uint32_t target;
grub_uint32_t td_head_phys; grub_uint32_t td_head_phys;
grub_uint32_t td_tail_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; int i;
grub_uint64_t maxtime; struct grub_ohci_transfer_controller_data *cdata;
grub_uint64_t bad_OHCI_delay = 0;
int err_halt = 0;
int err_timeout = 0;
int err_unrec = 0;
grub_uint32_t intstatus;
*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 */ /* Pre-set target for ED - we need it to find proper ED */
/* Set the device address. */ /* Set the device address. */
@ -703,21 +699,23 @@ grub_ohci_transfer (grub_usb_controller_t dev,
case GRUB_USB_TRANSACTION_TYPE_CONTROL: case GRUB_USB_TRANSACTION_TYPE_CONTROL:
break; break;
default : default:
grub_free (cdata);
return GRUB_USB_ERR_INTERNAL; return GRUB_USB_ERR_INTERNAL;
} }
/* Find proper ED or add new ED */ /* Find proper ED or add new ED */
ed_virt = grub_ohci_find_ed (o, bulk, target); cdata->ed_virt = grub_ohci_find_ed (o, bulk, target);
if (!ed_virt) if (!cdata->ed_virt)
{ {
grub_dprintf ("ohci","Fatal: No free ED !\n"); grub_dprintf ("ohci","Fatal: No free ED !\n");
grub_free (cdata);
return GRUB_USB_ERR_INTERNAL; return GRUB_USB_ERR_INTERNAL;
} }
/* Take pointer to first TD from ED */ /* Take pointer to first TD from ED */
td_head_phys = grub_le_to_cpu32 (ed_virt->td_head) & ~0xf; td_head_phys = grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf;
td_tail_phys = grub_le_to_cpu32 (ed_virt->td_tail) & ~0xf; td_tail_phys = grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf;
/* Sanity check - td_head should be equal to td_tail */ /* Sanity check - td_head should be equal to td_tail */
if (td_head_phys != td_tail_phys) /* Should never happen ! */ 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", grub_dprintf ("ohci", "HEAD = 0x%02x, TAIL = 0x%02x\n",
td_head_phys, td_tail_phys); td_head_phys, td_tail_phys);
/* XXX: Fix: What to do ? */ /* XXX: Fix: What to do ? */
grub_free (cdata);
return GRUB_USB_ERR_INTERNAL; return GRUB_USB_ERR_INTERNAL;
} }
@ -733,65 +732,64 @@ grub_ohci_transfer (grub_usb_controller_t dev,
* we must allocate the first TD. */ * we must allocate the first TD. */
if (!td_head_phys) if (!td_head_phys)
{ {
td_head_virt = grub_ohci_alloc_td (o); cdata->td_head_virt = grub_ohci_alloc_td (o);
if (!td_head_virt) if (!cdata->td_head_virt)
return GRUB_USB_ERR_INTERNAL; /* We don't need de-allocate ED */ 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. /* We can set td_head only when ED is not active, i.e.
* when it is newly allocated. */ * when it is newly allocated. */
ed_virt->td_head = grub_cpu_to_le32 ( grub_ohci_td_virt2phys (o, cdata->ed_virt->td_head
td_head_virt) ); = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_head_virt));
ed_virt->td_tail = ed_virt->td_head; cdata->ed_virt->td_tail = cdata->ed_virt->td_head;
} }
else 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 */ /* Set TDs */
td_last_phys = td_head_phys; /* initial value to make compiler happy... */ cdata->td_last_phys = td_head_phys; /* initial value to make compiler happy... */
for (i = 0, td_current_virt = td_head_virt; for (i = 0, cdata->td_current_virt = cdata->td_head_virt;
i < transfer->transcnt; i++) i < transfer->transcnt; i++)
{ {
grub_usb_transaction_t tr = &transfer->transactions[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); tr->size, tr->data);
/* Set index of TD in transfer */ /* 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 */ /* No IRQ request in TD if bad_OHCI set */
if (o->bad_OHCI) 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. */ /* 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 */ /* Allocate next TD */
td_next_virt = grub_ohci_alloc_td (o); td_next_virt = grub_ohci_alloc_td (o);
if (!td_next_virt) /* No free TD, cancel transfer and free TDs except head TD */ 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... */ if (i) /* if i==0 we have nothing to free... */
grub_ohci_free_tds (o, grub_ohci_free_tds (o, grub_ohci_td_phys2virt(o,
grub_ohci_td_phys2virt(o, grub_le_to_cpu32 (cdata->td_head_virt->next_td)));
grub_le_to_cpu32 (td_head_virt->next_td) ) );
/* Reset head TD */ /* Reset head TD */
grub_memset ( (void*)td_head_virt, 0, grub_memset ( (void*)cdata->td_head_virt, 0,
sizeof(struct grub_ohci_td) ); sizeof(struct grub_ohci_td) );
grub_dprintf ("ohci", "Fatal: No free TD !"); grub_dprintf ("ohci", "Fatal: No free TD !");
grub_free (cdata);
return GRUB_USB_ERR_INTERNAL; return GRUB_USB_ERR_INTERNAL;
} }
/* Chain TDs */ /* Chain TDs */
td_current_virt->link_td = (grub_uint32_t) td_next_virt; cdata->td_current_virt->link_td = (grub_uint32_t) td_next_virt;
td_current_virt->next_td = grub_cpu_to_le32 ( cdata->td_current_virt->next_td
grub_ohci_td_virt2phys (o, = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, td_next_virt));
td_next_virt) ); td_next_virt->prev_td_phys
td_next_virt->prev_td_phys = grub_ohci_td_virt2phys (o, = grub_ohci_td_virt2phys (o, cdata->td_current_virt);
td_current_virt); cdata->td_current_virt = td_next_virt;
td_current_virt = td_next_virt;
} }
grub_dprintf ("ohci", "Tail TD (not processed) = %p\n", grub_dprintf ("ohci", "Tail TD (not processed) = %p\n",
td_current_virt); cdata->td_current_virt);
/* Setup the Endpoint Descriptor for transfer. */ /* Setup the Endpoint Descriptor for transfer. */
/* First set necessary fields in TARGET but keep (or set) skip bit */ /* 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. * size never change after first allocation of ED.
* But unfortunately max. packet size may change during initial * But unfortunately max. packet size may change during initial
* setup sequence and we must handle it. */ * 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 */ /* Set td_tail */
ed_virt->td_tail cdata->ed_virt->td_tail
= grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, td_current_virt)); = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_current_virt));
/* Now reset skip bit */ /* 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->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 ! */ /* ed_virt->next_ed = grub_cpu_to_le32 (0); Handled by grub_ohci_find_ed, do not change ! */
@ -834,93 +832,21 @@ grub_ohci_transfer (grub_usb_controller_t dev,
} }
} }
/* Safety measure to avoid a hang. */ transfer->controller_data = cdata;
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) return GRUB_USB_ERR_NONE;
{ /* Unrecoverable error - only reset can help...! */ }
err_unrec = 1;
break;
}
/* Detected a HALT. */ static void
if (err_halt || (grub_le_to_cpu32 (ed_virt->td_head) & 1)) pre_finish_transfer (grub_usb_controller_t dev,
{ grub_usb_transfer_t transfer)
err_halt = 1; {
/* ED is halted, but donehead event can happened in the meantime */ struct grub_ohci *o = dev->data;
intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS); struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
if (!o->bad_OHCI && (intstatus & 0x2) != 0) grub_uint32_t target;
/* Don't break loop now, first do donehead action(s) */ grub_uint32_t status;
continue; grub_uint32_t control;
break; grub_uint32_t intstatus;
}
/* 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);
/* There are many ways how the loop above can finish: /* There are many ways how the loop above can finish:
* - normally without any error via INTSTATUS WDH bit * - normally without any error via INTSTATUS WDH bit
@ -952,8 +878,8 @@ grub_ohci_transfer (grub_usb_controller_t dev,
/* Remember target for debug and set skip flag in ED */ /* Remember target for debug and set skip flag in ED */
/* It should be normaly not necessary but we need it at least /* It should be normaly not necessary but we need it at least
* in case of timeout */ * in case of timeout */
target = grub_le_to_cpu32 ( ed_virt->target ); target = grub_le_to_cpu32 ( cdata->ed_virt->target );
ed_virt->target = grub_cpu_to_le32 (target | (1 << 14)); cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
/* Read registers for debug - they should be read now because /* Read registers for debug - they should be read now because
* debug prints case unwanted delays, so something can happen * debug prints case unwanted delays, so something can happen
* in the meantime... */ * in the meantime... */
@ -964,224 +890,23 @@ grub_ohci_transfer (grub_usb_controller_t dev,
grub_dprintf ("ohci", "loop finished: control=0x%02x status=0x%02x\n", grub_dprintf ("ohci", "loop finished: control=0x%02x status=0x%02x\n",
control, status); control, status);
grub_dprintf ("ohci", "intstatus=0x%02x \n\t\t tderr_phys=0x%02x, td_last_phys=0x%02x\n", 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); intstatus, cdata->tderr_phys, cdata->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);
grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n", grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n",
target, target,
grub_le_to_cpu32 (ed_virt->td_head), grub_le_to_cpu32 (cdata->ed_virt->td_head),
grub_le_to_cpu32 (ed_virt->td_tail) ); grub_le_to_cpu32 (cdata->ed_virt->td_tail) );
if (!err_halt && !err_unrec && !err_timeout) /* normal finish */ }
{
/* 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;
}
else if (err_halt) /* error, ED is halted by OHCI, i.e. can be modified */ static void
{ finish_transfer (grub_usb_controller_t dev,
/* First we must get proper tderr_phys value */ grub_usb_transfer_t transfer)
if (o->bad_OHCI) /* In case of bad_OHCI tderr_phys can be wrong */ {
{ struct grub_ohci *o = dev->data;
if ( tderr_phys ) /* check if tderr_phys points to TD with error */ struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
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;
}
}
/* 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;
}
/* 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;
/* 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;
}
}
else if (err_unrec)
{
/* 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!");
/* 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, 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);
/* 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 );
}
else if (err_timeout)
{
/* In case of timeout do not detect error from TD */
err = GRUB_ERR_TIMEOUT;
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 */
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;
}
/* Set empty ED - set HEAD = TAIL = last (not processed) TD */ /* 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); 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: /* At this point always should be:
* ED has skip bit set and halted or empty or after next SOF, * ED has skip bit set and halted or empty or after next SOF,
@ -1195,23 +920,368 @@ grub_ohci_transfer (grub_usb_controller_t dev,
grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS); grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
/* Un-chainig of last TD */ /* Un-chainig of last TD */
if (td_current_virt->prev_td_phys) if (cdata->td_current_virt->prev_td_phys)
{ {
grub_ohci_td_t td_prev_virt grub_ohci_td_t td_prev_virt
= grub_ohci_td_phys2virt (o, td_current_virt->prev_td_phys); = grub_ohci_td_phys2virt (o, cdata->td_current_virt->prev_td_phys);
td_next_virt = (grub_ohci_td_t) td_prev_virt->link_td; if (cdata->td_current_virt == (grub_ohci_td_t) td_prev_virt->link_td)
if (td_current_virt == td_next_virt)
td_prev_virt->link_td = 0; td_prev_virt->link_td = 0;
} }
grub_dprintf ("ohci", "OHCI finished, freeing, err=0x%02x, errcode=0x%02x\n", grub_dprintf ("ohci", "OHCI finished, freeing\n");
err, errcode); grub_ohci_free_tds (o, cdata->td_head_virt);
grub_ohci_free_tds (o, td_head_virt); }
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;
*actual = 0;
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
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)
{
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;
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;
}
finish_transfer (dev, transfer);
return err; return err;
} }
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, 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);
/* 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)
{
/* Remember last successful TD */
cdata->tderr_phys = grub_le_to_cpu32 (o->hcca->donehead) & ~0xf;
/* Reset DoneHead */
o->hcca->donehead = 0;
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
/* Read back of register should ensure it is really written */
grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
/* if TD is last, finish */
if (cdata->tderr_phys == cdata->td_last_phys)
{
if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)
return parse_halt (dev, transfer, actual);
else
return parse_success (dev, transfer, actual);
}
return GRUB_USB_ERR_WAIT;
}
if ((intstatus & 0x10) != 0)
/* Unrecoverable error - only reset can help...! */
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);
}
/* 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 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 static grub_err_t
grub_ohci_portstatus (grub_usb_controller_t dev, grub_ohci_portstatus (grub_usb_controller_t dev,
unsigned int port, unsigned int enable) unsigned int port, unsigned int enable)
@ -1279,8 +1349,16 @@ grub_ohci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
grub_dprintf ("ohci", "detect_dev status=0x%02x\n", status); grub_dprintf ("ohci", "detect_dev status=0x%02x\n", status);
/* Connect Status Change bit - it detects change of connection */ /* Connect Status Change bit - it detects change of connection */
*changed = ((status & GRUB_OHCI_RESET_CONNECT_CHANGE) != 0); if (status & GRUB_OHCI_RESET_CONNECT_CHANGE)
{
*changed = 1;
/* Reset bit Connect Status Change */
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
GRUB_OHCI_RESET_CONNECT_CHANGE);
}
else
*changed = 0;
if (! (status & 1)) if (! (status & 1))
return GRUB_USB_SPEED_NONE; return GRUB_USB_SPEED_NONE;
@ -1398,7 +1476,9 @@ static struct grub_usb_controller_dev usb_controller =
{ {
.name = "ohci", .name = "ohci",
.iterate = grub_ohci_iterate, .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, .hubports = grub_ohci_hubports,
.portstatus = grub_ohci_portstatus, .portstatus = grub_ohci_portstatus,
.detect_dev = grub_ohci_detect_dev .detect_dev = grub_ohci_detect_dev

View file

@ -39,6 +39,18 @@ typedef enum
#define GRUB_UHCI_LINK_TERMINATE 1 #define GRUB_UHCI_LINK_TERMINATE 1
#define GRUB_UHCI_LINK_QUEUE_HEAD 2 #define GRUB_UHCI_LINK_QUEUE_HEAD 2
enum
{
GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED = 0x0002,
GRUB_UHCI_REG_PORTSC_PORT_ENABLED = 0x0004,
GRUB_UHCI_REG_PORTSC_RESUME = 0x0040,
GRUB_UHCI_REG_PORTSC_RESET = 0x0200,
GRUB_UHCI_REG_PORTSC_SUSPEND = 0x1000,
GRUB_UHCI_REG_PORTSC_RW = GRUB_UHCI_REG_PORTSC_PORT_ENABLED
| GRUB_UHCI_REG_PORTSC_RESUME | GRUB_UHCI_REG_PORTSC_RESET
| GRUB_UHCI_REG_PORTSC_SUSPEND
};
/* UHCI Queue Head. */ /* UHCI Queue Head. */
struct grub_uhci_qh struct grub_uhci_qh
@ -438,26 +450,35 @@ grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
return td; return td;
} }
struct grub_uhci_transfer_controller_data
{
grub_uhci_qh_t qh;
grub_uhci_td_t td_first;
};
static grub_usb_err_t static grub_usb_err_t
grub_uhci_transfer (grub_usb_controller_t dev, grub_uhci_setup_transfer (grub_usb_controller_t dev,
grub_usb_transfer_t transfer, grub_usb_transfer_t transfer)
int timeout, grub_size_t *actual)
{ {
struct grub_uhci *u = (struct grub_uhci *) dev->data; struct grub_uhci *u = (struct grub_uhci *) dev->data;
grub_uhci_qh_t qh;
grub_uhci_td_t td; grub_uhci_td_t td;
grub_uhci_td_t td_first = NULL;
grub_uhci_td_t td_prev = NULL; grub_uhci_td_t td_prev = NULL;
grub_usb_err_t err = GRUB_USB_ERR_NONE;
int i; 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. */ /* Allocate a queue head for the transfer queue. */
qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL); cdata->qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL);
if (! qh) if (! cdata->qh)
return GRUB_USB_ERR_INTERNAL; {
grub_free (cdata);
return GRUB_USB_ERR_INTERNAL;
}
grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase); grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase);
@ -465,23 +486,25 @@ grub_uhci_transfer (grub_usb_controller_t dev,
{ {
grub_usb_transaction_t tr = &transfer->transactions[i]; grub_usb_transaction_t tr = &transfer->transactions[i];
td = grub_uhci_transaction (u, transfer->endpoint, tr->pid, td = grub_uhci_transaction (u, transfer->endpoint & 15, tr->pid,
transfer->devaddr, tr->toggle, transfer->devaddr, tr->toggle,
tr->size, tr->data); tr->size, tr->data);
if (! td) if (! td)
{ {
grub_size_t actual = 0;
/* Terminate and free. */ /* Terminate and free. */
td_prev->linkptr2 = 0; td_prev->linkptr2 = 0;
td_prev->linkptr = 1; td_prev->linkptr = 1;
if (td_first) if (cdata->td_first)
grub_free_queue (u, td_first, NULL, actual); grub_free_queue (u, cdata->td_first, NULL, &actual);
grub_free (cdata);
return GRUB_USB_ERR_INTERNAL; return GRUB_USB_ERR_INTERNAL;
} }
if (! td_first) if (! cdata->td_first)
td_first = td; cdata->td_first = td;
else else
{ {
td_prev->linkptr2 = (grub_uint32_t) td; td_prev->linkptr2 = (grub_uint32_t) td;
@ -497,81 +520,112 @@ grub_uhci_transfer (grub_usb_controller_t dev,
/* Link it into the queue and terminate. Now the transaction can /* Link it into the queue and terminate. Now the transaction can
take place. */ take place. */
qh->elinkptr = (grub_uint32_t) td_first; cdata->qh->elinkptr = (grub_uint32_t) cdata->td_first;
grub_dprintf ("uhci", "initiate transaction\n"); grub_dprintf ("uhci", "initiate transaction\n");
/* Wait until either the transaction completed or an error transfer->controller_data = cdata;
occurred. */
endtime = grub_get_time_ms () + timeout; return GRUB_USB_ERR_NONE;
for (;;) }
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;
*actual = 0;
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 the transaction completed. */
if (cdata->qh->elinkptr & 1)
{ {
grub_uhci_td_t errtd; grub_dprintf ("uhci", "transaction complete\n");
errtd = (grub_uhci_td_t) (qh->elinkptr & ~0x0f); /* 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;
}
grub_dprintf ("uhci", ">t status=0x%02x data=0x%02x td=%p\n", grub_dprintf ("uhci", "t status=0x%02x\n", errtd->ctrl_status);
errtd->ctrl_status, errtd->buffer & (~15), errtd);
/* Check if the transaction completed. */ if (!(errtd->ctrl_status & (1 << 23)))
if (qh->elinkptr & 1) {
break; grub_usb_err_t err = GRUB_USB_ERR_NONE;
grub_dprintf ("uhci", "t status=0x%02x\n", errtd->ctrl_status);
/* Check if the endpoint is stalled. */ /* Check if the endpoint is stalled. */
if (errtd->ctrl_status & (1 << 22)) if (errtd->ctrl_status & (1 << 22))
err = GRUB_USB_ERR_STALL; err = GRUB_USB_ERR_STALL;
/* Check if an error related to the data buffer occurred. */ /* Check if an error related to the data buffer occurred. */
if (errtd->ctrl_status & (1 << 21)) if (errtd->ctrl_status & (1 << 21))
err = GRUB_USB_ERR_DATA; err = GRUB_USB_ERR_DATA;
/* Check if a babble error occurred. */ /* Check if a babble error occurred. */
if (errtd->ctrl_status & (1 << 20)) if (errtd->ctrl_status & (1 << 20))
err = GRUB_USB_ERR_BABBLE; err = GRUB_USB_ERR_BABBLE;
/* Check if a NAK occurred. */ /* Check if a NAK occurred. */
if (errtd->ctrl_status & (1 << 19)) if (errtd->ctrl_status & (1 << 19))
err = GRUB_USB_ERR_NAK; err = GRUB_USB_ERR_NAK;
/* Check if a timeout occurred. */ /* Check if a timeout occurred. */
if (errtd->ctrl_status & (1 << 18)) if (errtd->ctrl_status & (1 << 18))
err = GRUB_USB_ERR_TIMEOUT; err = GRUB_USB_ERR_TIMEOUT;
/* Check if a bitstuff error occurred. */ /* Check if a bitstuff error occurred. */
if (errtd->ctrl_status & (1 << 17)) if (errtd->ctrl_status & (1 << 17))
err = GRUB_USB_ERR_BITSTUFF; err = GRUB_USB_ERR_BITSTUFF;
if (err) if (err)
goto fail;
/* 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 failed\n");
grub_dprintf ("uhci", "transaction timed out\n");
goto fail; /* 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 err;
} }
grub_cpu_idle ();
} }
grub_dprintf ("uhci", "transaction complete\n"); /* Fall through, no errors occurred, so the QH might be
updated. */
grub_dprintf ("uhci", "transaction fallthrough\n");
fail: return GRUB_USB_ERR_WAIT;
}
if (err != GRUB_USB_ERR_NONE) static grub_usb_err_t
grub_dprintf ("uhci", "transaction failed\n"); 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 /* Place the QH back in the free list and deallocate the associated
TDs. */ TDs. */
qh->elinkptr = 1; cdata->qh->elinkptr = 1;
grub_free_queue (u, td_first, transfer, actual); grub_free_queue (u, cdata->td_first, transfer, &actual);
grub_free (cdata);
return err; return GRUB_USB_ERR_NONE;
} }
static int static int
@ -651,7 +705,7 @@ grub_uhci_portstatus (grub_usb_controller_t dev,
return grub_error (GRUB_ERR_IO, "UHCI Timed out"); return grub_error (GRUB_ERR_IO, "UHCI Timed out");
/* Reset bit Connect Status Change */ /* Reset bit Connect Status Change */
grub_uhci_writereg16 (u, reg, status | (1 << 1)); grub_uhci_writereg16 (u, reg, status | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
/* Read final port status */ /* Read final port status */
status = grub_uhci_readreg16 (u, reg); status = grub_uhci_readreg16 (u, reg);
@ -683,7 +737,15 @@ grub_uhci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
grub_dprintf ("uhci", "detect=0x%02x port=%d\n", status, port); grub_dprintf ("uhci", "detect=0x%02x port=%d\n", status, port);
/* Connect Status Change bit - it detects change of connection */ /* Connect Status Change bit - it detects change of connection */
*changed = ((status & (1 << 1)) != 0); if (status & (1 << 1))
{
*changed = 1;
/* Reset bit Connect Status Change */
grub_uhci_writereg16 (u, reg, (status & GRUB_UHCI_REG_PORTSC_RW)
| GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
}
else
*changed = 0;
if (! (status & 1)) if (! (status & 1))
return GRUB_USB_SPEED_NONE; return GRUB_USB_SPEED_NONE;
@ -705,7 +767,9 @@ static struct grub_usb_controller_dev usb_controller =
{ {
.name = "uhci", .name = "uhci",
.iterate = grub_uhci_iterate, .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, .hubports = grub_uhci_hubports,
.portstatus = grub_uhci_portstatus, .portstatus = grub_uhci_portstatus,
.detect_dev = grub_uhci_detect_dev .detect_dev = grub_uhci_detect_dev

View file

@ -177,16 +177,16 @@ grub_usb_add_hub (grub_usb_device_t dev)
grub_dprintf ("usb", "Hub port %d status: 0x%02x\n", i, status); grub_dprintf ("usb", "Hub port %d status: 0x%02x\n", i, status);
/* If connected, reset and enable the port. */ /* If connected, reset and enable the port. */
if (status & GRUB_USB_HUB_STATUS_CONNECTED) if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)
{ {
grub_usb_speed_t speed; grub_usb_speed_t speed;
/* Determine the device speed. */ /* Determine the device speed. */
if (status & GRUB_USB_HUB_STATUS_LOWSPEED) if (status & GRUB_USB_HUB_STATUS_PORT_LOWSPEED)
speed = GRUB_USB_SPEED_LOW; speed = GRUB_USB_SPEED_LOW;
else else
{ {
if (status & GRUB_USB_HUB_STATUS_HIGHSPEED) if (status & GRUB_USB_HUB_STATUS_PORT_HIGHSPEED)
speed = GRUB_USB_SPEED_HIGH; speed = GRUB_USB_SPEED_HIGH;
else else
speed = GRUB_USB_SPEED_FULL; speed = GRUB_USB_SPEED_FULL;
@ -231,7 +231,7 @@ grub_usb_add_hub (grub_usb_device_t dev)
| GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER), | GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_CLEAR_FEATURE, GRUB_USB_REQ_CLEAR_FEATURE,
GRUB_USB_HUB_FEATURE_C_CONNECTED, GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED,
i, 0, 0); i, 0, 0);
/* Just ignore the device if the Hub reports some error */ /* Just ignore the device if the Hub reports some error */
if (err) if (err)
@ -252,6 +252,25 @@ grub_usb_add_hub (grub_usb_device_t dev)
} }
} }
for (i = 0; i < dev->config[0].interf[0].descif->endpointcnt;
i++)
{
struct grub_usb_desc_endp *endp = NULL;
endp = &dev->config[0].interf[0].descendp[i];
if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp)
== GRUB_USB_EP_INTERRUPT)
{
dev->hub_endpoint = endp;
dev->hub_transfer
= grub_usb_bulk_read_background (dev, endp->endp_addr,
grub_min (endp->maxpacket,
sizeof (dev->statuschange)),
(char *) &dev->statuschange);
break;
}
}
return GRUB_ERR_NONE; return GRUB_ERR_NONE;
} }
@ -341,6 +360,9 @@ detach_device (grub_usb_device_t dev)
return; return;
if (dev->descdev.class == GRUB_USB_CLASS_HUB) if (dev->descdev.class == GRUB_USB_CLASS_HUB)
{ {
if (dev->hub_transfer)
grub_usb_cancel_transfer (dev->hub_transfer);
for (i = 0; i < dev->nports; i++) for (i = 0; i < dev->nports; i++)
detach_device (dev->children[i]); detach_device (dev->children[i]);
grub_free (dev->children); grub_free (dev->children);
@ -364,41 +386,105 @@ poll_nonroot_hub (grub_usb_device_t dev)
grub_uint64_t timeout; grub_uint64_t timeout;
grub_usb_device_t next_dev; grub_usb_device_t next_dev;
grub_usb_device_t *attached_devices = dev->children; grub_usb_device_t *attached_devices = dev->children;
grub_uint8_t changed;
grub_size_t actual;
if (!dev->hub_transfer)
return;
err = grub_usb_check_transfer (dev->hub_transfer, &actual);
if (err == GRUB_USB_ERR_WAIT)
return;
changed = dev->statuschange;
dev->hub_transfer
= grub_usb_bulk_read_background (dev, dev->hub_endpoint->endp_addr,
grub_min (dev->hub_endpoint->maxpacket,
sizeof (dev->statuschange)),
(char *) &dev->statuschange);
if (err || actual == 0 || changed == 0)
return;
grub_dprintf ("usb", "statuschanged = %02x, err = %d, actual = %d\n",
changed, err, actual);
/* Iterate over the Hub ports. */ /* Iterate over the Hub ports. */
for (i = 1; i <= dev->nports; i++) for (i = 1; i <= dev->nports; i++)
{ {
grub_uint32_t status; grub_uint32_t status;
if (!(changed & (1 << i)))
continue;
/* Get the port status. */ /* Get the port status. */
err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN
| GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER), | GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_GET_STATUS, GRUB_USB_REQ_GET_STATUS,
0, i, sizeof (status), (char *) &status); 0, i, sizeof (status), (char *) &status);
/* Just ignore the device if the Hub does not report the
status. */
if (err) if (err)
continue; continue;
if (status & GRUB_USB_HUB_STATUS_C_CONNECTED) /* FIXME: properly handle these conditions. */
if (status & GRUB_USB_HUB_STATUS_C_PORT_RESET)
grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
| GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_CLEAR_FEATURE,
GRUB_USB_HUB_FEATURE_C_PORT_RESET, i, 0, 0);
if (status & GRUB_USB_HUB_STATUS_C_PORT_ENABLED)
grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
| GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_CLEAR_FEATURE,
GRUB_USB_HUB_FEATURE_C_PORT_ENABLED, i, 0, 0);
if (status & GRUB_USB_HUB_STATUS_C_PORT_SUSPEND)
grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
| GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_CLEAR_FEATURE,
GRUB_USB_HUB_FEATURE_C_PORT_SUSPEND, i, 0, 0);
if (status & GRUB_USB_HUB_STATUS_C_PORT_OVERCURRENT)
grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
| GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_CLEAR_FEATURE,
GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT, i, 0, 0);
if (!(status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED))
continue;
grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
| GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_CLEAR_FEATURE,
GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED, i, 0, 0);
if (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED)
{ {
detach_device (attached_devices[i-1]); detach_device (attached_devices[i-1]);
attached_devices[i - 1] = NULL; attached_devices[i - 1] = NULL;
} }
/* Connected and status of connection changed ? */ /* Connected and status of connection changed ? */
if ((status & GRUB_USB_HUB_STATUS_CONNECTED) if ((status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)
&& (status & GRUB_USB_HUB_STATUS_C_CONNECTED)) && (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED))
{ {
grub_usb_speed_t speed; grub_usb_speed_t speed;
/* Determine the device speed. */ /* Determine the device speed. */
if (status & GRUB_USB_HUB_STATUS_LOWSPEED) if (status & GRUB_USB_HUB_STATUS_PORT_LOWSPEED)
speed = GRUB_USB_SPEED_LOW; speed = GRUB_USB_SPEED_LOW;
else else
{ {
if (status & GRUB_USB_HUB_STATUS_HIGHSPEED) if (status & GRUB_USB_HUB_STATUS_PORT_HIGHSPEED)
speed = GRUB_USB_SPEED_HIGH; speed = GRUB_USB_SPEED_HIGH;
else else
speed = GRUB_USB_SPEED_FULL; speed = GRUB_USB_SPEED_FULL;
@ -442,7 +528,7 @@ poll_nonroot_hub (grub_usb_device_t dev)
| GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_CLASS
| GRUB_USB_REQTYPE_TARGET_OTHER), | GRUB_USB_REQTYPE_TARGET_OTHER),
GRUB_USB_REQ_CLEAR_FEATURE, GRUB_USB_REQ_CLEAR_FEATURE,
GRUB_USB_HUB_FEATURE_C_CONNECTED, GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED,
i, 0, 0); i, 0, 0);
/* Just ignore the device if the Hub reports some error */ /* Just ignore the device if the Hub reports some error */
if (err) if (err)

View file

@ -23,6 +23,39 @@
#include <grub/misc.h> #include <grub/misc.h>
#include <grub/usb.h> #include <grub/usb.h>
#include <grub/usbtrans.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_err_t
grub_usb_control_msg (grub_usb_device_t dev, 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; transfer->transactions[datablocks + 1].toggle = 1;
err = dev->controller.dev->transfer (&dev->controller, transfer, err = grub_usb_execute_and_wait_transfer (dev, transfer, 1000, &actual);
1000, &actual);
grub_dprintf ("usb", "control: err=%d\n", err); grub_dprintf ("usb", "control: err=%d\n", err);
grub_free (transfer->transactions); grub_free (transfer->transactions);
@ -162,29 +195,27 @@ grub_usb_control_msg (grub_usb_device_t dev,
return err; return err;
} }
static grub_usb_err_t static grub_usb_transfer_t
grub_usb_bulk_readwrite (grub_usb_device_t dev, grub_usb_bulk_setup_readwrite (grub_usb_device_t dev,
int endpoint, grub_size_t size0, char *data_in, int endpoint, grub_size_t size0, char *data_in,
grub_transfer_type_t type, int timeout, grub_transfer_type_t type)
grub_size_t *actual)
{ {
int i; int i;
grub_usb_transfer_t transfer; grub_usb_transfer_t transfer;
int datablocks; int datablocks;
unsigned int max; unsigned int max;
grub_usb_err_t err;
int toggle = dev->toggle[endpoint];
volatile char *data; volatile char *data;
grub_uint32_t data_addr; grub_uint32_t data_addr;
struct grub_pci_dma_chunk *data_chunk; struct grub_pci_dma_chunk *data_chunk;
grub_size_t size = size0; grub_size_t size = size0;
int toggle = dev->toggle[endpoint];
grub_dprintf ("usb", "bulk: size=0x%02x type=%d\n", size, type); grub_dprintf ("usb", "bulk: size=0x%02x type=%d\n", size, type);
/* FIXME: avoid allocation any kind of buffer in a first place. */ /* FIXME: avoid allocation any kind of buffer in a first place. */
data_chunk = grub_memalign_dma32 (128, size); data_chunk = grub_memalign_dma32 (128, size);
if (!data_chunk) if (!data_chunk)
return GRUB_USB_ERR_INTERNAL; return NULL;
data = grub_dma_get_virt (data_chunk); data = grub_dma_get_virt (data_chunk);
data_addr = grub_dma_get_phys (data_chunk); data_addr = grub_dma_get_phys (data_chunk);
if (type == GRUB_USB_TRANSFER_TYPE_OUT) if (type == GRUB_USB_TRANSFER_TYPE_OUT)
@ -209,18 +240,21 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
if (! transfer) if (! transfer)
{ {
grub_dma_free (data_chunk); grub_dma_free (data_chunk);
return grub_errno; return NULL;
} }
datablocks = ((size + max - 1) / max); datablocks = ((size + max - 1) / max);
transfer->transcnt = datablocks; transfer->transcnt = datablocks;
transfer->size = size - 1; transfer->size = size - 1;
transfer->endpoint = endpoint & 15; transfer->endpoint = endpoint;
transfer->devaddr = dev->addr; transfer->devaddr = dev->addr;
transfer->type = GRUB_USB_TRANSACTION_TYPE_BULK; transfer->type = GRUB_USB_TRANSACTION_TYPE_BULK;
transfer->dir = type;
transfer->max = max; transfer->max = max;
transfer->dev = dev; transfer->dev = dev;
transfer->last_trans = -1; /* Reset index of last processed transaction (TD) */ transfer->last_trans = -1; /* Reset index of last processed transaction (TD) */
transfer->data_chunk = data_chunk;
transfer->data = data_in;
/* Allocate an array of transfer data structures. */ /* Allocate an array of transfer data structures. */
transfer->transactions = grub_malloc (transfer->transcnt transfer->transactions = grub_malloc (transfer->transcnt
@ -229,7 +263,7 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
{ {
grub_free (transfer); grub_free (transfer);
grub_dma_free (data_chunk); grub_dma_free (data_chunk);
return grub_errno; return NULL;
} }
/* Set up all transfers. */ /* Set up all transfers. */
@ -247,25 +281,51 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
tr->preceding = i * max; tr->preceding = i * max;
size -= tr->size; size -= tr->size;
} }
return transfer;
}
static void
grub_usb_bulk_finish_readwrite (grub_usb_transfer_t transfer)
{
grub_usb_device_t dev = transfer->dev;
int toggle = dev->toggle[transfer->endpoint];
err = dev->controller.dev->transfer (&dev->controller, transfer, timeout,
actual);
/* We must remember proper toggle value even if some transactions /* We must remember proper toggle value even if some transactions
* were not processed - correct value should be inversion of last * were not processed - correct value should be inversion of last
* processed transaction (TD). */ * processed transaction (TD). */
if (transfer->last_trans >= 0) if (transfer->last_trans >= 0)
toggle = transfer->transactions[transfer->last_trans].toggle ? 0 : 1; toggle = transfer->transactions[transfer->last_trans].toggle ? 0 : 1;
else else
toggle = dev->toggle[endpoint]; /* Nothing done, take original */ toggle = dev->toggle[transfer->endpoint]; /* Nothing done, take original */
grub_dprintf ("usb", "bulk: err=%d, toggle=%d\n", err, toggle); grub_dprintf ("usb", "bulk: toggle=%d\n", toggle);
dev->toggle[endpoint] = toggle; dev->toggle[transfer->endpoint] = toggle;
if (transfer->dir == GRUB_USB_TRANSFER_TYPE_IN)
grub_memcpy (transfer->data, (void *)
grub_dma_get_virt (transfer->data_chunk),
transfer->size + 1);
grub_free (transfer->transactions); grub_free (transfer->transactions);
grub_free (transfer); grub_free (transfer);
grub_dma_free (data_chunk); grub_dma_free (transfer->data_chunk);
}
if (type == GRUB_USB_TRANSFER_TYPE_IN) static grub_usb_err_t
grub_memcpy (data_in, (char *) data, size0); grub_usb_bulk_readwrite (grub_usb_device_t dev,
int endpoint, grub_size_t size0, char *data_in,
grub_transfer_type_t type, int timeout,
grub_size_t *actual)
{
grub_usb_err_t err;
grub_usb_transfer_t transfer;
transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size0,
data_in, type);
if (!transfer)
return GRUB_USB_ERR_INTERNAL;
err = grub_usb_execute_and_wait_transfer (dev, transfer, timeout, actual);
grub_usb_bulk_finish_readwrite (transfer);
return err; return err;
} }
@ -297,6 +357,49 @@ grub_usb_bulk_read (grub_usb_device_t dev,
return err; return err;
} }
grub_usb_err_t
grub_usb_check_transfer (grub_usb_transfer_t transfer, grub_size_t *actual)
{
grub_usb_err_t err;
grub_usb_device_t dev = transfer->dev;
err = dev->controller.dev->check_transfer (&dev->controller, transfer,
actual);
if (err == GRUB_USB_ERR_WAIT)
return err;
grub_usb_bulk_finish_readwrite (transfer);
return err;
}
grub_usb_transfer_t
grub_usb_bulk_read_background (grub_usb_device_t dev,
int endpoint, grub_size_t size, void *data)
{
grub_usb_err_t err;
grub_usb_transfer_t transfer;
transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size,
data, GRUB_USB_TRANSFER_TYPE_IN);
if (!transfer)
return NULL;
err = dev->controller.dev->setup_transfer (&dev->controller, transfer);
if (err)
return NULL;
return transfer;
}
void
grub_usb_cancel_transfer (grub_usb_transfer_t transfer)
{
grub_usb_device_t dev = transfer->dev;
dev->controller.dev->cancel_transfer (&dev->controller, transfer);
grub_errno = GRUB_ERR_NONE;
}
grub_usb_err_t grub_usb_err_t
grub_usb_bulk_read_extended (grub_usb_device_t dev, grub_usb_bulk_read_extended (grub_usb_device_t dev,
int endpoint, grub_size_t size, char *data, int endpoint, grub_size_t size, char *data,

View file

@ -828,11 +828,30 @@ Since GNU/Hurd is Multiboot-compliant, it is easy to boot it; there is
nothing special about it. But do not forget that you have to specify a nothing special about it. But do not forget that you have to specify a
root partition to the kernel. root partition to the kernel.
FIXME: this section is incomplete.
@enumerate @enumerate
@item @item
Run the command @command{boot} (@pxref{boot}). Set GRUB's root device to the same drive as GNU/Hurd's. The command
@code{search --file --set /boot/gnumach.gz} or similar may help you
(@pxref{search}).
@item
Load the kernel and the modules, like this:
@example
@group
grub> @kbd{multiboot /boot/gnumach.gz root=device:hd0s1}
grub> @kbd{module /hurd/ext2fs.static ext2fs --readonly \
--multiboot-command-line='$@{kernel-command-line@}' \
--host-priv-port='$@{host-port@}' \
--device-master-port='$@{device-port@}' \
--exec-server-task='$@{exec-task@}' -T typed '$@{root@}' \
'$(task-create)' '$(task-resume)'}
grub> @kbd{module /lib/ld.so.1 exec /hurd/exec '$(exec-task=task-create)'}
@end group
@end example
@item
Finally, run the command @command{boot} (@pxref{boot}).
@end enumerate @end enumerate

View file

@ -30,6 +30,7 @@ typedef struct grub_usb_controller_dev *grub_usb_controller_dev_t;
typedef enum typedef enum
{ {
GRUB_USB_ERR_NONE, GRUB_USB_ERR_NONE,
GRUB_USB_ERR_WAIT,
GRUB_USB_ERR_INTERNAL, GRUB_USB_ERR_INTERNAL,
GRUB_USB_ERR_STALL, GRUB_USB_ERR_STALL,
GRUB_USB_ERR_DATA, GRUB_USB_ERR_DATA,
@ -48,14 +49,6 @@ typedef enum
GRUB_USB_SPEED_HIGH GRUB_USB_SPEED_HIGH
} grub_usb_speed_t; } grub_usb_speed_t;
enum
{
GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT = 0x21,
GRUB_USB_REQTYPE_VENDOR_OUT = 0x40,
GRUB_USB_REQTYPE_CLASS_INTERFACE_IN = 0xa1,
GRUB_USB_REQTYPE_VENDOR_IN = 0xc0
};
/* Call HOOK with each device, until HOOK returns non-zero. */ /* Call HOOK with each device, until HOOK returns non-zero. */
int grub_usb_iterate (int (*hook) (grub_usb_device_t dev)); int grub_usb_iterate (int (*hook) (grub_usb_device_t dev));
@ -97,6 +90,7 @@ grub_usb_err_t
grub_usb_root_hub (grub_usb_controller_t controller); grub_usb_root_hub (grub_usb_controller_t controller);
/* XXX: All handled by libusb for now. */ /* XXX: All handled by libusb for now. */
struct grub_usb_controller_dev struct grub_usb_controller_dev
{ {
@ -105,9 +99,15 @@ struct grub_usb_controller_dev
int (*iterate) (int (*hook) (grub_usb_controller_t dev)); int (*iterate) (int (*hook) (grub_usb_controller_t dev));
grub_usb_err_t (*transfer) (grub_usb_controller_t dev, grub_usb_err_t (*setup_transfer) (grub_usb_controller_t dev,
grub_usb_transfer_t transfer, grub_usb_transfer_t transfer);
int timeout, grub_size_t *actual);
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); int (*hubports) (grub_usb_controller_t dev);
@ -178,11 +178,22 @@ struct grub_usb_device
/* Data toggle values (used for bulk transfers only). */ /* Data toggle values (used for bulk transfers only). */
int toggle[256]; int toggle[256];
/* Used by libusb wrapper. Schedulded for removal. */
void *data;
/* Hub information. */
/* Array of children for a hub. */ /* Array of children for a hub. */
grub_usb_device_t *children; grub_usb_device_t *children;
/* Number of hub ports. */ /* Number of hub ports. */
unsigned nports; unsigned nports;
grub_usb_transfer_t hub_transfer;
grub_uint32_t statuschange;
struct grub_usb_desc_endp *hub_endpoint;
}; };
@ -263,5 +274,12 @@ grub_usb_err_t
grub_usb_bulk_read_extended (grub_usb_device_t dev, grub_usb_bulk_read_extended (grub_usb_device_t dev,
int endpoint, grub_size_t size, char *data, int endpoint, grub_size_t size, char *data,
int timeout, grub_size_t *actual); int timeout, grub_size_t *actual);
grub_usb_transfer_t
grub_usb_bulk_read_background (grub_usb_device_t dev,
int endpoint, grub_size_t size, void *data);
grub_usb_err_t
grub_usb_check_transfer (grub_usb_transfer_t trans, grub_size_t *actual);
void
grub_usb_cancel_transfer (grub_usb_transfer_t trans);
#endif /* GRUB_USB_H */ #endif /* GRUB_USB_H */

View file

@ -56,51 +56,88 @@ struct grub_usb_transfer
grub_transaction_type_t type; grub_transaction_type_t type;
grub_transfer_type_t dir;
struct grub_usb_device *dev; struct grub_usb_device *dev;
struct grub_usb_transaction *transactions; struct grub_usb_transaction *transactions;
int last_trans; int last_trans;
/* Index of last processed transaction in OHCI/UHCI driver. */ /* Index of last processed transaction in OHCI/UHCI driver. */
void *controller_data;
/* Used when finishing transfer to copy data back. */
struct grub_pci_dma_chunk *data_chunk;
void *data;
}; };
typedef struct grub_usb_transfer *grub_usb_transfer_t; typedef struct grub_usb_transfer *grub_usb_transfer_t;
#define GRUB_USB_REQTYPE_IN (1 << 7)
#define GRUB_USB_REQTYPE_OUT (0 << 7)
#define GRUB_USB_REQTYPE_STANDARD (0 << 5)
#define GRUB_USB_REQTYPE_CLASS (1 << 5)
#define GRUB_USB_REQTYPE_VENDOR (2 << 5)
#define GRUB_USB_REQTYPE_TARGET_DEV (0 << 0)
#define GRUB_USB_REQTYPE_TARGET_INTERF (1 << 0)
#define GRUB_USB_REQTYPE_TARGET_ENDP (2 << 0)
#define GRUB_USB_REQTYPE_TARGET_OTHER (3 << 0)
#define GRUB_USB_REQ_GET_STATUS 0x00 enum
#define GRUB_USB_REQ_CLEAR_FEATURE 0x01 {
#define GRUB_USB_REQ_SET_FEATURE 0x03 GRUB_USB_REQTYPE_TARGET_DEV = (0 << 0),
#define GRUB_USB_REQ_SET_ADDRESS 0x05 GRUB_USB_REQTYPE_TARGET_INTERF = (1 << 0),
#define GRUB_USB_REQ_GET_DESCRIPTOR 0x06 GRUB_USB_REQTYPE_TARGET_ENDP = (2 << 0),
#define GRUB_USB_REQ_SET_DESCRIPTOR 0x07 GRUB_USB_REQTYPE_TARGET_OTHER = (3 << 0),
#define GRUB_USB_REQ_GET_CONFIGURATION 0x08 GRUB_USB_REQTYPE_STANDARD = (0 << 5),
#define GRUB_USB_REQ_SET_CONFIGURATION 0x09 GRUB_USB_REQTYPE_CLASS = (1 << 5),
#define GRUB_USB_REQ_GET_INTERFACE 0x0A GRUB_USB_REQTYPE_VENDOR = (2 << 5),
#define GRUB_USB_REQ_SET_INTERFACE 0x0B GRUB_USB_REQTYPE_OUT = (0 << 7),
#define GRUB_USB_REQ_SYNC_FRAME 0x0C GRUB_USB_REQTYPE_IN = (1 << 7),
GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT = GRUB_USB_REQTYPE_TARGET_INTERF
| GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_OUT,
GRUB_USB_REQTYPE_VENDOR_OUT = GRUB_USB_REQTYPE_VENDOR | GRUB_USB_REQTYPE_OUT,
GRUB_USB_REQTYPE_CLASS_INTERFACE_IN = GRUB_USB_REQTYPE_TARGET_INTERF
| GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_IN,
GRUB_USB_REQTYPE_VENDOR_IN = GRUB_USB_REQTYPE_VENDOR | GRUB_USB_REQTYPE_IN
};
enum
{
GRUB_USB_REQ_GET_STATUS = 0x00,
GRUB_USB_REQ_CLEAR_FEATURE = 0x01,
GRUB_USB_REQ_SET_FEATURE = 0x03,
GRUB_USB_REQ_SET_ADDRESS = 0x05,
GRUB_USB_REQ_GET_DESCRIPTOR = 0x06,
GRUB_USB_REQ_SET_DESCRIPTOR = 0x07,
GRUB_USB_REQ_GET_CONFIGURATION = 0x08,
GRUB_USB_REQ_SET_CONFIGURATION = 0x09,
GRUB_USB_REQ_GET_INTERFACE = 0x0A,
GRUB_USB_REQ_SET_INTERFACE = 0x0B,
GRUB_USB_REQ_SYNC_FRAME = 0x0C
};
#define GRUB_USB_FEATURE_ENDP_HALT 0x00 #define GRUB_USB_FEATURE_ENDP_HALT 0x00
#define GRUB_USB_FEATURE_DEV_REMOTE_WU 0x01 #define GRUB_USB_FEATURE_DEV_REMOTE_WU 0x01
#define GRUB_USB_FEATURE_TEST_MODE 0x02 #define GRUB_USB_FEATURE_TEST_MODE 0x02
#define GRUB_USB_HUB_FEATURE_PORT_RESET 0x04 enum
#define GRUB_USB_HUB_FEATURE_PORT_POWER 0x08 {
#define GRUB_USB_HUB_FEATURE_C_CONNECTED 0x10 GRUB_USB_HUB_FEATURE_PORT_RESET = 0x04,
GRUB_USB_HUB_FEATURE_PORT_POWER = 0x08,
GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED = 0x10,
GRUB_USB_HUB_FEATURE_C_PORT_ENABLED = 0x11,
GRUB_USB_HUB_FEATURE_C_PORT_SUSPEND = 0x12,
GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT = 0x13,
GRUB_USB_HUB_FEATURE_C_PORT_RESET = 0x14
};
#define GRUB_USB_HUB_STATUS_CONNECTED (1 << 0) enum
#define GRUB_USB_HUB_STATUS_LOWSPEED (1 << 9) {
#define GRUB_USB_HUB_STATUS_HIGHSPEED (1 << 10) GRUB_USB_HUB_STATUS_PORT_CONNECTED = (1 << 0),
#define GRUB_USB_HUB_STATUS_C_CONNECTED (1 << 16) GRUB_USB_HUB_STATUS_PORT_ENABLED = (1 << 1),
#define GRUB_USB_HUB_STATUS_C_PORT_RESET (1 << 20) GRUB_USB_HUB_STATUS_PORT_SUSPEND = (1 << 2),
GRUB_USB_HUB_STATUS_PORT_OVERCURRENT = (1 << 3),
GRUB_USB_HUB_STATUS_PORT_LOWSPEED = (1 << 9),
GRUB_USB_HUB_STATUS_PORT_HIGHSPEED = (1 << 10),
GRUB_USB_HUB_STATUS_C_PORT_CONNECTED = (1 << 16),
GRUB_USB_HUB_STATUS_C_PORT_ENABLED = (1 << 17),
GRUB_USB_HUB_STATUS_C_PORT_SUSPEND = (1 << 18),
GRUB_USB_HUB_STATUS_C_PORT_OVERCURRENT = (1 << 19),
GRUB_USB_HUB_STATUS_C_PORT_RESET = (1 << 20)
};
struct grub_usb_packet_setup struct grub_usb_packet_setup
{ {

View file

@ -295,6 +295,17 @@ grub_cmd_module (grub_command_t cmd __attribute__ ((unused)),
grub_ssize_t size; grub_ssize_t size;
char *module = 0; char *module = 0;
grub_err_t err; grub_err_t err;
int nounzip = 0;
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "no module specified");
if (grub_strcmp (argv[0], "--nounzip") == 0)
{
argv++;
argc--;
nounzip = 1;
}
if (argc == 0) if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "no module specified"); return grub_error (GRUB_ERR_BAD_ARGUMENT, "no module specified");
@ -303,7 +314,10 @@ grub_cmd_module (grub_command_t cmd __attribute__ ((unused)),
return grub_error (GRUB_ERR_BAD_ARGUMENT, return grub_error (GRUB_ERR_BAD_ARGUMENT,
"you need to load the multiboot kernel first"); "you need to load the multiboot kernel first");
file = grub_gzfile_open (argv[0], 1); if (nounzip)
file = grub_file_open (argv[0]);
else
file = grub_gzfile_open (argv[0], 1);
if (! file) if (! file)
return grub_errno; return grub_errno;

View file

@ -128,6 +128,9 @@ struct grub_usb_keyboard_data
int key; int key;
int interfno; int interfno;
struct grub_usb_desc_endp *endp; struct grub_usb_desc_endp *endp;
grub_usb_transfer_t transfer;
grub_uint8_t report[8];
int dead;
}; };
static struct grub_term_input grub_usb_keyboards[16]; static struct grub_term_input grub_usb_keyboards[16];
@ -144,6 +147,8 @@ static struct grub_term_input grub_usb_keyboard_term =
.next = 0 .next = 0
}; };
static struct grub_term_input grub_usb_keyboards[16];
static int static int
interpret_status (grub_uint8_t data0) interpret_status (grub_uint8_t data0)
{ {
@ -182,6 +187,9 @@ grub_usb_keyboard_detach (grub_usb_device_t usbdev,
if (data->usbdev != usbdev) if (data->usbdev != usbdev)
continue; continue;
if (data->transfer)
grub_usb_cancel_transfer (data->transfer);
grub_term_unregister_input (&grub_usb_keyboards[i]); grub_term_unregister_input (&grub_usb_keyboards[i]);
grub_free ((char *) grub_usb_keyboards[i].name); grub_free ((char *) grub_usb_keyboards[i].name);
grub_usb_keyboards[i].name = NULL; grub_usb_keyboards[i].name = NULL;
@ -246,11 +254,11 @@ grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno)
/* Place the device in boot mode. */ /* Place the device in boot mode. */
grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT,
USB_HID_SET_PROTOCOL, 0, 0, 0, 0); USB_HID_SET_PROTOCOL, 0, interfno, 0, 0);
/* Reports every time an event occurs and not more often than that. */ /* Reports every time an event occurs and not more often than that. */
grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT,
USB_HID_SET_IDLE, 0<<8, 0, 0, 0); USB_HID_SET_IDLE, 0<<8, interfno, 0, 0);
grub_memcpy (&grub_usb_keyboards[curnum], &grub_usb_keyboard_term, grub_memcpy (&grub_usb_keyboards[curnum], &grub_usb_keyboard_term,
sizeof (grub_usb_keyboards[curnum])); sizeof (grub_usb_keyboards[curnum]));
@ -264,12 +272,18 @@ grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno)
return 0; return 0;
} }
/* Test showed that getting report may make the keyboard go nuts.
Moreover since we're reattaching keyboard it usually sends
an initial message on interrupt pipe and so we retrieve
the same keystatus.
*/
#if 0
{ {
grub_uint8_t report[8]; grub_uint8_t report[8];
grub_usb_err_t err; grub_usb_err_t err;
grub_memset (report, 0, sizeof (report)); grub_memset (report, 0, sizeof (report));
err = grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_IN, err = grub_usb_control_msg (usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_IN,
USB_HID_GET_REPORT, 0x0000, interfno, USB_HID_GET_REPORT, 0x0100, interfno,
sizeof (report), (char *) report); sizeof (report), (char *) report);
if (err) if (err)
{ {
@ -282,11 +296,27 @@ grub_usb_keyboard_attach (grub_usb_device_t usbdev, int configno, int interfno)
data->key = report[2] ? : -1; data->key = report[2] ? : -1;
} }
} }
#else
data->status = 0;
data->key = -1;
#endif
data->transfer = grub_usb_bulk_read_background (usbdev,
data->endp->endp_addr,
sizeof (data->report),
(char *) data->report);
if (!data->transfer)
{
grub_print_error ();
return 0;
}
data->mods = 0; data->mods = 0;
grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]); grub_term_register_input_active ("usb_keyboard", &grub_usb_keyboards[curnum]);
data->dead = 0;
return 1; return 1;
} }
@ -308,19 +338,43 @@ send_leds (struct grub_usb_keyboard_data *termdata)
static int static int
grub_usb_keyboard_checkkey (struct grub_term_input *term) grub_usb_keyboard_checkkey (struct grub_term_input *term)
{ {
grub_uint8_t data[8];
grub_usb_err_t err; grub_usb_err_t err;
struct grub_usb_keyboard_data *termdata = term->data; struct grub_usb_keyboard_data *termdata = term->data;
grub_uint8_t data[sizeof (termdata->report)];
grub_size_t actual; grub_size_t actual;
if (termdata->key != -1) if (termdata->key != -1)
return termdata->key; return termdata->key;
data[2] = 0; if (termdata->dead)
return -1;
/* Poll interrupt pipe. */ /* Poll interrupt pipe. */
err = grub_usb_bulk_read_extended (termdata->usbdev, err = grub_usb_check_transfer (termdata->transfer, &actual);
termdata->endp->endp_addr, sizeof (data),
(char *) data, 10, &actual); if (err == GRUB_USB_ERR_WAIT)
return -1;
grub_memcpy (data, termdata->report, sizeof (data));
termdata->transfer = grub_usb_bulk_read_background (termdata->usbdev,
termdata->endp->endp_addr,
sizeof (termdata->report),
(char *) termdata->report);
if (!termdata->transfer)
{
grub_printf ("%s failed. Stopped\n", term->name);
termdata->dead = 1;
}
grub_dprintf ("usb_keyboard",
"err = %d, actual = %d report: 0x%02x 0x%02x 0x%02x 0x%02x"
" 0x%02x 0x%02x 0x%02x 0x%02x\n",
err, actual,
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7]);
if (err || actual < 1) if (err || actual < 1)
return -1; return -1;
@ -336,15 +390,8 @@ grub_usb_keyboard_checkkey (struct grub_term_input *term)
return -1; return -1;
} }
grub_dprintf ("usb_keyboard",
"report: 0x%02x 0x%02x 0x%02x 0x%02x"
" 0x%02x 0x%02x 0x%02x 0x%02x\n",
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7]);
if (usb_to_at_map[data[2]] == 0) if (usb_to_at_map[data[2]] == 0)
grub_printf ("Unknown key 0x%x detected\n", data[2]); grub_printf ("Unknown key 0x%x detected\n", data[2]);
else else
termdata->key = grub_term_map_key (usb_to_at_map[data[2]], termdata->key = grub_term_map_key (usb_to_at_map[data[2]],
interpret_status (data[0]) interpret_status (data[0])
@ -376,6 +423,8 @@ grub_usb_keyboard_getkeystatus (struct grub_term_input *term)
{ {
struct grub_usb_keyboard_data *termdata = term->data; struct grub_usb_keyboard_data *termdata = term->data;
grub_usb_keyboard_checkkey (term);
return interpret_status (termdata->status) | termdata->mods; return interpret_status (termdata->status) | termdata->mods;
} }
@ -396,9 +445,18 @@ GRUB_MOD_FINI(usb_keyboard)
for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++) for (i = 0; i < ARRAY_SIZE (grub_usb_keyboards); i++)
if (grub_usb_keyboards[i].data) if (grub_usb_keyboards[i].data)
{ {
struct grub_usb_keyboard_data *data = grub_usb_keyboards[i].data;
if (!data)
continue;
if (data->transfer)
grub_usb_cancel_transfer (data->transfer);
grub_term_unregister_input (&grub_usb_keyboards[i]); grub_term_unregister_input (&grub_usb_keyboards[i]);
grub_free ((char *) grub_usb_keyboards[i].name); grub_free ((char *) grub_usb_keyboards[i].name);
grub_usb_keyboards[i].name = NULL; grub_usb_keyboards[i].name = NULL;
grub_free (grub_usb_keyboards[i].data);
grub_usb_keyboards[i].data = 0; grub_usb_keyboards[i].data = 0;
} }
grub_usb_unregister_attach_hook_class (&attach_hook); grub_usb_unregister_attach_hook_class (&attach_hook);