From 2c407d69f27abb4daa3f92467f8b431f77655e95 Mon Sep 17 00:00:00 2001 From: Adam Greloch Date: Fri, 20 Dec 2024 12:25:08 +0100 Subject: [PATCH] usb/ehci: adapt hcd driver to ia32 hw The root hub is now declared with bDeviceProtocol 0 instead of 1 to differentiate between root hubs and real TT layer 2 hubs in the usb stack JIRA: RTOS-937 --- usb/ehci/ehci-hub.c | 39 +++++++-- usb/ehci/ehci.c | 166 ++++++++++++++++++++++++++++++------ usb/ehci/ehci.h | 38 ++++++++- usb/ehci/phy-ia32-generic.c | 24 +++++- 4 files changed, 230 insertions(+), 37 deletions(-) diff --git a/usb/ehci/ehci-hub.c b/usb/ehci/ehci-hub.c index ca8235d7..53dc922f 100644 --- a/usb/ehci/ehci-hub.c +++ b/usb/ehci/ehci-hub.c @@ -39,7 +39,7 @@ static const struct { .bcdUSB = 0x0200, .bDeviceClass = USB_CLASS_HUB, .bDeviceSubClass = 0, - .bDeviceProtocol = 1, /* Single TT */ + .bDeviceProtocol = 0, /* Root hub */ .bMaxPacketSize0 = 64, .idVendor = 0x0, .idProduct = 0x0, @@ -88,9 +88,13 @@ static void ehci_resetPort(hcd_t *hcd, int port) { ehci_t *ehci = (ehci_t *)hcd->priv; volatile int *reg = (ehci->opbase + portsc1) + (port - 1); + int tmp; - *reg &= ~PORTSC_ENA; - *reg |= PORTSC_PR; + log_debug("resetting port %d", port); + + tmp = *reg; + tmp &= ~(PORTSC_ENA | PORTSC_PR); + *reg = tmp | PORTSC_PR; #ifdef EHCI_IMX /* @@ -101,10 +105,27 @@ static void ehci_resetPort(hcd_t *hcd, int port) ; usleep(20 * 1000); #else + /* Wait for reset to complete */ + usleep(50 * 1000); + + /* Stop the reset sequence */ + *reg = tmp; + + /* Wait until reset sequence stops */ + while ((*reg & PORTSC_PR) != 0) + ; + usleep(20 * 1000); - *reg &= ~PORTSC_PR; #endif + tmp = *reg; + + log_debug("port %d reset done, status after reset=%x", port, tmp); + + if ((tmp & PORTSC_ENA) == 0) { + log_debug("device on port %d is not a highspeed device", port); + } + ehci->portResetChange = 1 << port; if ((*reg & PORTSC_PSPD) == PORTSC_PSPD_HS) @@ -158,10 +179,16 @@ static int ehci_getPortStatus(usb_dev_t *hub, int port, usb_port_status_t *statu if (ehci->portResetChange & (1 << port)) status->wPortChange |= USB_PORT_STAT_C_RESET; +#ifdef EHCI_IMX if ((val & PORTSC_PSPD) >> 26 == 1) status->wPortStatus |= USB_PORT_STAT_LOW_SPEED; else if ((val & PORTSC_PSPD) >> 26 == 2) status->wPortStatus |= USB_PORT_STAT_HIGH_SPEED; +#endif + + /* TODO handle low/full speed devices on ia32 */ + + status->wPortStatus |= USB_PORT_STAT_HIGH_SPEED; /* TODO: set indicator */ @@ -334,11 +361,13 @@ uint32_t ehci_getHubStatus(usb_dev_t *hub) ehci_t *ehci = (ehci_t *)hcd->priv; for (i = 0; i < hub->nports; i++) { - val = ehci->portsc; + val = *(ehci->opbase + portsc1 + i); + log_debug("(INT%d) port %d portsc: %x", hcd->info->irq, i + 1, val); if (val & (PORTSC_CSC | PORTSC_PEC | PORTSC_OCC)) status |= 1 << (i + 1); } + log_debug("(INT%d): status: %x", hcd->info->irq, status); return status; } diff --git a/usb/ehci/ehci.c b/usb/ehci/ehci.c index cec6227e..8edda990 100644 --- a/usb/ehci/ehci.c +++ b/usb/ehci/ehci.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -164,6 +165,34 @@ static void ehci_qtdsPut(ehci_t *ehci, ehci_qtd_t **head) } +static void +ehci_qtdDump(ehci_qtd_t *qtd, bool dump_bufs) +{ +#if EHCI_DEBUG_QTD + uint32_t s; + + s = qtd->hw->token; + fprintf(stderr, "sts=0x%08x: tog=%d sz=0x%x ioc=%d", + s, s >> 31, (s >> 16) & 0x7fff, + (s >> 15) & 0b1); + fprintf(stderr, " cerr=%d pid=%d %s%s%s%s%s%s%s%s\n", + (s >> 10) & 0b11, (s >> 8) & 0b11, + (s & QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE", + (s & QTD_HALTED) ? "-HALTED" : "", + (s & QTD_BUFERR) ? "-BUFERR" : "", + (s & QTD_BABBLE) ? "-BABBLE" : "", + (s & QTD_XACT) ? "-XACT" : "", + (s & QTD_MISSED_UFRAME) ? "-MISSED" : "", + (s & QTD_SPLIT) ? "-SPLIT" : "", + (s & QTD_PING) ? "-PING" : ""); + + for (s = 0; dump_bufs && s < 5; s++) { + fprintf(stderr, " buf[%d]=0x%08x buf_hi[%d]=0x%08x\n", s, qtd->hw->buf[s], s, qtd->hw->buf_hi[s]); + } +#endif +} + + static ehci_qtd_t *ehci_qtdAlloc(ehci_t *ehci, int pid, size_t maxpacksz, char *data, size_t *size, int datax) { ehci_qtd_t *qtd; @@ -189,12 +218,16 @@ static ehci_qtd_t *ehci_qtdAlloc(ehci_t *ehci, int pid, size_t maxpacksz, char * if (data != NULL) { qtd->hw->buf[0] = (uintptr_t)va2pa(data); + qtd->hw->buf_hi[0] = 0; + offs = min(EHCI_PAGE_SIZE - QTD_OFFSET(qtd->hw->buf[0]), *size); bytes += offs; data += offs; for (i = 1; i < 5 && bytes != *size; i++) { qtd->hw->buf[i] = va2pa(data) & ~0xfff; + qtd->hw->buf_hi[i] = 0; + offs = min(*size - bytes, EHCI_PAGE_SIZE); /* If the data does not fit one qtd, don't leave a trailing short packet */ if (i == 4 && bytes + offs < *size) @@ -204,6 +237,11 @@ static ehci_qtd_t *ehci_qtdAlloc(ehci_t *ehci, int pid, size_t maxpacksz, char * data += offs; } + for (; i < 5; i++) { + qtd->hw->buf[i] = 0; + qtd->hw->buf_hi[i] = 0; + } + qtd->hw->token |= bytes << 16; *size -= bytes; } @@ -252,6 +290,7 @@ static void ehci_qhPut(ehci_t *ehci, ehci_qh_t *qh) static ehci_qh_t *ehci_qhAlloc(ehci_t *ehci) { ehci_qh_t *qh; + int i; if ((qh = ehci_qhGet(ehci)) == NULL) { if ((qh = malloc(sizeof(ehci_qh_t))) == NULL) @@ -278,6 +317,11 @@ static ehci_qh_t *ehci_qhAlloc(ehci_t *ehci) qh->phase = 0; qh->lastQtd = NULL; + for (i = 0; i < 5; i++) { + qh->hw->buf[i] = 0; + qh->hw->buf_hi[i] = 0; + } + return qh; } @@ -500,7 +544,7 @@ static int ehci_irqHandler(unsigned int n, void *data) currentStatus = *(ehci->opbase + usbsts); do { - *(ehci->opbase + usbsts) = currentStatus & EHCI_INTRMASK; + *(ehci->opbase + usbsts) = currentStatus & (EHCI_INTRMASK | USBSTS_FRI); ehci->status |= currentStatus; @@ -509,9 +553,6 @@ static int ehci_irqHandler(unsigned int n, void *data) currentStatus = *(ehci->opbase + usbsts); } while ((currentStatus & EHCI_INTRMASK) != 0); - if (ehci->status & USBSTS_PCI) - ehci->portsc = *(ehci->opbase + portsc1); - return -!(ehci->status & EHCI_INTRMASK); } @@ -524,8 +565,10 @@ static int ehci_qtdsCheck(hcd_t *hcd, usb_transfer_t *t, int *status) *status = 0; do { - if (qtds->hw->token & (QTD_XACT | QTD_BABBLE | QTD_BUFERR)) + ehci_qtdDump(qtds, false); + if (qtds->hw->token & (QTD_XACT | QTD_BABBLE | QTD_BUFERR | QTD_HALTED)) { error++; + } qtds = qtds->next; } while (qtds != t->hcdpriv); @@ -589,6 +632,29 @@ static void ehci_portStatusChanged(hcd_t *hcd) } +#if EHCI_DEBUG_IRQ +static void ehci_printIrq(hcd_t *hcd) +{ + ehci_t *ehci = (ehci_t *)hcd->priv; + static char buf[30]; + size_t i = 0; + + i += sprintf(buf, "INT%d: ", hcd->info->irq); + +#define append_to_buf(interrupt) \ + if (ehci->status & (interrupt)) { \ + i += sprintf(buf + i, #interrupt " "); \ + } + append_to_buf(USBSTS_UI); + append_to_buf(USBSTS_UEI); + append_to_buf(USBSTS_SEI); + append_to_buf(USBSTS_PCI); + + log_debug("%s", buf); +} +#endif + + static void ehci_irqThread(void *arg) { hcd_t *hcd = (hcd_t *)arg; @@ -598,13 +664,33 @@ static void ehci_irqThread(void *arg) for (;;) { condWait(ehci->irqCond, ehci->irqLock, 0); +#if EHCI_DEBUG_IRQ + ehci_printIrq(hcd); +#endif + + /* TODO: Think through how to guarantee race-free + status passing between irqHandler and irqThread. + + The irqThread must clear the handler interrupts, + since otherwise it would handle ghost interrupts + on every interrupt (irqHandler never clears ehci->status) */ + if (ehci->status & USBSTS_SEI) { + ehci->status &= ~USBSTS_SEI; + log_error("host system error, controller halted"); + /* TODO cleanup/reset after death */ + continue; + } + if (ehci->status & (USBSTS_UI | USBSTS_UEI)) { + /* TODO propagate back UEI to usb stack */ + ehci->status &= ~(USBSTS_UI | USBSTS_UEI); mutexLock(hcd->transLock); ehci_transUpdate(hcd); mutexUnlock(hcd->transLock); } if (ehci->status & USBSTS_PCI) { + ehci->status &= ~USBSTS_PCI; ehci_portStatusChanged(hcd); } } @@ -621,7 +707,7 @@ static int ehci_qtdAdd(ehci_t *ehci, ehci_qtd_t **list, int token, size_t maxpac return -ENOMEM; LIST_ADD(list, tmp); - dt = !dt; + dt = 1 - dt; } while (remaining > 0); return 0; @@ -780,21 +866,21 @@ static int ehci_init(hcd_t *hcd) { ehci_t *ehci; ehci_qh_t *qh; - int i; + int i, ret; if ((ehci = calloc(1, sizeof(ehci_t))) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); return -ENOMEM; } if ((ehci->periodicList = usb_allocAligned(EHCI_PERIODIC_SIZE * sizeof(uint32_t), EHCI_PERIODIC_ALIGN)) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if ((ehci->periodicNodes = calloc(EHCI_PERIODIC_SIZE, sizeof(ehci_qh_t *))) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } @@ -802,31 +888,31 @@ static int ehci_init(hcd_t *hcd) hcd->priv = ehci; if (phy_init(hcd) != 0) { - fprintf(stderr, "ehci: Phy init failed!\n"); + log_error("Phy init failed!"); ehci_free(ehci); return -EINVAL; } if (condCreate(&ehci->irqCond) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if (mutexCreate(&ehci->irqLock) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if (mutexCreate(&ehci->asyncLock) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } if (mutexCreate(&ehci->periodicLock) < 0) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } @@ -834,7 +920,7 @@ static int ehci_init(hcd_t *hcd) /* Initialize Async List with a dummy qh to optimize * accesses and make them safer */ if ((qh = ehci_qhAlloc(ehci)) == NULL) { - fprintf(stderr, "ehci: Out of memory!\n"); + log_error("Out of memory!"); ehci_free(ehci); return -ENOMEM; } @@ -845,15 +931,8 @@ static int ehci_init(hcd_t *hcd) for (i = 0; i < EHCI_PERIODIC_SIZE; ++i) ehci->periodicList[i] = QH_PTR_INVALID; - if (beginthread(ehci_irqThread, 2, ehci->stack, sizeof(ehci->stack), hcd) != 0) { - ehci_free(ehci); - return -ENOMEM; - } - - interrupt(hcd->info->irq, ehci_irqHandler, hcd, ehci->irqCond, &ehci->irqHandle); - if (((addr_t)hcd->base & (0x20 - 1)) != 0) { - fprintf(stderr, "ehci: USBBASE not aligned to 32 bits\n"); + log_error("USBBASE not aligned to 32 bits"); ehci_free(ehci); return -EINVAL; } @@ -872,10 +951,34 @@ static int ehci_init(hcd_t *hcd) ehci->opbase = (volatile int *)((char *)ehci->base + *(uint8_t *)(ehci->base + caplength)); #endif + log_debug("attaching handler to irq=%d", hcd->info->irq); + ret = interrupt(hcd->info->irq, ehci_irqHandler, hcd, ehci->irqCond, &ehci->irqHandle); + + if (ret < 0) { + log_error("failed to set interrupt handler"); + return ret; + } + + if (beginthread(ehci_irqThread, 2, ehci->stack, sizeof(ehci->stack), hcd) != 0) { + ehci_free(ehci); + return -ENOMEM; + } + + *(ehci->opbase + usbcmd) &= ~(USBCMD_RUN | USBCMD_IAA); + + while ((*(ehci->opbase + usbsts) & USBSTS_HCH) == 0) + ; + /* Reset controller */ + log_debug("controler hcreset"); *(ehci->opbase + usbcmd) |= USBCMD_HCRESET; while ((*(ehci->opbase + usbcmd) & USBCMD_HCRESET) != 0) ; + log_debug("controler hcreset done"); + + if ((*(ehci->base + hccparams) & HCCPARAMS_64BIT_ADDRS) != 0) { + *(ehci->opbase + ctrldssegment) = 0; + } #ifdef EHCI_IMX /* imx deviation: Set host mode */ @@ -883,7 +986,7 @@ static int ehci_init(hcd_t *hcd) #endif /* Enable interrupts */ - *(ehci->opbase + usbintr) = USBSTS_UI | USBSTS_UEI; + *(ehci->opbase + usbintr) = USBSTS_UI | USBSTS_UEI | USBSTS_SEI; /* Set periodic frame list */ *(ehci->opbase + periodiclistbase) = va2pa(ehci->periodicList); @@ -893,16 +996,25 @@ static int ehci_init(hcd_t *hcd) *(ehci->opbase + usbcmd) |= (3 << 2); #endif - /* Enable periodic scheduling, turn the controller on */ - *(ehci->opbase + usbcmd) |= USBCMD_PSE | USBCMD_RUN; + /* Turn the controller on, enable periodic scheduling */ + *(ehci->opbase + usbcmd) &= ~(USBCMD_LRESET | USBCMD_ASE); + + log_debug("run hc"); + *(ehci->opbase + usbcmd) |= (USBCMD_PSE | USBCMD_RUN); while ((*(ehci->opbase + usbsts) & (USBSTS_HCH)) != 0) ; + log_debug("run hc done"); /* Route all ports to this host controller */ *(ehci->opbase + configflag) = 1; + /* Allow for the hardware to catch up */ + usleep(50 * 1000); + ehci_startAsync(hcd); + log_debug("hc initialized"); + return 0; } diff --git a/usb/ehci/ehci.h b/usb/ehci/ehci.h index ff99f035..f492a375 100644 --- a/usb/ehci/ehci.h +++ b/usb/ehci/ehci.h @@ -29,13 +29,14 @@ #define USBSTS_UEI (1 << 1) #define USBSTS_UI (1 << 0) -#define EHCI_INTRMASK (USBSTS_PCI | USBSTS_UEI | USBSTS_UI) +#define EHCI_INTRMASK (USBSTS_SEI | USBSTS_PCI | USBSTS_UEI | USBSTS_UI) #define USBCMD_RUN (1 << 0) #define USBCMD_HCRESET (1 << 1) #define USBCMD_PSE (1 << 4) #define USBCMD_ASE (1 << 5) #define USBCMD_IAA (1 << 6) +#define USBCMD_LRESET (1 << 7) #define PORTSC_PTS_1 (3 << 30) #define PORTSC_STS (1 << 29) @@ -111,6 +112,8 @@ /* 'change' bits cleared by writing 1 */ #define PORTSC_CBITS (PORTSC_CSC | PORTSC_PEC | PORTSC_OCC) +#define HCCPARAMS_64BIT_ADDRS (1 << 0) + #define EHCI_PAGE_SIZE 4096 #define EHCI_PERIODIC_ALIGN 4096 @@ -161,12 +164,15 @@ enum { #endif /* clang-format on */ +/* TODO: buf_hi is required only on ia32 if hcd is capable of 64-bit addressing + * Shrink it on smaller targets to save memory? */ struct qtd { uint32_t next; uint32_t altnext; uint32_t token; uint32_t buf[5]; + uint32_t buf_hi[5]; }; @@ -180,6 +186,7 @@ struct qh { uint32_t altnextQtd; uint32_t token; uint32_t buf[5]; + uint32_t buf_hi[5]; }; @@ -217,7 +224,6 @@ typedef struct { handle_t irqCond, irqHandle, irqLock, asyncLock, periodicLock; volatile unsigned portResetChange; volatile unsigned status; - volatile unsigned portsc; volatile int *base; volatile int *opbase; @@ -236,4 +242,32 @@ int ehci_roothubReq(usb_dev_t *hub, usb_transfer_t *t); uint32_t ehci_getHubStatus(usb_dev_t *hub); +#define EHCI_TRACE 0 +#define EHCI_DEBUG 0 +#define EHCI_DEBUG_IRQ 0 +#define EHCI_DEBUG_QTD 0 + +#define COL_RED "\033[1;31m" +#define COL_NORMAL "\033[0m" +#define LOG_TAG "ehci: " + +#define log_error(fmt, ...) \ + do { \ + if (1) \ + fprintf(stderr, COL_RED LOG_TAG fmt "\n" COL_NORMAL, ##__VA_ARGS__); \ + } while (0) + +#define log_debug(fmt, ...) \ + do { \ + if (EHCI_DEBUG != 0) \ + fprintf(stderr, LOG_TAG fmt "\n", ##__VA_ARGS__); \ + } while (0) + +#define log_trace(fmt, ...) \ + do { \ + if (EHCI_TRACE != 0) \ + fprintf(stderr, LOG_TAG fmt "\n", ##__VA_ARGS__); \ + } while (0) + + #endif /* _USB_EHCI_H_ */ diff --git a/usb/ehci/phy-ia32-generic.c b/usb/ehci/phy-ia32-generic.c index 80715076..0e617e62 100644 --- a/usb/ehci/phy-ia32-generic.c +++ b/usb/ehci/phy-ia32-generic.c @@ -23,7 +23,8 @@ #include -#define EHCI_PROGIF (0x20) +#define EHCI_MAP_SIZE (0x1000) +#define EHCI_PROGIF (0x20) int hcd_getInfo(const hcd_info_t **info) @@ -73,7 +74,24 @@ int hcd_getInfo(const hcd_info_t **info) sprintf(hcd_info[i].type, "ehci"); hcd_info[i].hcdaddr = pctl.pci.dev.resources[0].base; - hcd_info[i].irq = pctl.pci.dev.irq; + + /* TODO replace with ACPI _PRT lookup */ + if (pctl.pci.dev.dev == 0x1a) { + if (pctl.pci.dev.pin == 1) { + hcd_info[i].irq = 16; + } + else { + hcd_info[i].irq = 18; + } + } + else if (pctl.pci.dev.dev == 0x1d) { + hcd_info[i].irq = 23; + } + else { + fprintf(stderr, "phy: choosing default irq from pci\n"); + hcd_info[i].irq = pctl.pci.dev.irq; + } + hcd_info[i].pci_devId.bus = pctl.pci.dev.bus; hcd_info[i].pci_devId.dev = pctl.pci.dev.dev; hcd_info[i].pci_devId.func = pctl.pci.dev.func; @@ -158,7 +176,7 @@ int phy_init(hcd_t *hcd) hcd->phybase = NULL; offs = hcd->info->hcdaddr % _PAGE_SIZE; - hcd->base = mmap(NULL, 2 * _PAGE_SIZE, PROT_WRITE | PROT_READ, MAP_DEVICE | MAP_PHYSMEM | MAP_ANONYMOUS, -1, hcd->info->hcdaddr - offs); + hcd->base = mmap(NULL, EHCI_MAP_SIZE, PROT_WRITE | PROT_READ, MAP_DEVICE | MAP_PHYSMEM | MAP_ANONYMOUS, -1, hcd->info->hcdaddr - offs); if (hcd->base == MAP_FAILED) { return -ENOMEM; }