[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[cdi-devel] [PATCH v2 7/9] ehci: Add EHCI driver
+ This adds an EHCI USB host controller driver. It is missing support
for periodic schedule (interrupt and isochronous transfers),
hotplugging, suspend/resume, etc. pp..
Signed-off-by: Max Reitz <max@xxxxxxxxxx>
---
ehci/ehci.c | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ehci/ehci.h | 170 ++++++++++++++++
ehci/main.c | 68 +++++++
3 files changed, 899 insertions(+)
create mode 100644 ehci/ehci.c
create mode 100644 ehci/ehci.h
create mode 100644 ehci/main.c
diff --git a/ehci/ehci.c b/ehci/ehci.c
new file mode 100644
index 0000000..6da316e
--- /dev/null
+++ b/ehci/ehci.c
@@ -0,0 +1,661 @@
+/******************************************************************************
+ * Copyright (c) 2015 Max Reitz *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction, including without limitation *
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
+ * and/or sell copies of the Software, and to permit persons to whom the *
+ * Software is furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
+ * DEALINGS IN THE SOFTWARE. *
+ ******************************************************************************/
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cdi.h>
+#include <cdi/lists.h>
+#include <cdi/misc.h>
+#include <cdi/pci.h>
+#include <cdi/usb.h>
+#include <cdi/usb_hcd.h>
+
+#include "ehci.h"
+
+
+#define EHCI_TIMEOUT 1000 // ms
+
+
+static bool bios_handoff(struct cdi_pci_device *pci_dev, struct ehci *hc);
+static void irq_handler(struct cdi_device *dev);
+
+
+struct cdi_device *ehci_init_device(struct cdi_bus_data *bus_data)
+{
+ struct cdi_pci_device *pci_dev = (struct cdi_pci_device *)bus_data;
+ static int dev_counter = 0;
+
+ if (pci_dev->class_id != 0x0c ||
+ pci_dev->subclass_id != 0x03 ||
+ pci_dev->interface_id != 0x20)
+ {
+ return NULL;
+ }
+
+ struct ehci *hc = calloc(1, sizeof(*hc));
+
+ hc->hc.dev.name = malloc(8);
+ sprintf((char *)hc->hc.dev.name, "ehci%i", dev_counter++);
+
+ hc->retired_qhs = cdi_list_create();
+
+ // Enable bus mastering
+ cdi_pci_config_writew(pci_dev, 4, cdi_pci_config_readw(pci_dev, 4) | 0x6);
+
+ cdi_pci_alloc_memory(pci_dev);
+
+ struct cdi_pci_resource *res;
+ for (int i = 0; (res = cdi_list_get(pci_dev->resources, i)); i++) {
+ if (res->type == CDI_PCI_MEMORY && !res->index) {
+ hc->caps = res->address;
+ hc->regs = (struct ehci_registers *)((uintptr_t)res->address +
+ hc->caps->caplength);
+ }
+ }
+
+ if (!bios_handoff(pci_dev, hc)) {
+ goto fail;
+ }
+
+ // Stop operation
+ hc->regs->usbcmd = (hc->regs->usbcmd & 0xff00ff00u) | 0x00080010u;
+
+ // Wait until HCHALTED becomes true
+ int timeout_counter = 0;
+ while (!(hc->regs->usbsts & (1 << 12)) && ++timeout_counter < 50) {
+ cdi_sleep_ms(1);
+ }
+ if (timeout_counter >= 50) {
+ goto fail;
+ }
+
+ hc->regs->usbcmd |= (1 << 1); // Reset
+ timeout_counter = 0;
+ while ((hc->regs->usbcmd & (1 << 1)) && ++timeout_counter < 50) {
+ cdi_sleep_ms(1);
+ }
+ if (timeout_counter >= 50) {
+ goto fail;
+ }
+
+ if (hc->caps->hccparams & 1) {
+ // 64-bit addressing capability
+ hc->regs->ctrldssegment = 0;
+ mem_barrier();
+ }
+
+ // async advance doorbell
+ hc->regs->usbintr = (1 << 5);
+ hc->regs->usbsts = hc->regs->usbsts;
+ cdi_register_irq(pci_dev->irq, &irq_handler, &hc->hc.dev);
+
+ hc->periodic_list_mem = cdi_mem_alloc(4096, 12
+ | CDI_MEM_PHYS_CONTIGUOUS
+ | CDI_MEM_DMA_4G
+ | CDI_MEM_NOINIT);
+ hc->periodic_list = hc->periodic_list_mem->vaddr;
+ for (int i = 0; i < 1024; i++) {
+ hc->periodic_list[i] = EHCI_T;
+ }
+ mem_barrier();
+ hc->regs->periodiclistbase = hc->periodic_list_mem->paddr.items[0].start;
+
+ // Create anchor
+ struct cdi_mem_area *anchor_qh;
+ anchor_qh = cdi_mem_alloc(sizeof(ehci_fat_qh_t), 6
+ | CDI_MEM_PHYS_CONTIGUOUS
+ | CDI_MEM_DMA_4G
+ | CDI_MEM_NOINIT);
+
+ hc->async_start = anchor_qh->vaddr;
+ memset(hc->async_start, 0, sizeof(*hc->async_start));
+
+ hc->async_start->cdi_mem_area = anchor_qh;
+ hc->async_start->paddr = anchor_qh->paddr.items[0].start
+ + offsetof(ehci_fat_qh_t, qh);
+
+ hc->async_start->next = hc->async_start;
+ hc->async_start->previous = hc->async_start;
+
+ hc->async_start->qh.next_qh = hc->async_start->paddr | EHCI_QH;
+ hc->async_start->qh.ep_state[0] = 0x0008e000u;
+ hc->async_start->qh.ep_state[1] = 0x4000ff00u;
+
+ hc->async_start->qh.overlay.next_qtd = EHCI_T;
+ hc->async_start->qh.overlay.alt_next_qtd = EHCI_T;
+
+ hc->regs->asynclistaddr = hc->async_start->paddr;
+
+ mem_barrier();
+
+ // Start operation
+ hc->regs->usbcmd = (hc->regs->usbcmd & 0xff00ff00u) | 0x00080031u;
+
+ // Wait until HCHalted becomes false and the schedules are running
+ timeout_counter = 0;
+ while ((hc->regs->usbsts & 0xd000) != 0xc000 && ++timeout_counter < 50) {
+ cdi_sleep_ms(1);
+ }
+ if (timeout_counter >= 50) {
+ goto fail;
+ }
+
+ // Route everything to this HC
+ hc->regs->configflag |= 1;
+
+ mem_barrier();
+ cdi_sleep_ms(5);
+
+ hc->hc.rh.ports = hc->caps->hcsparams & 0xf;
+
+ // Power up all ports
+ for (int i = 0; i < hc->hc.rh.ports; i++) {
+ hc->regs->portsc[i] |= EHCI_PSC_PP;
+ }
+
+ mem_barrier();
+
+ return &hc->hc.dev;
+
+fail:
+ if (hc->async_start) {
+ cdi_mem_free(hc->async_start->cdi_mem_area);
+ }
+ if (hc->periodic_list_mem) {
+ cdi_mem_free(hc->periodic_list_mem);
+ }
+ free((void *)hc->hc.dev.name);
+ free(hc);
+ cdi_pci_free_memory(pci_dev);
+
+ return NULL;
+}
+
+
+static bool bios_handoff(struct cdi_pci_device *pci_dev, struct ehci *hc)
+{
+ int eecp = (hc->caps->hccparams >> 8) & 0xff;
+
+ // Iterate through the list of capabilities
+ while (eecp >= 0x40) {
+ uint32_t cap = cdi_pci_config_readl(pci_dev, eecp);
+
+ // Look for USBLEGSUP
+ if ((cap & 0xff) == 0x01) {
+ // Set OS semaphore
+ cdi_pci_config_writeb(pci_dev, eecp + 3, 1);
+
+ // Wait for BIOS semaphore to get unset
+ int timeout_counter = 0;
+ while ((cdi_pci_config_readb(pci_dev, eecp + 2) & 1) &&
+ ++timeout_counter < 1000)
+ {
+ cdi_sleep_ms(1);
+ }
+ if (timeout_counter >= 1000) {
+ return false;
+ }
+ }
+
+ eecp = (cap >> 8) & 0xff;
+ }
+
+ return true;
+}
+
+
+static void irq_handler(struct cdi_device *dev)
+{
+ struct cdi_usb_hc *cdi_hc = CDI_UPCAST(dev, dev, struct cdi_usb_hc);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, hc, struct ehci);
+
+ uint32_t usbsts = hc->regs->usbsts;
+ mem_barrier();
+ hc->regs->usbsts = usbsts;
+
+ // async advance doorbell
+ if (!(usbsts & (1 << 5))) {
+ return;
+ }
+
+ ehci_fat_qh_t *rqh;
+ while ((rqh = cdi_list_pop(hc->retired_qhs))) {
+ cdi_mem_free(rqh->cdi_mem_area);
+ }
+}
+
+
+void ehci_rh_port_down(struct cdi_usb_hub *hub, int index)
+{
+ struct cdi_usb_hc *cdi_hc = CDI_UPCAST(hub, rh, struct cdi_usb_hc);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, hc, struct ehci);
+
+ hc->regs->portsc[index] &= ~EHCI_PSC_PED;
+
+ mem_barrier();
+}
+
+
+void ehci_rh_port_up(struct cdi_usb_hub *hub, int index)
+{
+ struct cdi_usb_hc *cdi_hc = CDI_UPCAST(hub, rh, struct cdi_usb_hc);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, hc, struct ehci);
+ uint32_t portsc = hc->regs->portsc[index];
+
+ if ((portsc & EHCI_PSC_LS_MASK) == EHCI_PSC_LS_K) {
+ // Low-speed device, release ownership
+ hc->regs->portsc[index] = portsc | EHCI_PSC_PO;
+ mem_barrier();
+ return;
+ }
+
+ // Assert reset signal
+ hc->regs->portsc[index] = ((portsc & ~(EHCI_PSC_PO | EHCI_PSC_SUS |
+ EHCI_PSC_FPR | EHCI_PSC_PED))
+ | EHCI_PSC_RES);
+ mem_barrier();
+ cdi_sleep_ms(50);
+
+ // De-assert reset signal
+ int timeout_counter = 0;
+ hc->regs->portsc[index] = portsc & ~EHCI_PSC_RES;
+ do {
+ portsc = hc->regs->portsc[index];
+ cdi_sleep_ms(1);
+ } while ((portsc & EHCI_PSC_RES) && ++timeout_counter < 50);
+
+ if (portsc & EHCI_PSC_RES) {
+ // Disable port, route on to companion controller
+ // (maybe it can do at least something with it)
+ hc->regs->portsc[index] = (portsc & ~EHCI_PSC_PED) | EHCI_PSC_PO;
+ } else if ((portsc & (EHCI_PSC_PED | EHCI_PSC_CCS)) == EHCI_PSC_CCS) {
+ // Device connected but port disabled:
+ // Full-speed device, release ownership
+ hc->regs->portsc[index] = portsc | EHCI_PSC_PO;
+ }
+
+ mem_barrier();
+}
+
+
+cdi_usb_port_status_t ehci_rh_port_status(struct cdi_usb_hub *hub, int index)
+{
+ struct cdi_usb_hc *cdi_hc = CDI_UPCAST(hub, rh, struct cdi_usb_hc);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, hc, struct ehci);
+ uint32_t portsc = hc->regs->portsc[index];
+ cdi_usb_port_status_t status = 0;
+
+ if ((portsc & (EHCI_PSC_PED | EHCI_PSC_CCS)) ==
+ (EHCI_PSC_PED | EHCI_PSC_CCS))
+ {
+ // Device connected and port enabled
+ status |= CDI_USB_PORT_CONNECTED | CDI_USB_HIGH_SPEED;
+ }
+
+ return status;
+}
+
+
+static void enter_async_qh(struct ehci *hc, ehci_fat_qh_t *qh)
+{
+ // Enter the new QH as last
+
+ qh->next = hc->async_start;
+ qh->previous = hc->async_start->previous;
+
+ qh->previous->next = qh;
+ hc->async_start->previous = qh;
+
+ qh->qh.next_qh = hc->async_start->paddr | EHCI_QH;
+ mem_barrier();
+
+ qh->previous->qh.next_qh = qh->paddr | EHCI_QH;
+ mem_barrier();
+}
+
+
+static void retire_qh(struct ehci *hc, ehci_fat_qh_t *qh)
+{
+ qh->previous->qh.next_qh = qh->qh.next_qh;
+ mem_barrier();
+
+ qh->previous->next = qh->next;
+ qh->next->previous = qh->previous;
+
+ cdi_list_push(hc->retired_qhs, qh);
+ // Request async advance doorbell
+ hc->regs->usbcmd |= (1 << 6);
+}
+
+
+static void retire_qtd(ehci_fat_qtd_t *qtd)
+{
+ ehci_fat_qh_t *qh = qtd->qh;
+
+ if (qtd->qtd.status & 0x7c) {
+ if (qtd->qtd.status & (1 << 4)) {
+ *qtd->result = CDI_USB_BABBLE;
+ } else if (qtd->qtd.status & (1 << 3)) {
+ *qtd->result = CDI_USB_BAD_RESPONSE;
+ } else if (qtd->qtd.status & ((1 << 5) | (1 << 2))) {
+ *qtd->result = CDI_USB_HC_ERROR;
+ } else if (qtd->qtd.status & (1 << 6)) {
+ *qtd->result = CDI_USB_STALL;
+ } else {
+ *qtd->result = CDI_USB_HC_ERROR;
+ }
+ }
+
+ if (qtd->write_back) {
+ size_t rem = qtd->write_back_size;
+ for (int i = 0; i < 5 && rem; i++) {
+ size_t sz = rem > 4096 ? 4096 : rem;
+ memcpy((void *)((uintptr_t)qtd->write_back + i * 4096),
+ qtd->buffers[i]->vaddr, sz);
+ rem -= sz;
+ }
+ assert(!rem);
+ }
+
+ qh->first_qtd = qtd->next;
+ if (qh->last_qtd == qtd) {
+ qh->last_qtd = NULL;
+ }
+
+ for (int i = 0; i < 5; i++) {
+ if (qtd->buffers[i]) {
+ cdi_mem_free(qtd->buffers[i]);
+ }
+ }
+
+ cdi_mem_free(qtd->cdi_mem_area);
+}
+
+
+static void enter_qtd(ehci_fat_qh_t *qh, ehci_fat_qtd_t *qtd)
+{
+ bool first;
+
+ qtd->qh = qh;
+ qtd->qtd.next_qtd = EHCI_T;
+ qtd->qtd.alt_next_qtd = EHCI_T;
+ mem_barrier();
+
+ while (qh->first_qtd) {
+ /* Break if an active qTD is encountered */
+ if (qh->first_qtd->qtd.status & (1 << 7)) {
+ break;
+ }
+
+ retire_qtd(qh->first_qtd);
+ }
+
+ first = !qh->first_qtd;
+
+ // Note: Race condition (a qTD might retire here and we are thus appending
+ // the one to be queued to an inactive qTD); it's fine as long as we are
+ // constantly rescanning the queue (here and in ehci_wait_transaction())
+
+ if (first) {
+ qh->first_qtd = qtd;
+ } else {
+ qh->last_qtd->next = qtd;
+ }
+ qh->last_qtd = qtd;
+
+ if (!first) {
+ qh->last_qtd->qtd.next_qtd = qtd->paddr;
+ mem_barrier();
+ }
+
+ // If the QH is not active, it's done with its queue of qTDs and we should
+ // continue from this qTD
+ if (!(qh->qh.overlay.status & (1 << 7))) {
+ qh->qh.overlay.next_qtd = qh->first_qtd->paddr;
+ mem_barrier();
+ }
+}
+
+
+cdi_usb_hc_transaction_t ehci_create_transaction(struct cdi_usb_hc *cdi_hc,
+ const struct cdi_usb_hc_ep_info *target)
+{
+ struct ehci *hc = CDI_UPCAST(cdi_hc, hc, struct ehci);
+ struct cdi_mem_area *qh_mem;
+
+ qh_mem = cdi_mem_alloc(sizeof(ehci_fat_qh_t), 6
+ | CDI_MEM_PHYS_CONTIGUOUS
+ | CDI_MEM_DMA_4G
+ | CDI_MEM_NOINIT);
+ ehci_fat_qh_t *qh = qh_mem->vaddr;
+
+ memset(qh, 0, sizeof(*qh));
+
+ int non_high_speed_control = target->speed != CDI_USB_HIGH_SPEED
+ && target->ep_type == CDI_USB_CONTROL;
+
+ qh->qh.ep_state[0] = target->dev | (target->ep << 8) | (1 << 14)
+ | (target->mps << 16) | (non_high_speed_control << 27);
+ switch (target->speed) {
+ case CDI_USB_LOW_SPEED: qh->qh.ep_state[0] |= (1 << 12); break;
+ case CDI_USB_FULL_SPEED: qh->qh.ep_state[0] |= (0 << 12); break;
+ case CDI_USB_HIGH_SPEED: qh->qh.ep_state[0] |= (2 << 12); break;
+ case CDI_USB_SUPERSPEED:
+ default:
+ abort();
+ }
+
+ qh->qh.ep_state[1] = (0xff << 8) | (1 << 30) | (target->tt_addr << 16)
+ | ((target->tt_port + 1) << 23);
+
+ qh->qh.overlay.next_qtd = EHCI_T;
+ qh->qh.overlay.alt_next_qtd = EHCI_T;
+
+ qh->cdi_mem_area = qh_mem;
+ qh->paddr = qh_mem->paddr.items[0].start + offsetof(ehci_fat_qh_t, qh);
+
+ mem_barrier();
+
+ enter_async_qh(hc, qh);
+
+ return qh;
+}
+
+
+static void create_qtd(ehci_fat_qh_t *qh, cdi_usb_transfer_token_t token,
+ int toggle, void *buffer, size_t size,
+ cdi_usb_transmission_result_t *result)
+{
+ struct cdi_mem_area *qtd_mem;
+
+ qtd_mem = cdi_mem_alloc(sizeof(ehci_fat_qtd_t), 6
+ | CDI_MEM_PHYS_CONTIGUOUS
+ | CDI_MEM_DMA_4G
+ | CDI_MEM_NOINIT);
+ ehci_fat_qtd_t *qtd = qtd_mem->vaddr;
+
+ memset(qtd, 0, sizeof(*qtd));
+
+ qtd->qtd.status = (1 << 7) | (3 << 10) | (size << 16)
+ | ((uint32_t)toggle << 31);
+
+ switch (token) {
+ case CDI_USB_IN: qtd->qtd.status |= (1 << 8); break;
+ case CDI_USB_OUT: qtd->qtd.status |= (0 << 8); break;
+ case CDI_USB_SETUP: qtd->qtd.status |= (2 << 8); break;
+ default:
+ abort();
+ }
+
+ if (token == CDI_USB_IN) {
+ qtd->write_back = buffer;
+ qtd->write_back_size = size;
+ }
+
+ for (int i = 0; i < 5 && size; i++) {
+ size_t sz = size > 4096 ? 4096 : size;
+
+ qtd->buffers[i] = cdi_mem_alloc(4096, 12
+ | CDI_MEM_PHYS_CONTIGUOUS
+ | CDI_MEM_DMA_4G
+ | CDI_MEM_NOINIT);
+ qtd->qtd.buffers[i] = qtd->buffers[i]->paddr.items[0].start;
+
+ if (token == CDI_USB_OUT || token == CDI_USB_SETUP) {
+ memcpy(qtd->buffers[i]->vaddr,
+ (void *)((uintptr_t)buffer + i * 4096), sz);
+ }
+
+ size -= sz;
+ }
+
+ qtd->cdi_mem_area = qtd_mem;
+ qtd->paddr = qtd_mem->paddr.items[0].start + offsetof(ehci_fat_qtd_t, qtd);
+
+ qtd->result = result;
+
+ mem_barrier();
+
+ enter_qtd(qh, qtd);
+}
+
+
+void ehci_enqueue(struct cdi_usb_hc *cdi_hc,
+ const struct cdi_usb_hc_transmission *trans)
+{
+ (void)cdi_hc;
+
+ ehci_fat_qh_t *qh = trans->ta;
+ bool first = true;
+ size_t sz = trans->size;
+ void *buf = trans->buffer;
+ int toggle = trans->toggle;
+ size_t mps = (qh->qh.ep_state[0] >> 16) & 0x7ff;
+
+ *trans->result = CDI_USB_OK;
+
+ while (sz || first) {
+ first = false;
+
+ size_t iter_sz = sz > 4096 * 5 ? 4096 * 5 : sz;
+
+ create_qtd(qh, trans->token, toggle, buf, iter_sz, trans->result);
+
+ buf = (void *)((uintptr_t)buf + iter_sz);
+ sz -= iter_sz;
+
+ toggle ^= ((iter_sz + mps - 1) / mps) & 1;
+ }
+}
+
+
+void ehci_wait_transaction(struct cdi_usb_hc *cdi_hc,
+ cdi_usb_hc_transaction_t ta)
+{
+ struct ehci *hc = CDI_UPCAST(cdi_hc, hc, struct ehci);
+ ehci_fat_qh_t *qh = ta;
+ bool had_timeout = false;
+
+ irq_handler(&cdi_hc->dev);
+
+ while (qh->first_qtd) {
+ if (!had_timeout) {
+ if (!(qh->qh.overlay.status & (1 << 7))) {
+ qh->qh.overlay.next_qtd = qh->first_qtd->paddr;
+ mem_barrier();
+ }
+
+ int frame_counter = 0, lframe = (hc->regs->frindex >> 3) % 1024;
+
+ while ((qh->first_qtd->qtd.status & (1 << 7)) &&
+ frame_counter < EHCI_TIMEOUT)
+ {
+ int cframe = (hc->regs->frindex >> 3) % 1024;
+ frame_counter += (cframe + 1024 - lframe) % 1024;
+ lframe = cframe;
+
+ // I don't really know who's to blame for this (qemu or
+ // týndur), and actually, I don't even want to know.
+ // As a matter of fact, qemu will not work on the async
+ // schedule if the async advance doorbell is set but the driver
+ // has not acknowledged this; however, the interrupt handler
+ // will always clear that bit and the interrupt handler is
+ // active all the time, so something is apparently sometimes
+ // blocking the handler from being executed. Force it here to
+ // prevent qemu's HC from stalling.
+ irq_handler(&cdi_hc->dev);
+ }
+
+ had_timeout = frame_counter >= EHCI_TIMEOUT;
+ }
+
+ if (had_timeout) {
+ // Stop this queue
+ qh->qh.overlay.next_qtd = EHCI_T;
+ qh->qh.overlay.status &= ~(1u << 7);
+
+ *qh->first_qtd->result = CDI_USB_TIMEOUT;
+ }
+
+ retire_qtd(qh->first_qtd);
+ }
+}
+
+
+void ehci_destroy_transaction(struct cdi_usb_hc *cdi_hc,
+ cdi_usb_hc_transaction_t ta)
+{
+ struct ehci *hc = CDI_UPCAST(cdi_hc, hc, struct ehci);
+ ehci_fat_qh_t *qh = ta;
+
+ // TODO: Does this work without having called ehci_wait_transaction()
+ // before?
+ qh->qh.overlay.next_qtd = EHCI_T;
+
+ int frame_counter = 0, lframe = (hc->regs->frindex >> 3) % 1024;
+
+ while ((qh->qh.overlay.status & (1 << 7)) && frame_counter < EHCI_TIMEOUT) {
+ int cframe = (hc->regs->frindex >> 3) % 1024;
+ frame_counter += (cframe + 1024 - lframe) % 1024;
+ lframe = cframe;
+
+ // See above.
+ irq_handler(&cdi_hc->dev);
+ }
+
+ if (frame_counter >= EHCI_TIMEOUT) {
+ qh->qh.overlay.status &= ~(1 << 7);
+ // Could probably be solved more elegantly, but we are in a timeout
+ // already, so waiting a bit longer will not be that big of a problem
+ // (the sleep is here to ensure that the HC is no longer trying to work
+ // on the qTDs belonging to this QH)
+ cdi_sleep_ms(100);
+ }
+
+ while (qh->first_qtd) {
+ retire_qtd(qh->first_qtd);
+ }
+ retire_qh(hc, qh);
+}
diff --git a/ehci/ehci.h b/ehci/ehci.h
new file mode 100644
index 0000000..d1aa99a
--- /dev/null
+++ b/ehci/ehci.h
@@ -0,0 +1,170 @@
+/******************************************************************************
+ * Copyright (c) 2015 Max Reitz *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction, including without limitation *
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
+ * and/or sell copies of the Software, and to permit persons to whom the *
+ * Software is furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
+ * DEALINGS IN THE SOFTWARE. *
+ ******************************************************************************/
+
+#ifndef EHCI_H
+#define EHCI_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cdi/lists.h>
+#include <cdi/mem.h>
+#include <cdi/usb.h>
+#include <cdi/usb_hcd.h>
+
+
+struct ehci_registers {
+ uint32_t usbcmd;
+ uint32_t usbsts;
+ uint32_t usbintr;
+ uint32_t frindex;
+ uint32_t ctrldssegment;
+ uint32_t periodiclistbase;
+ uint32_t asynclistaddr;
+ uint8_t reserved[0x24];
+ uint32_t configflag;
+ uint32_t portsc[];
+} __attribute__((packed));
+
+struct ehci_capabilities {
+ uint8_t caplength;
+ uint8_t reserved;
+ uint16_t hciversion;
+ uint32_t hcsparams;
+ uint32_t hccparams;
+ uint64_t hcsp_portroute;
+} __attribute__((packed));
+
+struct ehci_qtd {
+ uint32_t next_qtd;
+ uint32_t alt_next_qtd;
+ uint32_t status;
+ uint32_t buffers[5];
+
+ // 64-bit structures
+ uint32_t ext_buffer[5];
+} __attribute__((packed));
+
+struct ehci_qh {
+ uint32_t next_qh;
+ uint32_t ep_state[2];
+ uint32_t current_qtd;
+ struct ehci_qtd overlay;
+} __attribute__((packed));
+
+typedef struct ehci_fat_qh ehci_fat_qh_t;
+typedef struct ehci_fat_qtd ehci_fat_qtd_t;
+
+#define DIFF_TO_MULTIPLE(value, divisor) \
+ ((divisor) - ((((value) + (divisor) - 1) % (divisor)) + 1))
+
+struct ehci_fat_qh {
+ volatile struct ehci_qh qh;
+
+ // Align the QH to 128 bytes (something with cache lines?). Otherwise,
+ // there may be conflicts between data we are writing to our very own
+ // fields provided by ehci_fat_qh_t and writes done by the HC to this
+ // structure.
+ uint8_t alignment[DIFF_TO_MULTIPLE(sizeof(struct ehci_qh), 128)];
+
+ struct cdi_mem_area *cdi_mem_area;
+ uintptr_t paddr;
+
+ ehci_fat_qh_t *next, *previous;
+ ehci_fat_qtd_t *first_qtd, *last_qtd;
+};
+
+struct ehci_fat_qtd {
+ volatile struct ehci_qtd qtd;
+
+ // Align the qTD to a multiple of 128 bytes.
+ uint8_t alignment[DIFF_TO_MULTIPLE(sizeof(struct ehci_qtd), 128)];
+
+ struct cdi_mem_area *cdi_mem_area;
+ uintptr_t paddr;
+
+ ehci_fat_qh_t *qh;
+ ehci_fat_qtd_t *next;
+
+ struct cdi_mem_area *buffers[5];
+
+ void *write_back;
+ size_t write_back_size;
+
+ cdi_usb_transmission_result_t *result;
+};
+
+struct ehci {
+ struct cdi_usb_hc hc;
+
+ volatile struct ehci_capabilities *caps;
+ volatile struct ehci_registers *regs;
+
+ struct cdi_mem_area *periodic_list_mem;
+ volatile uint32_t *periodic_list;
+
+ ehci_fat_qh_t *async_start;
+ cdi_list_t retired_qhs;
+};
+
+#define EHCI_T (1u << 0)
+#define EHCI_ITD (0u << 1)
+#define EHCI_QH (1u << 1)
+#define EHCI_SITD (2u << 1)
+#define EHCI_FSTN (3u << 1)
+
+#define EHCI_PSC_CCS (1u << 0) // current connect status
+#define EHCI_PSC_PED (1u << 2) // port enable/disable
+#define EHCI_PSC_FPR (1u << 6) // force port resume
+#define EHCI_PSC_SUS (1u << 7) // suspend
+#define EHCI_PSC_RES (1u << 8) // reset
+#define EHCI_PSC_PP (1u << 12) // port power
+#define EHCI_PSC_PO (1u << 13) // port owner
+
+#define EHCI_PSC_LS_MASK (0x3u << 10)
+#define EHCI_PSC_LS_SE0 (0x0u << 10)
+#define EHCI_PSC_LS_K (0x1u << 10)
+#define EHCI_PSC_LS_J (0x2u << 10)
+
+
+static inline void mem_barrier(void)
+{
+ __asm__ __volatile("" ::: "memory");
+}
+
+
+struct cdi_device *ehci_init_device(struct cdi_bus_data *bus_data);
+
+void ehci_rh_port_down(struct cdi_usb_hub *hub, int index);
+void ehci_rh_port_up(struct cdi_usb_hub *hub, int index);
+cdi_usb_port_status_t ehci_rh_port_status(struct cdi_usb_hub *hub, int index);
+
+cdi_usb_hc_transaction_t
+ ehci_create_transaction(struct cdi_usb_hc *hc,
+ const struct cdi_usb_hc_ep_info *target);
+void ehci_enqueue(struct cdi_usb_hc *hc,
+ const struct cdi_usb_hc_transmission *trans);
+void ehci_wait_transaction(struct cdi_usb_hc *hc, cdi_usb_hc_transaction_t ta);
+void ehci_destroy_transaction(struct cdi_usb_hc *hc,
+ cdi_usb_hc_transaction_t ta);
+
+#endif
diff --git a/ehci/main.c b/ehci/main.c
new file mode 100644
index 0000000..62939b2
--- /dev/null
+++ b/ehci/main.c
@@ -0,0 +1,68 @@
+/******************************************************************************
+ * Copyright (c) 2015 Max Reitz *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction, including without limitation *
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
+ * and/or sell copies of the Software, and to permit persons to whom the *
+ * Software is furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
+ * DEALINGS IN THE SOFTWARE. *
+ ******************************************************************************/
+
+#include <cdi.h>
+#include <cdi/usb_hcd.h>
+
+#include "ehci.h"
+
+
+#define DRIVER_NAME "ehci"
+
+
+static struct cdi_usb_hcd ehcd;
+
+static int ehcd_init(void)
+{
+ cdi_driver_init(&ehcd.drv);
+ return 0;
+}
+
+static int ehcd_destroy(void)
+{
+ cdi_driver_destroy(&ehcd.drv);
+ return 0;
+}
+
+static struct cdi_usb_hcd ehcd = {
+ .drv = {
+ .name = DRIVER_NAME,
+ .type = CDI_USB_HCD,
+ .bus = CDI_PCI,
+ .init = ehcd_init,
+ .destroy = ehcd_destroy,
+ .init_device = ehci_init_device,
+ },
+
+ .rh_drv = {
+ .port_disable = ehci_rh_port_down,
+ .port_reset_enable = ehci_rh_port_up,
+ .port_status = ehci_rh_port_status,
+ },
+
+ .create_transaction = ehci_create_transaction,
+ .enqueue = ehci_enqueue,
+ .wait_transaction = ehci_wait_transaction,
+ .destroy_transaction = ehci_destroy_transaction,
+};
+
+CDI_DRIVER(DRIVER_NAME, ehcd)
--
2.3.5