summaryrefslogtreecommitdiff
path: root/payloads/libpayload/drivers/usb/ehci.c
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/drivers/usb/ehci.c')
-rw-r--r--payloads/libpayload/drivers/usb/ehci.c237
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;