[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[cdi-devel] [PATCH v3 08/10] 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 | 643 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ehci/ehci.h | 290 +++++++++++++++++++++++++++
ehci/main.c | 68 +++++++
3 files changed, 1001 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..7ece05f
--- /dev/null
+++ b/ehci/ehci.c
@@ -0,0 +1,643 @@
+/******************************************************************************
+ * 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 void 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 != PCI_CLASS_SERIAL_BUS_CONTROLLER ||
+ pci_dev->subclass_id != PCI_SUBCLASS_USB_HOST_CONTROLLER ||
+ pci_dev->interface_id != PCI_INTERFACE_EHCI)
+ {
+ 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();
+
+ 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);
+ break;
+ }
+ }
+ if (!hc->regs) {
+ goto fail;
+ }
+
+ bios_handoff(pci_dev, hc);
+
+ // Stop operation
+ hc->regs->usbcmd = (hc->regs->usbcmd & EHCI_CMD_RSVD) | EHCI_CMD_ITC(8);
+
+ // Wait until HCHALTED becomes true
+ CDI_CONDITION_WAIT(hc->regs->usbsts & EHCI_STS_HCH, 50);
+ if (!(hc->regs->usbsts & EHCI_STS_HCH)) {
+ goto fail;
+ }
+
+ hc->regs->usbcmd |= EHCI_CMD_HCRES; // Reset
+ CDI_CONDITION_WAIT(!(hc->regs->usbcmd & EHCI_CMD_HCRES), 50);
+ if (hc->regs->usbcmd & EHCI_CMD_HCRES) {
+ goto fail;
+ }
+
+ if (hc->caps->hccparams & EHCI_HCC_64) {
+ // 64-bit addressing capability
+ hc->regs->ctrldssegment = 0;
+ }
+
+ hc->regs->usbintr = 0;
+ hc->regs->usbsts = hc->regs->usbsts;
+
+ 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;
+ }
+ 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] = EHCI_QHES0_RL(0) | EHCI_QHES0_MPL(8)
+ | EHCI_QHES0_EP(0) | EHCI_QHES0_DEV(0)
+ | EHCI_QHES0_H | EHCI_QHES0_DTC
+ | EHCI_QHES0_EPS_HS;
+ hc->async_start->qh.ep_state[1] = EHCI_QHES1_MULT(1) | EHCI_QHES1_SCM(0xff);
+
+ 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;
+
+ // Start operation
+ hc->regs->usbcmd = (hc->regs->usbcmd & EHCI_CMD_RSVD)
+ | EHCI_CMD_ITC(8) | EHCI_CMD_ASE | EHCI_CMD_PSE
+ | EHCI_CMD_FLS_1K | EHCI_CMD_RS;
+
+ // Wait until HCHalted becomes false and the schedules are running
+ CDI_CONDITION_WAIT((hc->regs->usbsts
+ & (EHCI_STS_ASS | EHCI_STS_PSS | EHCI_STS_HCH))
+ == (EHCI_STS_ASS | EHCI_STS_PSS), 50);
+ if ((hc->regs->usbsts & (EHCI_STS_ASS | EHCI_STS_PSS | EHCI_STS_HCH))
+ != (EHCI_STS_ASS | EHCI_STS_PSS))
+ {
+ goto fail;
+ }
+
+ cdi_register_irq(pci_dev->irq, &irq_handler, &hc->hc.dev);
+
+ // async advance doorbell
+ hc->regs->usbintr = EHCI_INT_IAA;
+
+ // Route everything to this HC
+ hc->regs->configflag |= EHCI_CF_CF;
+
+ cdi_sleep_ms(5);
+
+ hc->hc.rh.ports = EHCI_HCS_N_PORTS(hc->caps->hcsparams);
+
+ // Power up all ports
+ for (int i = 0; i < hc->hc.rh.ports; i++) {
+ hc->regs->portsc[i] |= EHCI_PSC_PP;
+ }
+
+ 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);
+ }
+ cdi_list_destroy(hc->retired_qhs);
+ free((void *)hc->hc.dev.name);
+ free(hc);
+ cdi_pci_free_memory(pci_dev);
+
+ return NULL;
+}
+
+
+static void bios_handoff(struct cdi_pci_device *pci_dev, struct ehci *hc)
+{
+ int eecp = EHCI_HCC_EECP(hc->caps->hccparams);
+
+ // Iterate through the list of capabilities
+ while (eecp >= 0x40) {
+ uint32_t cap = cdi_pci_config_readl(pci_dev, eecp);
+
+ // Look for USBLEGSUP
+ if (EHCI_EEC_ID(cap) == EHCI_EECID_LEGSUP) {
+ // Set OS semaphore
+ cdi_pci_config_writeb(pci_dev, eecp + 3, 1);
+
+ // Wait for BIOS semaphore to get unset
+ CDI_CONDITION_WAIT(!(cdi_pci_config_readb(pci_dev, eecp + 2) & 1),
+ 5000);
+ if (cdi_pci_config_readb(pci_dev, eecp + 2) & 1) {
+ // Okay Max, stay calm, don't swear
+ fprintf(stderr, "[ehci] Warning: Clean BIOS handoff failed\n");
+ // Disable all SMI sources (0xff000000 is the R/WC mask of this
+ // register)
+ cdi_pci_config_writel(pci_dev, eecp + 4,
+ cdi_pci_config_readl(pci_dev, eecp + 4) & 0xff000000u);
+ // Unset BIOS semaphore
+ cdi_pci_config_writeb(pci_dev, eecp + 2, 0);
+ }
+ }
+
+ eecp = EHCI_EEC_NEXT(cap);
+ }
+}
+
+
+static void irq_handler(struct cdi_device *dev)
+{
+ struct cdi_usb_hc *cdi_hc = CDI_UPCAST(dev, struct cdi_usb_hc, dev);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, struct ehci, hc);
+
+ uint32_t usbsts = hc->regs->usbsts;
+ hc->regs->usbsts = usbsts;
+
+ // async advance doorbell
+ if (!(usbsts & EHCI_STS_IAA)) {
+ 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, struct cdi_usb_hc, rh);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, struct ehci, hc);
+
+ hc->regs->portsc[index] &= ~EHCI_PSC_PED;
+}
+
+
+void ehci_rh_port_up(struct cdi_usb_hub *hub, int index)
+{
+ struct cdi_usb_hc *cdi_hc = CDI_UPCAST(hub, struct cdi_usb_hc, rh);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, struct ehci, hc);
+ 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;
+ return;
+ }
+
+ // Assert reset signal
+ hc->regs->portsc[index] = ((portsc & ~(EHCI_PSC_PO | EHCI_PSC_SUS |
+ EHCI_PSC_FPR | EHCI_PSC_PED))
+ | EHCI_PSC_RES);
+ cdi_sleep_ms(50);
+
+ // De-assert reset signal
+ hc->regs->portsc[index] = portsc & ~EHCI_PSC_RES;
+ CDI_CONDITION_WAIT(!(hc->regs->portsc[index] & EHCI_PSC_RES), 50);
+
+ portsc = hc->regs->portsc[index];
+ 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;
+ }
+}
+
+
+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, struct cdi_usb_hc, rh);
+ struct ehci *hc = CDI_UPCAST(cdi_hc, struct ehci, hc);
+ 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;
+ qh->previous->qh.next_qh = qh->paddr | EHCI_QH;
+}
+
+
+static void retire_qh(struct ehci *hc, ehci_fat_qh_t *qh)
+{
+ qh->previous->qh.next_qh = qh->qh.next_qh;
+
+ qh->previous->next = qh->next;
+ qh->next->previous = qh->previous;
+
+ cdi_list_push(hc->retired_qhs, qh);
+ // Request async advance doorbell
+ hc->regs->usbcmd |= EHCI_CMD_IAADB;
+}
+
+
+static void retire_qtd(ehci_fat_qtd_t *qtd)
+{
+ ehci_fat_qh_t *qh = qtd->qh;
+
+ uint32_t err_mask = (qh->qh.ep_state[0] & EHCI_QHES0_EPS_MASK)
+ == EHCI_QHES0_EPS_HS
+ ? EHCI_QTDS_HS_ERR_MASK
+ : EHCI_QTDS_LFS_ERR_MASK;
+
+ if (qtd->qtd.status & err_mask) {
+ if (qtd->qtd.status & EHCI_QTDS_BABBLE) {
+ *qtd->result = CDI_USB_BABBLE;
+ } else if (qtd->qtd.status & (EHCI_QTDS_XACTERR | EHCI_QTDS_P_ERR)) {
+ *qtd->result = CDI_USB_BAD_RESPONSE;
+ } else if (qtd->qtd.status & (EHCI_QTDS_BUFERR | EHCI_QTDS_MuF)) {
+ *qtd->result = CDI_USB_HC_ERROR;
+ } else if (qtd->qtd.status & EHCI_QTDS_HALTED) {
+ *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;
+
+ while (qh->first_qtd) {
+ // Break if an active qTD is encountered
+ if (qh->first_qtd->qtd.status & EHCI_QTDS_ACTIVE) {
+ 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;
+ }
+
+ // 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 & EHCI_QTDS_ACTIVE)) {
+ qh->qh.overlay.next_qtd = qh->first_qtd->paddr;
+ }
+}
+
+
+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, struct ehci, hc);
+ 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] = EHCI_QHES0_RL(0)
+ | (non_high_speed_control ? EHCI_QHES0_C : 0)
+ | EHCI_QHES0_MPL(target->mps)
+ | EHCI_QHES0_DTC
+ | EHCI_QHES0_EP(target->ep)
+ | EHCI_QHES0_DEV(target->dev);
+ switch (target->speed) {
+ case CDI_USB_LOW_SPEED: qh->qh.ep_state[0] |= EHCI_QHES0_EPS_LS; break;
+ case CDI_USB_FULL_SPEED: qh->qh.ep_state[0] |= EHCI_QHES0_EPS_FS; break;
+ case CDI_USB_HIGH_SPEED: qh->qh.ep_state[0] |= EHCI_QHES0_EPS_HS; break;
+ case CDI_USB_SUPERSPEED:
+ default:
+ abort();
+ }
+
+ qh->qh.ep_state[1] = EHCI_QHES1_MULT(1)
+ | EHCI_QHES1_PORT(target->tt_port + 1)
+ | EHCI_QHES1_HUB(target->tt_addr)
+ | EHCI_QHES1_SCM(0xff);
+
+ 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);
+
+ 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 = EHCI_QTDS_DT(toggle)
+ | EHCI_QTDS_XFLEN(size)
+ | EHCI_QTDS_CP(0)
+ | EHCI_QTDS_CERR(3)
+ | EHCI_QTDS_ACTIVE;
+
+ switch (token) {
+ case CDI_USB_IN: qtd->qtd.status |= EHCI_QTDS_PID_IN; break;
+ case CDI_USB_OUT: qtd->qtd.status |= EHCI_QTDS_PID_OUT; break;
+ case CDI_USB_SETUP: qtd->qtd.status |= EHCI_QTDS_PID_SETUP; 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;
+
+ 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;
+ char *buf = trans->buffer;
+ int toggle = trans->toggle;
+ size_t mps = EHCI_QHES0_READ_MPL(qh->qh.ep_state[0]);
+
+ *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 += 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)
+{
+ (void)cdi_hc;
+
+ 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 & EHCI_QTDS_ACTIVE)) {
+ qh->qh.overlay.next_qtd = qh->first_qtd->paddr;
+ }
+
+ uint64_t timestamp = cdi_elapsed_ms();
+
+ while ((qh->first_qtd->qtd.status & EHCI_QTDS_ACTIVE) &&
+ cdi_elapsed_ms() - timestamp < EHCI_TIMEOUT)
+ {
+ // 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 = qh->first_qtd->qtd.status & EHCI_QTDS_ACTIVE;
+ }
+
+ if (had_timeout) {
+ // Stop this queue
+ qh->qh.overlay.next_qtd = EHCI_T;
+ qh->qh.overlay.status &= ~(uint32_t)EHCI_QTDS_ACTIVE;
+
+ *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, struct ehci, hc);
+ ehci_fat_qh_t *qh = ta;
+
+ qh->qh.overlay.next_qtd = EHCI_T;
+
+ uint64_t timestamp = cdi_elapsed_ms();
+
+ while ((qh->qh.overlay.status & EHCI_QTDS_ACTIVE) &&
+ cdi_elapsed_ms() - timestamp < EHCI_TIMEOUT)
+ {
+ // See above.
+ irq_handler(&cdi_hc->dev);
+ }
+
+ if (qh->qh.overlay.status & EHCI_QTDS_ACTIVE) {
+ qh->qh.overlay.status &= ~(uint32_t)EHCI_QTDS_ACTIVE;
+ // 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..9207170
--- /dev/null
+++ b/ehci/ehci.h
@@ -0,0 +1,290 @@
+/******************************************************************************
+ * 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 PCI_CLASS_SERIAL_BUS_CONTROLLER 0x0c
+#define PCI_SUBCLASS_USB_HOST_CONTROLLER 0x03
+#define PCI_INTERFACE_EHCI 0x20
+
+#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)
+
+// Operational Registers: USBCMD
+#define EHCI_CMD_RSVD 0xff00ff00u // Consider async sched park mode reserved
+#define EHCI_CMD_ITC(x) ((x) << 16) // Interrupt Threshold Control
+#define EHCI_CMD_LHCRES (1 << 7) // Light Host Controller Reset
+#define EHCI_CMD_IAADB (1 << 6) // Interrupt on Async Advance Doorbell
+#define EHCI_CMD_ASE (1 << 5) // Async Schedule Enable
+#define EHCI_CMD_PSE (1 << 4) // Periodic Schedule Enable
+#define EHCI_CMD_FLS_1K (0 << 2) // Frame List Size: 1024 elements (default)
+#define EHCI_CMD_HCRES (1 << 1) // Host Controller Reset
+#define EHCI_CMD_RS (1 << 0) // Run/Stop (1 = Run)
+
+// Operational Registers: USBINTR
+#define EHCI_INT_IAA (1 << 5) // Interrupt on Async Advance Enable
+#define EHCI_INT_HSE (1 << 4) // Host System Error Enable
+#define EHCI_INT_FLR (1 << 3) // Frame List Rollover Enable
+#define EHCI_INT_PCD (1 << 2) // Port Change Detect Enable
+#define EHCI_INT_USBERR (1 << 1) // USB Error Interrupt
+#define EHCI_INT_USBINT (1 << 0) // USB Interrupt
+
+// Operational Registers: USBSTS
+#define EHCI_STS_ASS (1 << 15) // Async Schedule Status
+#define EHCI_STS_PSS (1 << 14) // Periodic Schedule Status
+#define EHCI_STS_REC (1 << 13) // Reclamation bit
+#define EHCI_STS_HCH (1 << 12) // Host Controller Halted
+#define EHCI_STS_IAA EHCI_INT_IAA // Interrupt on Async Advance
+#define EHCI_STS_HSE EHCI_INT_HSE // Host System Error
+#define EHCI_STS_FLR EHCI_INT_FLR // Frame List Rollover
+#define EHCI_STS_PCD EHCI_INT_PCD // Port Change Detect
+#define EHCI_STS_USBERR EHCI_INT_USBERR // USB Error Interrupt
+#define EHCI_STS_USBINT EHCI_INT_USBINT // USB Interrupt
+
+// Operational Registers: FRINDEX
+#define EHCI_FI_FI(x) (((x) >> 3) & 0x3ff) // Frame Index
+
+// Operational Registers: CONFIGFLAG
+#define EHCI_CF_CF (1 << 0) // Configure Flag
+
+// Operational Registers: PORTSC
+#define EHCI_PSC_WKOC_E (1 << 22) // Wake on Over-current Enable
+#define EHCI_PSC_WKDSCNNT_E (1 << 21) // Wake on Disconnect Enable
+#define EHCI_PSC_WKCNNT_E (1 << 20) // Wake on Connect Enable
+#define EHCI_PSC_PTC_DIS (0 << 16) // Port Test Control: disabled
+#define EHCI_PSC_PTC_J (1 << 16) // Port Test Control: J-State
+#define EHCI_PSC_PTC_K (2 << 16) // Port Test Control: K-State
+#define EHCI_PSC_PTC_SE0 (3 << 16) // Port Test Control: SE0 Nak
+#define EHCI_PSC_PTC_PKT (4 << 16) // Port Test Control: Packet
+#define EHCI_PSC_PTC_FEN (5 << 16) // Port Test Control: Force Enable
+#define EHCI_PSC_PI_OFF (0 << 14) // Port Indicators: off
+#define EHCI_PSC_PI_AMBER (1 << 14) // Port Indicators: amber
+#define EHCI_PSC_PI_GREEN (2 << 14) // Port Indicators: green
+#define EHCI_PSC_PO (1 << 13) // Port Owner
+#define EHCI_PSC_PP (1 << 12) // Port Power
+#define EHCI_PSC_LS_MASK (3 << 10) // Line Status mask
+#define EHCI_PSC_LS_SE0 (0 << 10) // Line Status: SE0
+#define EHCI_PSC_LS_K (1 << 10) // Line Status: K-State
+#define EHCI_PSC_LS_J (2 << 10) // Line Status: J-State
+#define EHCI_PSC_RES (1 << 8) // Port Reset
+#define EHCI_PSC_SUS (1 << 7) // Suspend
+#define EHCI_PSC_FPR (1 << 6) // Force Port Resume
+#define EHCI_PSC_OCC (1 << 5) // Over-current Change
+#define EHCI_PSC_OCA (1 << 4) // Over-current Active
+#define EHCI_PSC_PEDC (1 << 3) // Port Enable/Disable Change
+#define EHCI_PSC_PED (1 << 2) // Port Enabled/Disabled
+#define EHCI_PSC_CSC (1 << 1) // Connect Status Change
+#define EHCI_PSC_CCS (1 << 0) // Current Connect Status
+
+// Capability Registers: HCSPARAMS
+#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) // Number of Companion
+ // Controllers
+#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) // Number of Ports per CC
+#define EHCI_HCS_PRR (1 << 7) // Port Routing Rules
+#define EHCI_HCS_PPC (1 << 4) // Port Power Control
+#define EHCI_HCS_N_PORTS(x) (((x) >> 0) & 0xf) // Number of Ports
+
+// Capability Registers: HCCPARAMS
+#define EHCI_HCC_EECP(x) (((x) >> 8) & 0xff) // EHCI Extended Capabilities
+ // Pointer
+#define EHCI_HCC_64 (1 << 0) // 64-bit Addressing Capability
+
+// EHCI Extended Capability
+#define EHCI_EEC_NEXT(x) (((x) >> 8) & 0xff)
+#define EHCI_EEC_ID(x) (((x) >> 0) & 0xff)
+
+#define EHCI_EECID_LEGSUP 0x01
+
+// QH.ep_state[0] (Endpoint Characteristics)
+#define EHCI_QHES0_RL(x) ((uint32_t)(x) << 28) // Nak Count Reload
+#define EHCI_QHES0_C (1 << 27) // Control Endpoint Flag
+#define EHCI_QHES0_MPL(x) ((x) << 16) // Maximum Packet Length
+#define EHCI_QHES0_READ_MPL(x) (((x) >> 16) & 0x7ff)
+#define EHCI_QHES0_H (1 << 15) // Head of Reclamation List flag
+#define EHCI_QHES0_DTC (1 << 14) // Data Toggle Control
+#define EHCI_QHES0_EPS_MASK (3 << 12) // Endpoint Speed mask
+#define EHCI_QHES0_EPS_FS (0 << 12) // Endpoint Speed: Full Speed
+#define EHCI_QHES0_EPS_LS (1 << 12) // Endpoint Speed: Low Speed
+#define EHCI_QHES0_EPS_HS (2 << 12) // Endpoint Speed: High Speed
+#define EHCI_QHES0_EP(x) ((x) << 8) // Endpoint Number
+#define EHCI_QHES0_I (1 << 7) // Inactive on Next Transaction
+#define EHCI_QHES0_DEV(x) ((x) << 0) // Device Address
+
+// QH.ep_state[1] (Endpoint Capabilities)
+#define EHCI_QHES1_MULT(x) ((uint32_t)(x) << 30) // High-Bandwidth Pipe
+ // Multiplier
+#define EHCI_QHES1_PORT(x) ((x) << 23) // Port Number
+#define EHCI_QHES1_HUB(x) ((x) << 16) // Hub Address
+#define EHCI_QHES1_SCM(x) ((x) << 8) // Split Completion Mask
+#define EHCI_QHES1_ISM(x) ((x) << 0) // Interrupt Schedule Mask
+
+// qTD.status (Token)
+#define EHCI_QTDS_DT(x) ((uint32_t)(x) << 31) // Data Toggle
+#define EHCI_QTDS_XFLEN(x) ((x) << 16) // Total Bytes to Transfer
+#define EHCI_QTDS_IOC (1 << 15) // Interrupt on Complete
+#define EHCI_QTDS_CP(x) ((x) << 12) // Current Page
+#define EHCI_QTDS_CERR(x) ((x) << 10) // Error Counter
+#define EHCI_QTDS_PID_OUT (0 << 8) // PID: OUT
+#define EHCI_QTDS_PID_IN (1 << 8) // PID: IN
+#define EHCI_QTDS_PID_SETUP (2 << 8) // PID: SETUP
+#define EHCI_QTDS_ACTIVE (1 << 7) // Status: Active
+#define EHCI_QTDS_HALTED (1 << 6) // Status: Halted
+#define EHCI_QTDS_BUFERR (1 << 5) // Status: Data Buffer Error
+#define EHCI_QTDS_BABBLE (1 << 4) // Status: Babble Detected
+#define EHCI_QTDS_XACTERR (1 << 3) // Status: Transaction Error
+#define EHCI_QTDS_MuF (1 << 2) // Status: Missed Microframe
+#define EHCI_QTDS_SPLITXS (1 << 1) // Status: Split Transaction State
+#define EHCI_QTDS_P_ERR (1 << 0) // Status: Ping State / Error
+
+// High Speed Error Mask
+#define EHCI_QTDS_HS_ERR_MASK \
+ (EHCI_QTDS_HALTED | EHCI_QTDS_BUFERR | EHCI_QTDS_BABBLE | \
+ EHCI_QTDS_XACTERR | EHCI_QTDS_MuF)
+
+// Low/Full Speed Error Mask
+#define EHCI_QTDS_LFS_ERR_MASK \
+ (EHCI_QTDS_HS_ERR_MASK | EHCI_QTDS_P_ERR)
+
+
+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.4.0