diff options
author | Nico Huber <nico.huber@secunet.com> | 2012-06-20 14:58:21 +0200 |
---|---|---|
committer | Nico Huber <nico.huber@secunet.com> | 2012-06-21 11:59:11 +0200 |
commit | 274c63e367aacbd50fb35f25da896fead08a6c3d (patch) | |
tree | 5c8d313a5e6be81e472774401d1eeb435db13f60 /payloads/libpayload/drivers/usb/ohci.c | |
parent | 542fe85da956c62e46da1d9cfeb79c3d06b75427 (diff) |
libpayload: Add support for interrupt transfers in OHCI
This adds support for usb interrupt transfers to the OHCI driver.
Basically this enables support for HID keyboard devices.
For each interrupt transfer endpoint, two queues of transfer
descriptors (TDs) are maintained: the first with initialized TDs
is linked to the periodic schedule of the host controller (HC), the
second holds processed TDs which will be polled by the usb class
driver. The HC moves processed TDs from its schedule to a done queue.
We periodically fetch all TDs from the done queue, to put them on the
queue associated with the endpoint, where they can be polled from.
Fully processed TDs (i.e. which have gone throuch all of this) will be
reinitialized and put on the first queue again.
Change-Id: Iaab72c04087b36c9f0f6e539e31b47060c190015
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: http://review.coreboot.org/1128
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Tested-by: build bot (Jenkins)
Diffstat (limited to 'payloads/libpayload/drivers/usb/ohci.c')
-rw-r--r-- | payloads/libpayload/drivers/usb/ohci.c | 230 |
1 files changed, 222 insertions, 8 deletions
diff --git a/payloads/libpayload/drivers/usb/ohci.c b/payloads/libpayload/drivers/usb/ohci.c index 6d98cc1100..0f4886fc63 100644 --- a/payloads/libpayload/drivers/usb/ohci.c +++ b/payloads/libpayload/drivers/usb/ohci.c @@ -144,6 +144,8 @@ ohci_init (pcidev_t addr) OHCI_INST (controller)->periodic_ed = periodic_ed; OHCI_INST (controller)->opreg->HcHCCA = virt_to_phys(OHCI_INST (controller)->hcca); + /* Make sure periodic schedule is enabled. */ + OHCI_INST (controller)->opreg->HcControl |= PeriodicListEnable; OHCI_INST (controller)->opreg->HcControl &= ~IsochronousEnable; // unused by this driver // disable everything, contrary to what OHCI spec says in 5.1.1.4, as we don't need IRQs OHCI_INST (controller)->opreg->HcInterruptEnable = 1<<31; @@ -477,33 +479,215 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) return failure; } + +struct _intr_queue; + +struct _intrq_td { + volatile td_t td; + u8 *data; + struct _intrq_td *next; + struct _intr_queue *intrq; +}; + +struct _intr_queue { + volatile ed_t ed; + struct _intrq_td *head; + struct _intrq_td *tail; + u8 *data; + int reqsize; + endpoint_t *endp; + unsigned int remaining_tds; +}; + +typedef struct _intrq_td intrq_td_t; +typedef struct _intr_queue intr_queue_t; + +#define INTRQ_TD_FROM_TD(x) ((intrq_td_t *)x) + +static void +ohci_fill_intrq_td(intrq_td_t *const td, intr_queue_t *const intrq, + u8 *const data) +{ + memset(td, 0, sizeof(*td)); + td->td.config = TD_QUEUETYPE_INTR | + (intrq->endp->direction == IN + ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | + TD_DELAY_INTERRUPT_ZERO | + TD_TOGGLE_FROM_ED | + TD_CC_NOACCESS; + td->td.current_buffer_pointer = virt_to_phys(data); + td->td.buffer_end = td->td.current_buffer_pointer + intrq->reqsize - 1; + td->intrq = intrq; + td->data = data; +} + /* create and hook-up an intr queue into device schedule */ -static void* -ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming) +static void * +ohci_create_intr_queue(endpoint_t *const ep, const int reqsize, + const int reqcount, const int reqtiming) { - return NULL; + int i; + intrq_td_t *first_td = NULL, *last_td = NULL; + + if (reqsize > 4096) + return NULL; + + intr_queue_t *const intrq = + (intr_queue_t *)memalign(sizeof(intrq->ed), sizeof(*intrq)); + memset(intrq, 0, sizeof(*intrq)); + intrq->data = (u8 *)malloc(reqcount * reqsize); + intrq->reqsize = reqsize; + intrq->endp = ep; + + /* Create #reqcount TDs. */ + u8 *cur_data = intrq->data; + for (i = 0; i < reqcount; ++i) { + intrq_td_t *const td = memalign(sizeof(td->td), sizeof(*td)); + ++intrq->remaining_tds; + ohci_fill_intrq_td(td, intrq, cur_data); + cur_data += reqsize; + if (!first_td) + first_td = td; + else + last_td->td.next_td = virt_to_phys(&td->td); + last_td = td; + } + + /* Create last, dummy TD. */ + intrq_td_t *dummy_td = memalign(sizeof(dummy_td->td), sizeof(*dummy_td)); + memset(dummy_td, 0, sizeof(*dummy_td)); + dummy_td->intrq = intrq; + if (last_td) + last_td->td.next_td = virt_to_phys(&dummy_td->td); + last_td = dummy_td; + + /* Initialize ED. */ + intrq->ed.config = (ep->dev->address << ED_FUNC_SHIFT) | + ((ep->endpoint & 0xf) << ED_EP_SHIFT) | + (((ep->direction == IN) ? OHCI_IN : OHCI_OUT) << ED_DIR_SHIFT) | + (ep->dev->speed ? ED_LOWSPEED : 0) | + (ep->maxpacketsize << ED_MPS_SHIFT); + intrq->ed.tail_pointer = virt_to_phys(last_td); + intrq->ed.head_pointer = virt_to_phys(first_td) | + (ep->toggle ? ED_TOGGLE : 0); + + /* Insert ED into periodic table. */ + int nothing_placed = 1; + ohci_t *const ohci = OHCI_INST(ep->dev->controller); + u32 *const intr_table = ohci->hcca->HccaInterruptTable; + const u32 dummy_ptr = virt_to_phys(ohci->periodic_ed); + for (i = 0; i < 32; i += reqtiming) { + /* Advance to the next free position. */ + while ((i < 32) && (intr_table[i] != dummy_ptr)) ++i; + if (i < 32) { + intr_table[i] = virt_to_phys(&intrq->ed); + nothing_placed = 0; + } + } + if (nothing_placed) { + printf("Error: Failed to place ohci interrupt endpoint " + "descriptor into periodic table: no space left\n"); + ohci_destroy_intr_queue(ep, intrq); + return NULL; + } + + return intrq; } /* remove queue from device schedule, dropping all data that came in */ static void -ohci_destroy_intr_queue (endpoint_t *ep, void *q_) +ohci_destroy_intr_queue(endpoint_t *const ep, void *const q_) { + intr_queue_t *const intrq = (intr_queue_t *)q_; + + int i; + + /* Remove interrupt queue from periodic table. */ + ohci_t *const ohci = OHCI_INST(ep->dev->controller); + u32 *const intr_table = ohci->hcca->HccaInterruptTable; + for (i=0; i < 32; ++i) { + if (intr_table[i] == virt_to_phys(intrq)) + intr_table[i] = virt_to_phys(ohci->periodic_ed); + } + /* Wait for frame to finish. */ + mdelay(1); + + /* Free unprocessed TDs. */ + while ((intrq->ed.head_pointer & ~0x3) != intrq->ed.tail_pointer) { + td_t *const cur_td = + (td_t *)phys_to_virt(intrq->ed.head_pointer & ~0x3); + intrq->ed.head_pointer = cur_td->next_td; + free(INTRQ_TD_FROM_TD(cur_td)); + --intrq->remaining_tds; + } + /* Free final, dummy TD. */ + free(phys_to_virt(intrq->ed.head_pointer & ~0x3)); + /* Free data buffer. */ + free(intrq->data); + + /* Process done queue and free processed TDs. */ + ohci_process_done_queue(ohci, 1); + while (intrq->head) { + intrq_td_t *const cur_td = intrq->head; + intrq->head = intrq->head->next; + free(cur_td); + --intrq->remaining_tds; + } + if (intrq->remaining_tds) { + printf("error: ohci_destroy_intr_queue(): " + "freed all but %d TDs.\n", intrq->remaining_tds); + } + + free(intrq); + + /* Save data toggle. */ + ep->toggle = intrq->ed.head_pointer & ED_TOGGLE; } /* read one intr-packet from queue, if available. extend the queue for new input. return NULL if nothing new available. Recommended use: while (data=poll_intr_queue(q)) process(data); */ -static u8* -ohci_poll_intr_queue (void *q_) +static u8 * +ohci_poll_intr_queue(void *const q_) { - return NULL; + intr_queue_t *const intrq = (intr_queue_t *)q_; + + u8 *data = NULL; + + /* Process done queue first, then check if we have work to do. */ + ohci_process_done_queue(OHCI_INST(intrq->endp->dev->controller), 0); + + if (intrq->head) { + /* Save pointer to processed TD and advance. */ + intrq_td_t *const cur_td = intrq->head; + intrq->head = cur_td->next; + + /* Return data buffer of this TD. */ + data = cur_td->data; + + /* Requeue this TD (i.e. copy to dummy and requeue as dummy). */ + intrq_td_t *const dummy_td = + INTRQ_TD_FROM_TD(phys_to_virt(intrq->ed.tail_pointer)); + ohci_fill_intrq_td(dummy_td, intrq, cur_td->data); + /* Reset all but intrq pointer (i.e. init as dummy). */ + memset(cur_td, 0, sizeof(*cur_td)); + cur_td->intrq = intrq; + /* Insert into interrupt queue as dummy. */ + dummy_td->td.next_td = virt_to_phys(&cur_td->td); + intrq->ed.tail_pointer = virt_to_phys(&cur_td->td); + } + + return data; } static void ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) { - int i; + int i, j; + + /* Temporary queue of interrupt queue TDs (to reverse order). */ + intrq_td_t *temp_tdq = NULL; /* Check if done head has been written. */ if (!(ohci->opreg->HcInterruptStatus & WritebackDoneHead)) @@ -527,6 +711,11 @@ ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) /* Free processed async TDs. */ free((void *)done_td); break; + case TD_QUEUETYPE_INTR: + /* Save done TD if it comes from an interrupt queue. */ + INTRQ_TD_FROM_TD(done_td)->next = temp_tdq; + temp_tdq = INTRQ_TD_FROM_TD(done_td); + break; default: break; } @@ -534,5 +723,30 @@ ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) } if (spew_debug) debug("Processed %d done TDs.\n", i); + + j = 0; + /* Process interrupt queue TDs in right order. */ + while (temp_tdq) { + /* Save pointer of current TD and advance. */ + intrq_td_t *const cur_td = temp_tdq; + temp_tdq = temp_tdq->next; + + /* The interrupt queue for the current TD. */ + intr_queue_t *const intrq = cur_td->intrq; + /* Append to interrupt queue. */ + if (!intrq->head) { + /* First element. */ + intrq->head = intrq->tail = cur_td; + } else { + /* Insert at tail. */ + intrq->tail->next = cur_td; + intrq->tail = cur_td; + } + /* It's always the last element. */ + cur_td->next = NULL; + ++j; + } + if (spew_debug) + debug("processed %d done tds, %d intr tds thereof.\n", i, j); } |