diff options
author | Nico Huber <nico.huber@secunet.com> | 2012-05-25 10:09:13 +0200 |
---|---|---|
committer | Stefan Reinauer <stefan.reinauer@coreboot.org> | 2012-06-07 23:16:35 +0200 |
commit | 62eb5b3837c51228898288cb7347dce81c563842 (patch) | |
tree | 4a37a05545fc332d0de5102438b1eef2e6301e10 /payloads/libpayload/drivers/usb/ehci.c | |
parent | 4842dfe6f77c21f7074ca79b0ce3f882f08f92b2 (diff) | |
download | coreboot-62eb5b3837c51228898288cb7347dce81c563842.tar.xz |
libpayload: Add support for interrupt transfers in EHCI
This adds support for usb interrupt transfers in the EHCI driver. Split
transactions are supported, so this enables support for HID keyboards
devices over hubs in high-speed mode.
Change-Id: I9eb08f12b12c67ece10814952cb8651278b02f9d
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: http://review.coreboot.org/1083
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Diffstat (limited to 'payloads/libpayload/drivers/usb/ehci.c')
-rw-r--r-- | payloads/libpayload/drivers/usb/ehci.c | 237 |
1 files changed, 232 insertions, 5 deletions
diff --git a/payloads/libpayload/drivers/usb/ehci.c b/payloads/libpayload/drivers/usb/ehci.c index c8f469e606..7ee97612be 100644 --- a/payloads/libpayload/drivers/usb/ehci.c +++ b/payloads/libpayload/drivers/usb/ehci.c @@ -54,8 +54,35 @@ static void ehci_reset (hci_t *controller) } +static int ehci_set_periodic_schedule(ehci_t *ehcic, int enable) +{ + /* Set periodic schedule status. */ + if (enable) + ehcic->operation->usbcmd |= HC_OP_PERIODIC_SCHED_EN; + else + ehcic->operation->usbcmd &= ~HC_OP_PERIODIC_SCHED_EN; + /* Wait for the controller to accept periodic schedule status. + * This shouldn't take too long, but we should timeout nevertheless. + */ + enable = enable ? HC_OP_PERIODIC_SCHED_STAT : 0; + int timeout = 100; /* time out after 100ms */ + while (((ehcic->operation->usbsts & HC_OP_PERIODIC_SCHED_STAT) != enable) + && timeout--) + mdelay(1); + if (timeout < 0) { + debug("ehci periodic schedule status change timed out.\n"); + return 1; + } + return 0; +} + static void ehci_shutdown (hci_t *controller) { + /* Make sure periodic schedule is disabled */ + ehci_set_periodic_schedule(EHCI_INST(controller), 0); + /* Free periodic frame list */ + free(phys_to_virt(EHCI_INST(controller)->operation->periodiclistbase)); + EHCI_INST(controller)->operation->configflag = 0; } @@ -328,18 +355,202 @@ static int ehci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq return result; } -static void* ehci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming) + +typedef struct _intr_qtd_t intr_qtd_t; + +struct _intr_qtd_t { + volatile qtd_t td; + u8 *data; + intr_qtd_t *next; +}; + +typedef struct { + volatile ehci_qh_t qh; + intr_qtd_t *head; + intr_qtd_t *tail; + u8 *data; + endpoint_t *endp; + int reqsize; +} intr_queue_t; + +static void fill_intr_queue_td( + intr_queue_t *const intrq, + intr_qtd_t *const intr_qtd, + u8 *const data) +{ + const int pid = (intrq->endp->direction == IN) ? EHCI_IN + : (intrq->endp->direction == OUT) ? EHCI_OUT + : EHCI_SETUP; + const int cerr = (intrq->endp->dev->speed < 2) ? 1 : 0; + + memset(intr_qtd, 0, sizeof(*intr_qtd)); + intr_qtd->td.next_qtd = QTD_TERMINATE; + intr_qtd->td.alt_next_qtd = QTD_TERMINATE; + intr_qtd->td.token = QTD_ACTIVE | + (pid << QTD_PID_SHIFT) | + (cerr << QTD_CERR_SHIFT) | + ((intrq->endp->toggle & 1) << QTD_TOGGLE_SHIFT); + fill_td(&intr_qtd->td, data, intrq->reqsize); + intr_qtd->data = data; + intr_qtd->next = NULL; + + intrq->endp->toggle ^= 1; +} + +static void ehci_destroy_intr_queue(endpoint_t *const, void *const); + +static void *ehci_create_intr_queue( + endpoint_t *const ep, + const int reqsize, + int reqcount, + const int reqtiming) { - return NULL; + int i; + + if ((reqsize > (4 * 4096 + 1)) || /* the maximum for arbitrary aligned + data in five 4096 byte pages */ + (reqtiming > 1024)) + return NULL; + if (reqcount < 2) /* we need at least 2: + one for processing, one for the hc to advance to */ + reqcount = 2; + + int hubaddr = 0, hubport = 0; + if (ep->dev->speed < 2) { + /* we need a split transaction */ + if (closest_usb2_hub(ep->dev, &hubaddr, &hubport)) + return NULL; + } + + intr_queue_t *const intrq = + (intr_queue_t *)memalign(32, sizeof(intr_queue_t)); + u8 *data = (u8 *)malloc(reqsize * reqcount); + if (!intrq || !data) + fatal("Not enough memory to create USB interrupt queue.\n"); + intrq->data = data; + intrq->endp = ep; + intrq->reqsize = reqsize; + + /* create #reqcount transfer descriptors (qTDs) */ + intrq->head = (intr_qtd_t *)memalign(32, sizeof(intr_qtd_t)); + intr_qtd_t *cur_td = intrq->head; + for (i = 0; i < reqcount; ++i) { + fill_intr_queue_td(intrq, cur_td, data); + data += reqsize; + if (i < reqcount - 1) { + /* create one more qTD */ + intr_qtd_t *const next_td = + (intr_qtd_t *)memalign(32, sizeof(intr_qtd_t)); + cur_td->td.next_qtd = virt_to_phys(&next_td->td); + cur_td->next = next_td; + cur_td = next_td; + } + } + intrq->tail = cur_td; + + /* initialize QH */ + const int endp = ep->endpoint & 0xf; + memset(&intrq->qh, 0, sizeof(intrq->qh)); + intrq->qh.horiz_link_ptr = PS_TERMINATE; + intrq->qh.epchar = ep->dev->address | + (endp << QH_EP_SHIFT) | + (ep->dev->speed << QH_EPS_SHIFT) | + (1 << QH_DTC_SHIFT) | + (0 << QH_RECLAIM_HEAD_SHIFT) | + (ep->maxpacketsize << QH_MPS_SHIFT) | + (0 << QH_NAK_CNT_SHIFT); + intrq->qh.epcaps = (1 << QH_PIPE_MULTIPLIER_SHIFT) | + (hubport << QH_PORT_NUMBER_SHIFT) | + (hubaddr << QH_HUB_ADDRESS_SHIFT) | + (0xfe << QH_UFRAME_CMASK_SHIFT) | + 1 /* uFrame S-mask */; + intrq->qh.td.next_qtd = virt_to_phys(&intrq->head->td); + + /* insert QH into periodic schedule */ + int nothing_placed = 1; + u32 *const ps = (u32 *)phys_to_virt(EHCI_INST(ep->dev->controller) + ->operation->periodiclistbase); + for (i = 0; i < 1024; i += reqtiming) { + /* advance to the next free position */ + while ((i < 1024) && !(ps[i] & PS_TERMINATE)) ++i; + if (i < 1024) { + ps[i] = virt_to_phys(&intrq->qh) | PS_TYPE_QH; + nothing_placed = 0; + } + } + if (nothing_placed) { + printf("Error: Failed to place ehci interrupt queue head " + "into periodic schedule: no space left\n"); + ehci_destroy_intr_queue(ep, intrq); + return NULL; + } + + return intrq; } -static void ehci_destroy_intr_queue (endpoint_t *ep, void *queue) +static void ehci_destroy_intr_queue(endpoint_t *const ep, void *const queue) { + intr_queue_t *const intrq = (intr_queue_t *)queue; + + /* remove QH from periodic schedule */ + int i; + u32 *const ps = (u32 *)phys_to_virt(EHCI_INST( + ep->dev->controller)->operation->periodiclistbase); + for (i = 0; i < 1024; ++i) { + if ((ps[i] & PS_PTR_MASK) == virt_to_phys(&intrq->qh)) + ps[i] = PS_TERMINATE; + } + + /* wait 1ms for frame to end */ + mdelay(1); + + while (intrq->head) { + /* disable qTD and destroy list */ + intrq->head->td.next_qtd = QTD_TERMINATE; + intrq->head->td.token &= ~QTD_ACTIVE; + + /* save and advance head ptr */ + intr_qtd_t *const to_free = intrq->head; + intrq->head = intrq->head->next; + + /* free current interrupt qTD */ + free(to_free); + } + free(intrq->data); + free(intrq); } -static u8* ehci_poll_intr_queue (void *queue) +static u8 *ehci_poll_intr_queue(void *const queue) { - return NULL; + intr_queue_t *const intrq = (intr_queue_t *)queue; + + u8 *ret = NULL; + + /* process if head qTD is inactive AND QH has been moved forward */ + if (!(intrq->head->td.token & QTD_ACTIVE) && + (intrq->qh.current_td_ptr != + virt_to_phys(&intrq->head->td))) { + if (!(intrq->head->td.token & QTD_STATUS_MASK)) + ret = intrq->head->data; + else + debug("ehci_poll_intr_queue: transfer failed, " + "status == 0x%02x\n", + intrq->head->td.token & QTD_STATUS_MASK); + + /* save and advance our head ptr */ + intr_qtd_t *const new_td = intrq->head; + intrq->head = intrq->head->next; + + /* reuse executed qTD */ + fill_intr_queue_td(intrq, new_td, new_td->data); + + /* at last insert reused qTD at the + * end and advance our tail ptr */ + intrq->tail->td.next_qtd = virt_to_phys(&new_td->td); + intrq->tail->next = new_td; + intrq->tail = intrq->tail->next; + } + return ret; } hci_t * @@ -391,6 +602,22 @@ ehci_init (pcidev_t addr) /* take over all ports. USB1 should be blind now */ EHCI_INST(controller)->operation->configflag = 1; + /* Initialize periodic frame list */ + /* 1024 32-bit pointers, 4kb aligned */ + u32 *const periodic_list = (u32 *)memalign(4096, 1024 * sizeof(u32)); + if (!periodic_list) + fatal("Not enough memory creating EHCI periodic frame list.\n"); + for (i = 0; i < 1024; ++i) + periodic_list[i] = PS_TERMINATE; + + /* Make sure periodic schedule is disabled */ + ehci_set_periodic_schedule(EHCI_INST(controller), 0); + /* Set periodic frame list pointer */ + EHCI_INST(controller)->operation->periodiclistbase = + virt_to_phys(periodic_list); + /* Enable use of periodic schedule */ + ehci_set_periodic_schedule(EHCI_INST(controller), 1); + /* TODO lots of stuff missing */ controller->devices[0]->controller = controller; |