From ed8084b5bbe03410357c2885ad8cb5b686ee1fdd 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 | 46 ++++++++-- usb/ehci/ehci.c | 164 +++++++++++++++++++++++++++++------- usb/ehci/ehci.h | 26 +++++- usb/ehci/phy-ia32-generic.c | 9 +- 4 files changed, 203 insertions(+), 42 deletions(-) diff --git a/usb/ehci/ehci-hub.c b/usb/ehci/ehci-hub.c index ca8235d7..5b81f5cc 100644 --- a/usb/ehci/ehci-hub.c +++ b/usb/ehci/ehci-hub.c @@ -3,8 +3,8 @@ * * ehci root hub implementation * - * Copyright 2021 Phoenix Systems - * Author: Maciej Purski + * Copyright 2021, 2024 Phoenix Systems + * Author: Maciej Purski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -39,7 +39,12 @@ static const struct { .bcdUSB = 0x0200, .bDeviceClass = USB_CLASS_HUB, .bDeviceSubClass = 0, +#ifdef EHCI_IMX + /* imx deviation: the controller has an embedded TT */ .bDeviceProtocol = 1, /* Single TT */ +#else + .bDeviceProtocol = 0, /* Root hub */ +#endif .bMaxPacketSize0 = 64, .idVendor = 0x0, .idProduct = 0x0, @@ -88,9 +93,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; + + log_debug("resetting port %d", port); - *reg &= ~PORTSC_ENA; - *reg |= PORTSC_PR; + tmp = *reg; + tmp &= ~(PORTSC_ENA | PORTSC_PR); + *reg = tmp | PORTSC_PR; #ifdef EHCI_IMX /* @@ -101,10 +110,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 +184,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 +366,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 dafcd3c5..c7c56d71 100644 --- a/usb/ehci/ehci.c +++ b/usb/ehci/ehci.c @@ -5,9 +5,9 @@ * * ehci/ehci.c * - * Copyright 2018, 2021 Phoenix Systems + * Copyright 2018, 2021, 2024 Phoenix Systems * Copyright 2007 Pawel Pisarczyk - * Author: Jan Sikorski, Maciej Purski + * Author: Jan Sikorski, Maciej Purski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -170,6 +171,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; @@ -195,12 +224,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) @@ -210,6 +243,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; } @@ -258,6 +296,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) @@ -284,6 +323,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; } @@ -506,7 +550,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; @@ -515,9 +559,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); } @@ -530,8 +571,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); @@ -595,6 +638,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; @@ -604,13 +670,29 @@ static void ehci_irqThread(void *arg) for (;;) { condWait(ehci->irqCond, ehci->irqLock, 0); +#if EHCI_DEBUG_IRQ + ehci_printIrq(hcd); +#endif + + /* The irqThread must clear the handler interrupt status, + 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)) { + 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); } } @@ -627,7 +709,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; @@ -786,21 +868,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; } @@ -808,31 +890,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; } @@ -840,7 +922,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; } @@ -851,15 +933,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, EHCI_PRIO, 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; } @@ -878,37 +953,66 @@ 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, EHCI_PRIO, 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 */ *(ehci->opbase + usbcmd) |= USBCMD_HCRESET; while ((*(ehci->opbase + usbcmd) & USBCMD_HCRESET) != 0) ; + if ((*(ehci->base + hccparams) & HCCPARAMS_64BIT_ADDRS) != 0) { + *(ehci->opbase + ctrldssegment) = 0; + } + #ifdef EHCI_IMX /* imx deviation: Set host mode */ *(ehci->opbase + usbmode) |= 3; #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); #ifdef EHCI_IMX - /* imx deviation: Set frame list size (128 bytes) */ + /* imx deviation: Set frame list size (128 elements) */ *(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); + + *(ehci->opbase + usbcmd) |= (USBCMD_PSE | USBCMD_RUN); while ((*(ehci->opbase + usbsts) & (USBSTS_HCH)) != 0) ; /* 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..8dd4e86f 100644 --- a/usb/ehci/ehci.h +++ b/usb/ehci/ehci.h @@ -3,8 +3,8 @@ * * USB EHCI host controller * - * Copyright 2021 Phoenix Systems - * Author: Maciej Purski + * Copyright 2021, 2024 Phoenix Systems + * Author: Maciej Purski, Adam Greloch * * This file is part of Phoenix-RTOS. * @@ -14,6 +14,18 @@ #ifndef _USB_EHCI_H_ #define _USB_EHCI_H_ +#define EHCI_DEBUG 0 +#define EHCI_DEBUG_IRQ 0 +#define EHCI_DEBUG_QTD 0 + +#define LOG_TAG "ehci: " + +/* clang-format off */ +#define log_msg(fmt, ...) do { fprintf(stderr, LOG_TAG fmt "\n", ##__VA_ARGS__); } while (0) +#define log_error(fmt, ...) do { log_msg("error: " fmt, ##__VA_ARGS__); } while (0) +#define log_debug(fmt, ...) do { if (EHCI_DEBUG != 0) log_msg(fmt, ##__VA_ARGS__); } while (0) +/* clang-format on */ + #define USBSTS_AS (1 << 15) #define USBSTS_PS (1 << 14) #define USBSTS_RCL (1 << 13) @@ -29,13 +41,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 +124,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 +176,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 +198,7 @@ struct qh { uint32_t altnextQtd; uint32_t token; uint32_t buf[5]; + uint32_t buf_hi[5]; }; @@ -217,7 +236,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; diff --git a/usb/ehci/phy-ia32-generic.c b/usb/ehci/phy-ia32-generic.c index 80715076..43ae2941 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,11 @@ int hcd_getInfo(const hcd_info_t **info) sprintf(hcd_info[i].type, "ehci"); hcd_info[i].hcdaddr = pctl.pci.dev.resources[0].base; + + /* TODO do ACPI _PRT lookup instead */ + 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 +163,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; }