diff options
author | Nico Huber <nico.huber@secunet.com> | 2012-06-20 10:08:06 +0200 |
---|---|---|
committer | Nico Huber <nico.huber@secunet.com> | 2012-06-21 11:55:10 +0200 |
commit | 9951adeffd02c0226c5dd9dcbddbab9d86e28ee4 (patch) | |
tree | e68d40a10db03be7ce72ecd1452afa33e95b6644 /payloads/libpayload | |
parent | ac8d5508541874b60aae6363f50e56f7df40b27f (diff) |
libpayload: Implement correct done queue processing for OHCI
This adds correct processing of the done queue of the OHCI host
controller (HC). We will always process the done queue after a control
or bulk transfer. Unfortunately, it's hard to tell when the HC will
write out the done queue, so we have do free the transfer descriptors
later and have to allocate them one by one.
To distinguish different types of TDs (e.g. async vs. interrupt
transfers) on the done queue, they are flagged in the lsb of there
.config field. We can utilize this bit for our own purpose, as it's
reserved and the host controller won't interpret it and preserves its
state.
Change-Id: I3b2271ae6221cdd50fc0f94582afdfe52bf7e797
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: http://review.coreboot.org/1125
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Tested-by: build bot (Jenkins)
Diffstat (limited to 'payloads/libpayload')
-rw-r--r-- | payloads/libpayload/drivers/usb/ohci.c | 192 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/ohci_private.h | 21 |
2 files changed, 137 insertions, 76 deletions
diff --git a/payloads/libpayload/drivers/usb/ohci.c b/payloads/libpayload/drivers/usb/ohci.c index 4b3aa39c6c..be6d5e343a 100644 --- a/payloads/libpayload/drivers/usb/ohci.c +++ b/payloads/libpayload/drivers/usb/ohci.c @@ -44,6 +44,7 @@ static int ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq static void* ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming); static void ohci_destroy_intr_queue (endpoint_t *ep, void *queue); static u8* ohci_poll_intr_queue (void *queue); +static void ohci_process_done_queue(ohci_t *ohci, int spew_debug); static void ohci_reset (hci_t *controller) @@ -197,8 +198,6 @@ dump_td(td_t *cur, int level) static int wait_for_ed(usbdev_t *dev, ed_t *head, int pages) { - td_t *cur; - /* wait for results */ /* TODO: how long to wait? * give 50ms per page plus another 100ms for now @@ -225,35 +224,8 @@ wait_for_ed(usbdev_t *dev, ed_t *head, int pages) if (timeout < 0) printf("Error: ohci: endpoint " "descriptor processing timed out.\n"); -#if 0 - /* XXX: The following debugging code may follow invalid lists and - * cause a reboot. - */ -#ifdef USB_DEBUG - if (OHCI_INST(dev->controller)->opreg->HcInterruptStatus & WritebackDoneHead) { - debug("done queue:\n"); - debug("%x, %x\n", OHCI_INST(dev->controller)->hcca->HccaDoneHead, phys_to_virt(OHCI_INST(dev->controller)->hcca->HccaDoneHead)); - if ((OHCI_INST(dev->controller)->hcca->HccaDoneHead & ~1) == 0) { - debug("HcInterruptStatus %x\n", OHCI_INST(dev->controller)->opreg->HcInterruptStatus); - } - td_t *done_queue = NULL; - td_t *done_head = (td_t*)phys_to_virt(OHCI_INST(dev->controller)->hcca->HccaDoneHead); - while (1) { - td_t *oldnext = (td_t*)phys_to_virt(done_head->next_td); - if (oldnext == done_queue) break; /* last element refers to second to last, ie. endless loop */ - if (oldnext == phys_to_virt(0)) break; /* last element of done list == first element of real list */ - debug("head is %x, pointing to %x. requeueing to %x\n", done_head, oldnext, done_queue); - done_head->next_td = (u32)done_queue; - done_queue = done_head; - done_head = oldnext; - } - for (cur = done_queue; cur != 0; cur = (td_t*)cur->next_td) { - dump_td(cur, 1); - } - OHCI_INST(dev->controller)->opreg->HcInterruptStatus &= ~WritebackDoneHead; - } -#endif -#endif + /* Clear the done queue. */ + ohci_process_done_queue(OHCI_INST(dev->controller), 1); if (head->head_pointer & 1) { debug("HALTED!\n"); @@ -262,12 +234,31 @@ wait_for_ed(usbdev_t *dev, ed_t *head, int pages) return 0; } +static void +ohci_free_ed (ed_t *const head) +{ + /* In case the transfer canceled, we have to free unprocessed TDs. */ + while ((head->head_pointer & ~0x3) != head->tail_pointer) { + /* Save current TD pointer. */ + td_t *const cur_td = + (td_t*)phys_to_virt(head->head_pointer & ~0x3); + /* Advance head pointer. */ + head->head_pointer = cur_td->next_td; + /* Free current TD. */ + free((void *)cur_td); + } + + /* Always free the dummy TD */ + if ((head->head_pointer & ~0x3) == head->tail_pointer) + free(phys_to_virt(head->head_pointer & ~0x3)); + /* and the ED. */ + free((void *)head); +} + static int ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen, unsigned char *data) { - int i; - td_t *cur; // pages are specified as 4K in OHCI, so don't use getpagesize() @@ -275,30 +266,31 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen int last_page = (unsigned long)(data+dalen-1)/4096; if (last_page < first_page) last_page = first_page; int pages = (dalen==0)?0:(last_page - first_page + 1); - int td_count = (pages+1)/2; - td_t *tds = memalign(sizeof(td_t), (td_count+3)*sizeof(td_t)); - memset((void*)tds, 0, (td_count+3)*sizeof(td_t)); + /* First TD. */ + td_t *const first_td = (td_t *)memalign(sizeof(td_t), sizeof(td_t)); + memset((void *)first_td, 0, sizeof(*first_td)); + cur = first_td; - for (i=0; i < td_count + 3; i++) { - tds[i].next_td = virt_to_phys(&tds[i+1]); - } - tds[td_count + 3].next_td = 0; - - tds[0].config = TD_DIRECTION_SETUP | - TD_DELAY_INTERRUPT_NODELAY | + cur->config = TD_DIRECTION_SETUP | + TD_DELAY_INTERRUPT_NOINTR | TD_TOGGLE_FROM_TD | TD_TOGGLE_DATA0 | TD_CC_NOACCESS; - tds[0].current_buffer_pointer = virt_to_phys(devreq); - tds[0].buffer_end = virt_to_phys(devreq + drlen - 1); - - cur = &tds[0]; + cur->current_buffer_pointer = virt_to_phys(devreq); + cur->buffer_end = virt_to_phys(devreq + drlen - 1); while (pages > 0) { - cur++; + /* One more TD. */ + td_t *const next = (td_t *)memalign(sizeof(td_t), sizeof(td_t)); + memset((void *)next, 0, sizeof(*next)); + /* Linked to the previous. */ + cur->next_td = virt_to_phys(next); + /* Advance to the new TD. */ + cur = next; + cur->config = (dir == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | - TD_DELAY_INTERRUPT_NODELAY | + TD_DELAY_INTERRUPT_NOINTR | TD_TOGGLE_FROM_ED | TD_CC_NOACCESS; cur->current_buffer_pointer = virt_to_phys(data); @@ -323,17 +315,26 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen } } - cur++; + /* One more TD. */ + td_t *const next_td = (td_t *)memalign(sizeof(td_t), sizeof(td_t)); + memset((void *)next_td, 0, sizeof(*next_td)); + /* Linked to the previous. */ + cur->next_td = virt_to_phys(next_td); + /* Advance to the new TD. */ + cur = next_td; cur->config = (dir == IN ? TD_DIRECTION_OUT : TD_DIRECTION_IN) | - TD_DELAY_INTERRUPT_NODELAY | + TD_DELAY_INTERRUPT_ZERO | /* Write done head after this TD. */ TD_TOGGLE_FROM_TD | TD_TOGGLE_DATA1 | TD_CC_NOACCESS; cur->current_buffer_pointer = 0; cur->buffer_end = 0; - /* final dummy TD */ - cur++; + /* Final dummy TD. */ + td_t *const final_td = (td_t *)memalign(sizeof(td_t), sizeof(td_t)); + memset((void *)final_td, 0, sizeof(*final_td)); + /* Linked to the previous. */ + cur->next_td = virt_to_phys(final_td); /* Data structures */ ed_t *head = memalign(sizeof(ed_t), sizeof(ed_t)); @@ -343,10 +344,11 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen (OHCI_FROM_TD << ED_DIR_SHIFT) | (dev->speed?ED_LOWSPEED:0) | (dev->endpoints[0].maxpacketsize << ED_MPS_SHIFT); - head->tail_pointer = virt_to_phys(cur); - head->head_pointer = virt_to_phys(tds); + head->tail_pointer = virt_to_phys(final_td); + head->head_pointer = virt_to_phys(first_td); - debug("doing control transfer with %x. first_td at %x\n", head->config & ED_FUNC_MASK, virt_to_phys(tds)); + debug("doing control transfer with %x. first_td at %x\n", + head->config & ED_FUNC_MASK, virt_to_phys(first_td)); /* activate schedule */ OHCI_INST(dev->controller)->opreg->HcControlHeadED = virt_to_phys(head); @@ -358,8 +360,7 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen OHCI_INST(dev->controller)->opreg->HcControl &= ~ControlListEnable; /* free memory */ - free((void*)tds); - free((void*)head); + ohci_free_ed(head); return failure; } @@ -371,7 +372,7 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) int i; debug("bulk: %x bytes from %x, finalize: %x, maxpacketsize: %x\n", dalen, data, finalize, ep->maxpacketsize); - td_t *cur; + td_t *cur, *next; // pages are specified as 4K in OHCI, so don't use getpagesize() int first_page = (unsigned long)data / 4096; @@ -384,16 +385,16 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) td_count++; } - td_t *tds = memalign(sizeof(td_t), (td_count+1)*sizeof(td_t)); - memset((void*)tds, 0, (td_count+1)*sizeof(td_t)); - - for (i=0; i < td_count; i++) { - tds[i].next_td = virt_to_phys(&tds[i+1]); - } + /* First TD. */ + td_t *const first_td = (td_t *)memalign(sizeof(td_t), sizeof(td_t)); + memset((void *)first_td, 0, sizeof(*first_td)); + cur = next = first_td; - for (cur = tds; cur->next_td != 0; cur++) { + for (i = 0; i < td_count; ++i) { + /* Advance to next TD. */ + cur = next; cur->config = (ep->direction == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | - TD_DELAY_INTERRUPT_NODELAY | + TD_DELAY_INTERRUPT_NOINTR | TD_TOGGLE_FROM_ED | TD_CC_NOACCESS; cur->current_buffer_pointer = virt_to_phys(data); @@ -422,8 +423,18 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) dalen -= second_page_size; data += second_page_size; } + /* One more TD. */ + next = (td_t *)memalign(sizeof(td_t), sizeof(td_t)); + memset((void *)next, 0, sizeof(*next)); + /* Linked to the previous. */ + cur->next_td = virt_to_phys(next); } + /* Write done head after last TD. */ + cur->config &= ~TD_DELAY_INTERRUPT_MASK; + /* Advance to final, dummy TD. */ + cur = next; + /* Data structures */ ed_t *head = memalign(sizeof(ed_t), sizeof(ed_t)); memset((void*)head, 0, sizeof(*head)); @@ -433,10 +444,12 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) (ep->dev->speed?ED_LOWSPEED:0) | (ep->maxpacketsize << ED_MPS_SHIFT); head->tail_pointer = virt_to_phys(cur); - head->head_pointer = virt_to_phys(tds) | (ep->toggle?ED_TOGGLE:0); + head->head_pointer = virt_to_phys(first_td) | (ep->toggle?ED_TOGGLE:0); - debug("doing bulk transfer with %x(%x). first_td at %x, last %x\n", head->config & ED_FUNC_MASK, - (head->config & ED_EP_MASK) >> ED_EP_SHIFT, virt_to_phys(tds), virt_to_phys(cur)); + debug("doing bulk transfer with %x(%x). first_td at %x, last %x\n", + head->config & ED_FUNC_MASK, + (head->config & ED_EP_MASK) >> ED_EP_SHIFT, + virt_to_phys(first_td), virt_to_phys(cur)); /* activate schedule */ OHCI_INST(ep->dev->controller)->opreg->HcBulkHeadED = virt_to_phys(head); @@ -450,8 +463,7 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) ep->toggle = head->head_pointer & ED_TOGGLE; /* free memory */ - free((void*)tds); - free((void*)head); + ohci_free_ed(head); if (failure) { /* try cleanup */ @@ -484,3 +496,39 @@ ohci_poll_intr_queue (void *q_) return NULL; } +static void +ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) +{ + int i; + + /* Check if done head has been written. */ + if (!(ohci->opreg->HcInterruptStatus & WritebackDoneHead)) + return; + /* Fetch current done head. + Lsb is only interesting for hw interrupts. */ + u32 phys_done_queue = ohci->hcca->HccaDoneHead & ~1; + /* Tell host controller, he may overwrite the done head pointer. */ + ohci->opreg->HcInterruptStatus = WritebackDoneHead; + + i = 0; + /* Process done queue (it's in reversed order). */ + while (phys_done_queue) { + td_t *const done_td = (td_t *)phys_to_virt(phys_done_queue); + + /* Advance pointer to next TD. */ + phys_done_queue = done_td->next_td; + + switch (done_td->config & TD_QUEUETYPE_MASK) { + case TD_QUEUETYPE_ASYNC: + /* Free processed async TDs. */ + free((void *)done_td); + break; + default: + break; + } + ++i; + } + if (spew_debug) + debug("Processed %d done TDs.\n", i); +} + diff --git a/payloads/libpayload/drivers/usb/ohci_private.h b/payloads/libpayload/drivers/usb/ohci_private.h index a60b294607..0bcae03e97 100644 --- a/payloads/libpayload/drivers/usb/ohci_private.h +++ b/payloads/libpayload/drivers/usb/ohci_private.h @@ -191,9 +191,9 @@ typedef struct { u32 HccaInterruptTable[32]; - u16 HccaFrameNumber; - u16 HccaPad1; - u32 HccaDoneHead; + volatile u16 HccaFrameNumber; + volatile u16 HccaPad1; + volatile u32 HccaDoneHead; u8 reserved[116]; // pad to 256 byte } __attribute__ ((packed)) hcca_t; @@ -229,12 +229,25 @@ u32 next_td; u32 buffer_end; } __attribute__ ((packed)) td_t; +/* + * Bits 0 through 17 of .config won't be interpreted by the host controller + * (HC) and, after processing the TD, the HC has to ensure those bits have + * the same state as before. So we are free to use those bits for our own + * purpose. + */ +#define TD_QUEUETYPE_SHIFT 0 +#define TD_QUEUETYPE_MASK MASK(TD_QUEUETYPE_SHIFT, 2) +#define TD_QUEUETYPE_ASYNC (0 << TD_QUEUETYPE_SHIFT) + #define TD_DIRECTION_SHIFT 19 #define TD_DIRECTION_MASK MASK(TD_DIRECTION_SHIFT, 2) #define TD_DIRECTION_SETUP OHCI_SETUP << TD_DIRECTION_SHIFT #define TD_DIRECTION_IN OHCI_IN << TD_DIRECTION_SHIFT #define TD_DIRECTION_OUT OHCI_OUT << TD_DIRECTION_SHIFT -#define TD_DELAY_INTERRUPT_NODELAY (7 << 21) +#define TD_DELAY_INTERRUPT_SHIFT 21 +#define TD_DELAY_INTERRUPT_MASK MASK(TD_DELAY_INTERRUPT_SHIFT, 3) +#define TD_DELAY_INTERRUPT_ZERO 0 +#define TD_DELAY_INTERRUPT_NOINTR (7 << TD_DELAY_INTERRUPT_SHIFT) #define TD_TOGGLE_DATA0 0 #define TD_TOGGLE_DATA1 (1 << 24) #define TD_TOGGLE_FROM_ED 0 |