diff --git a/_targets/Makefile.ia32-generic b/_targets/Makefile.ia32-generic index b939c8ce9..579f8d7a6 100644 --- a/_targets/Makefile.ia32-generic +++ b/_targets/Makefile.ia32-generic @@ -6,4 +6,4 @@ # Copyright 2019 Phoenix Systems # -DEFAULT_COMPONENTS := pc-tty uart16550 pc-ata +DEFAULT_COMPONENTS := pc-tty uart16550 pc-ata libusbehci umass diff --git a/usb/ehci/Makefile b/usb/ehci/Makefile index 8fe36da3c..3b7cf5a0a 100644 --- a/usb/ehci/Makefile +++ b/usb/ehci/Makefile @@ -1,11 +1,15 @@ # -# Makefile for Phoenix-RTOS imx6ull-ehci +# Makefile for Phoenix-RTOS ehci # # Copyright 2018, 2019 Phoenix Systems # # FIXME: rename usb host component NAME := libusbehci -LOCAL_SRCS := ehci.c ehci-hub.c phy-$(TARGET_SUBFAMILY).c +LOCAL_SRCS := ehci.c ehci-hub.c phy-$(TARGET_FAMILY)-$(TARGET_SUBFAMILY).c + +ifneq (,$(findstring imx,$(TARGET_SUBFAMILY))) + CFLAGS += -DEHCI_IMX +endif include $(static-lib.mk) diff --git a/usb/ehci/ehci-hub.c b/usb/ehci/ehci-hub.c index df8f5cc6d..bd50c4a68 100644 --- a/usb/ehci/ehci-hub.c +++ b/usb/ehci/ehci-hub.c @@ -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, @@ -87,17 +92,44 @@ static const struct { static void ehci_resetPort(hcd_t *hcd, int port) { ehci_t *ehci = (ehci_t *)hcd->priv; - volatile int *reg = (hcd->base + portsc1) + (port - 1); + 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 /* - * This is imx deviation. According to ehci documentation + * imx deviation: According to ehci documentation * it is up to software to set the PR bit 0 after waiting 20ms */ while (*reg & PORTSC_PR) ; 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); +#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; @@ -118,7 +150,7 @@ static int ehci_getPortStatus(usb_dev_t *hub, int port, usb_port_status_t *statu status->wPortChange = 0; status->wPortStatus = 0; - val = *(hcd->base + portsc1 + port - 1); + val = *(ehci->opbase + portsc1 + port - 1); if (val & PORTSC_CCS) status->wPortStatus |= USB_PORT_STAT_CONNECTION; @@ -152,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 */ @@ -192,7 +230,7 @@ static int ehci_clearPortFeature(usb_dev_t *hub, int port, uint16_t wValue) { hcd_t *hcd = hub->hcd; ehci_t *ehci = (ehci_t *)hcd->priv; - volatile int *portsc = hcd->base + portsc1 + port - 1; + volatile int *portsc = ehci->opbase + portsc1 + port - 1; uint32_t val = *portsc; if (port > hub->nports) @@ -285,6 +323,7 @@ static int ehci_getStringDesc(usb_dev_t *hub, int index, char *buf, size_t size) static int ehci_getDesc(usb_dev_t *hub, int type, int index, char *buf, size_t size) { hcd_t *hcd = hub->hcd; + ehci_t *ehci = (ehci_t *)hcd->priv; usb_hub_desc_t *hdesc; int bytes = 0; @@ -309,7 +348,7 @@ static int ehci_getDesc(usb_dev_t *hub, int type, int index, char *buf, size_t s hdesc->wHubCharacteristics = 0x1; hdesc->bPwrOn2PwrGood = 10; hdesc->bHubContrCurrent = 10; - hdesc->bNbrPorts = *(hcd->base + hcsparams) & 0xf; + hdesc->bNbrPorts = *(ehci->base + hcsparams) & 0xf; hdesc->variable[0] = 0; /* Device not removable */ hdesc->variable[1] = 0xff; /* PortPwrCtrlMask */ break; @@ -327,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; } @@ -340,12 +381,13 @@ int ehci_roothubReq(usb_dev_t *hub, usb_transfer_t *t) { usb_setup_packet_t *setup = t->setup; int ret; + ehci_t *ehci = (ehci_t *)hub->hcd->priv; /* It will be finished, when a port status changes */ if (t->type == usb_transfer_interrupt) { /* Enable Port Status Changed interrupt if this is a first call */ - if ((*(hub->hcd->base + usbintr) & USBSTS_PCI) == 0) - *(hub->hcd->base + usbintr) |= USBSTS_PCI; + if ((*(ehci->opbase + usbintr) & USBSTS_PCI) == 0) + *(ehci->opbase + usbintr) |= USBSTS_PCI; return 0; } diff --git a/usb/ehci/ehci.c b/usb/ehci/ehci.c index 80515e535..a65d65f69 100644 --- a/usb/ehci/ehci.c +++ b/usb/ehci/ehci.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -34,12 +35,20 @@ #include "ehci.h" +#ifdef EHCI_IMX #define EHCI_PERIODIC_SIZE 128 +#else +#define EHCI_PERIODIC_SIZE 1024 +#endif static inline void ehci_memDmb(void) { - asm volatile ("dmb" ::: "memory"); +#ifdef EHCI_IMX + asm volatile("dmb" ::: "memory"); +#else + __sync_synchronize(); +#endif } @@ -47,19 +56,21 @@ static void ehci_startAsync(hcd_t *hcd) { ehci_t *ehci = (ehci_t *)hcd->priv; - *(hcd->base + asynclistaddr) = va2pa((void *)ehci->asyncList->hw); - *(hcd->base + usbcmd) |= USBCMD_ASE; + *(ehci->opbase + asynclistaddr) = va2pa((void *)ehci->asyncList->hw); + *(ehci->opbase + usbcmd) |= USBCMD_ASE; ehci_memDmb(); - while ((*(hcd->base + usbsts) & USBSTS_AS) == 0) + while ((*(ehci->opbase + usbsts) & USBSTS_AS) == 0) ; } static void ehci_stopAsync(hcd_t *hcd) { - *(hcd->base + usbcmd) &= ~USBCMD_ASE; + ehci_t *ehci = (ehci_t *)hcd->priv; + + *(ehci->opbase + usbcmd) &= ~USBCMD_ASE; ehci_memDmb(); - while ((*(hcd->base + usbsts) & USBSTS_AS) != 0) + while ((*(ehci->opbase + usbsts) & USBSTS_AS) != 0) ; } @@ -154,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; @@ -179,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) @@ -194,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; } @@ -242,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) @@ -268,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; } @@ -488,20 +542,17 @@ static int ehci_irqHandler(unsigned int n, void *data) ehci_t *ehci = (ehci_t *)hcd->priv; uint32_t currentStatus; - currentStatus = *(hcd->base + usbsts); + currentStatus = *(ehci->opbase + usbsts); do { - *(hcd->base + usbsts) = currentStatus & EHCI_INTRMASK; + *(ehci->opbase + usbsts) = currentStatus & (EHCI_INTRMASK | USBSTS_FRI); ehci->status |= currentStatus; /* For edge triggered interrupts to prevent losing interrupts, * poll the usbsts register until it is stable */ - currentStatus = *(hcd->base + usbsts); + currentStatus = *(ehci->opbase + usbsts); } while ((currentStatus & EHCI_INTRMASK) != 0); - if (ehci->status & USBSTS_PCI) - ehci->portsc = *(hcd->base + portsc1); - return -!(ehci->status & EHCI_INTRMASK); } @@ -514,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); @@ -579,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; @@ -588,14 +664,35 @@ 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) + if (ehci->status & USBSTS_PCI) { + ehci->status &= ~USBSTS_PCI; ehci_portStatusChanged(hcd); + } } } @@ -610,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; @@ -671,7 +768,7 @@ static int ehci_transferEnqueue(hcd_t *hcd, usb_transfer_t *t, usb_pipe_t *pipe) /* Data stage */ if ((t->type == usb_transfer_control && t->size > 0) || t->type == usb_transfer_bulk || - t->type == usb_transfer_interrupt) { + t->type == usb_transfer_interrupt) { if (ehci_qtdAdd(hcd->priv, &qtds, token, pipe->maxPacketLen, t->buffer, t->size, 1) < 0) { ehci_qtdsPut(hcd->priv, &qtds); t->hcdpriv = NULL; @@ -769,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; } @@ -791,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; } @@ -823,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; } @@ -834,34 +931,90 @@ static int ehci_init(hcd_t *hcd) for (i = 0; i < EHCI_PERIODIC_SIZE; ++i) ehci->periodicList[i] = QH_PTR_INVALID; + if (((addr_t)hcd->base & (0x20 - 1)) != 0) { + log_error("USBBASE not aligned to 32 bits"); + ehci_free(ehci); + return -EINVAL; + } + + /* Set USBBASE */ + ehci->base = hcd->base; + +#ifdef EHCI_IMX + /* imx deviation: Here we don't distinguish between base/opbase addresses, as + * the distance between operational register base and USBBASE is a known + * constant accounted for in the register enum already. */ + ehci->opbase = ehci->base; +#else + /* In general, EHCI states that the operational register base has address: + * USBBASE + CAPLENGTH */ + 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; } - interrupt(hcd->info->irq, ehci_irqHandler, hcd, ehci->irqCond, &ehci->irqHandle); + + *(ehci->opbase + usbcmd) &= ~(USBCMD_RUN | USBCMD_IAA); + + while ((*(ehci->opbase + usbsts) & USBSTS_HCH) == 0) + ; /* Reset controller */ - *(hcd->base + usbcmd) |= 2; - while (*(hcd->base + usbcmd) & 2) + 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; + } - /* Set host mode */ - *(hcd->base + usbmode) |= 3; +#ifdef EHCI_IMX + /* imx deviation: Set host mode */ + *(ehci->opbase + usbmode) |= 3; +#endif /* Enable interrupts */ - *(hcd->base + usbintr) = USBSTS_UI | USBSTS_UEI; + *(ehci->opbase + usbintr) = USBSTS_UI | USBSTS_UEI | USBSTS_SEI; /* Set periodic frame list */ - *(hcd->base + periodiclistbase) = va2pa(ehci->periodicList); + *(ehci->opbase + periodiclistbase) = va2pa(ehci->periodicList); - /* Set interrupts threshold, frame list size - 128 bytes, turn controller on */ - *(hcd->base + usbcmd) |= (1 << 4) | (3 << 2) | 1; +#ifdef EHCI_IMX + /* imx deviation: Set frame list size (128 elements) */ + *(ehci->opbase + usbcmd) |= (3 << 2); +#endif + + /* 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 */ - *(hcd->base + configflag) = 1; + *(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 e8dd87ad8..f492a375a 100644 --- a/usb/ehci/ehci.h +++ b/usb/ehci/ehci.h @@ -29,10 +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_ASE (1 << 5) -#define USBCMD_IAA (1 << 6) +#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) @@ -108,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 @@ -116,6 +122,9 @@ #define EHCI_MAX_QTD_POOL 20 #define EHCI_MAX_QH_POOL 10 + +/* clang-format off */ +#ifdef EHCI_IMX enum { /* identification regs */ id = 0x0, hwgeneral, hwhost, hwdevice, hwtxbuf, hwrxbuf, @@ -136,17 +145,34 @@ enum { endptctrl2, endptctrl3, endptctrl4, endptctrl5, endptctrl6, endptctrl7, }; - enum { usb_otg1_ctrl = 0x200, usb_otg2_ctrl, usb_otg1_phy_ctrl = usb_otg2_ctrl + 5, usb_otg2_phy_ctrl }; enum { ehci_item_itd = 0, ehci_item_qh, ehci_item_sitd, ehci_item_fstn }; +#else +enum { + /* capability regs */ + caplength = 0x0, hciversion = 0x0, hcsparams, hccparams, + hcspportroute1, hcspportroute2 /* hcspportroute is a 64-bit register */ +}; + +enum { + /* operational regs */ + usbcmd = 0x0, usbsts, usbintr, frindex, ctrldssegment, + periodiclistbase = 0x5, asynclistaddr, + configflag = 0x10, portsc1 +}; +#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]; }; @@ -160,6 +186,7 @@ struct qh { uint32_t altnextQtd; uint32_t token; uint32_t buf[5]; + uint32_t buf_hi[5]; }; @@ -184,6 +211,7 @@ typedef struct _ehci_qh { typedef struct { char stack[1024] __attribute__((aligned(8))); + uint32_t *periodicList; ehci_qh_t *asyncList; ehci_qh_t **periodicNodes; @@ -196,7 +224,9 @@ typedef struct { handle_t irqCond, irqHandle, irqLock, asyncLock, periodicLock; volatile unsigned portResetChange; volatile unsigned status; - volatile unsigned portsc; + + volatile int *base; + volatile int *opbase; } ehci_t; @@ -212,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-imx6ull.c b/usb/ehci/phy-armv7a7-imx6ull.c similarity index 92% rename from usb/ehci/phy-imx6ull.c rename to usb/ehci/phy-armv7a7-imx6ull.c index 011d2b3f9..fb8abc4d0 100644 --- a/usb/ehci/phy-imx6ull.c +++ b/usb/ehci/phy-armv7a7-imx6ull.c @@ -33,13 +33,13 @@ enum { phy_pwd, phy_pwd_set, phy_pwd_clr, phy_pwd_tog, phy_tx, phy_tx_set, /* NOTE: This should be obtained using device tree */ static const hcd_info_t imx6ull_info[] = { - { - .type = "ehci", + { .type = "ehci", .hcdaddr = 0x02184200, - .phyaddr = 0x020ca000, - .clk = pctl_clk_usboh3, - .irq = 74 - } + .phy = { + .addr = 0x020ca000, + .clk = pctl_clk_usboh3, + }, + .irq = 74 } }; @@ -88,7 +88,7 @@ void phy_initClock(hcd_t *hcd) .action = pctl_set, .type = pctl_devclock, .devclock = { - .dev = hcd->info->clk, + .dev = hcd->info->phy.clk, .state = 3, } }; @@ -103,7 +103,7 @@ void phy_disableClock(hcd_t *hcd) .action = pctl_set, .type = pctl_devclock, .devclock = { - .dev = hcd->info->clk, + .dev = hcd->info->phy.clk, .state = 0, } }; @@ -123,8 +123,8 @@ int phy_init(hcd_t *hcd) { off_t offs; - offs = hcd->info->phyaddr % _PAGE_SIZE; - hcd->phybase = mmap(NULL, _PAGE_SIZE, PROT_WRITE | PROT_READ, MAP_DEVICE | MAP_PHYSMEM | MAP_ANONYMOUS, -1, hcd->info->phyaddr - offs); + offs = hcd->info->phy.addr % _PAGE_SIZE; + hcd->phybase = mmap(NULL, _PAGE_SIZE, PROT_WRITE | PROT_READ, MAP_DEVICE | MAP_PHYSMEM | MAP_ANONYMOUS, -1, hcd->info->phy.addr - offs); if (hcd->phybase == MAP_FAILED) return -ENOMEM; hcd->phybase += (offs / sizeof(int)); diff --git a/usb/ehci/phy-imxrt106x.c b/usb/ehci/phy-armv7m7-imxrt106x.c similarity index 93% rename from usb/ehci/phy-imxrt106x.c rename to usb/ehci/phy-armv7m7-imxrt106x.c index dec0ec339..641fee21e 100644 --- a/usb/ehci/phy-imxrt106x.c +++ b/usb/ehci/phy-armv7m7-imxrt106x.c @@ -32,13 +32,13 @@ enum { phy_pwd, phy_pwd_set, phy_pwd_clr, phy_pwd_tog, phy_tx, phy_tx_set, /* NOTE: This should be later implemented using device tree */ static const hcd_info_t imxrt_info[] = { - { - .type = "ehci", + { .type = "ehci", .hcdaddr = 0x402e0200, - .phyaddr = 0x400da000, - .clk = pctl_clk_usboh3, - .irq = 128 - } + .phy = { + .addr = 0x400da000, + .clk = pctl_clk_usboh3, + }, + .irq = 128 } }; @@ -104,10 +104,10 @@ void phy_enableHighSpeedDisconnect(hcd_t *hcd, int enable) int phy_init(hcd_t *hcd) { /* No mmapping, since we are on NOMMU architecture */ - hcd->phybase = (volatile int *)hcd->info->phyaddr; + hcd->phybase = (volatile int *)hcd->info->phy.addr; hcd->base = (volatile int *)hcd->info->hcdaddr; - setClock(hcd->info->clk, clk_state_run); + setClock(hcd->info->phy.clk, clk_state_run); phy_reset(hcd); phy_config(hcd); diff --git a/usb/ehci/phy-imxrt117x.c b/usb/ehci/phy-armv7m7-imxrt117x.c similarity index 95% rename from usb/ehci/phy-imxrt117x.c rename to usb/ehci/phy-armv7m7-imxrt117x.c index 9b6ebcdf7..f1698e283 100644 --- a/usb/ehci/phy-imxrt117x.c +++ b/usb/ehci/phy-armv7m7-imxrt117x.c @@ -40,8 +40,10 @@ static const hcd_info_t imxrt_info[] = { { .type = "ehci", .hcdaddr = 0x40430000, - .phyaddr = 0x40434000, - .clk = pctl_lpcg_usb, + .phy = { + .addr = 0x40434000, + .clk = pctl_lpcg_usb, + }, .irq = usb_otg1_irq, }, #endif @@ -49,8 +51,10 @@ static const hcd_info_t imxrt_info[] = { { .type = "ehci", .hcdaddr = 0x4042c000, - .phyaddr = 0x40438000, - .clk = pctl_lpcg_usb, + .phy = { + .addr = 0x40438000, + .clk = pctl_lpcg_usb, + }, .irq = usb_otg2_irq, } #endif @@ -165,13 +169,13 @@ int phy_init(hcd_t *hcd) return -ENODEV; } - res = setClock(hcd->info->clk, 1); + res = setClock(hcd->info->phy.clk, 1); if (res < 0) { return res; } /* NOMMU architecture, mmap not needed */ - hcd->phybase = (void *)hcd->info->phyaddr; + hcd->phybase = (void *)hcd->info->phy.addr; hcd->base = (void *)hcd->info->hcdaddr; phy_start(hcd); diff --git a/usb/ehci/phy-ia32-generic.c b/usb/ehci/phy-ia32-generic.c new file mode 100644 index 000000000..0e617e628 --- /dev/null +++ b/usb/ehci/phy-ia32-generic.c @@ -0,0 +1,188 @@ +/* + * Phoenix-RTOS + * + * EHCI USB Physical Layer for ia32 + * + * Copyright 2024 Phoenix Systems + * Author: Adam Greloch + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define EHCI_MAP_SIZE (0x1000) +#define EHCI_PROGIF (0x20) + + +int hcd_getInfo(const hcd_info_t **info) +{ + platformctl_t pctl; + int i, err; + + hcd_info_t *hcd_info = NULL; + + pctl.action = pctl_get; + pctl.type = pctl_pci; + pctl.pci.id.cl = 0x0c03; + pctl.pci.id.progif = EHCI_PROGIF; + pctl.pci.dev.bus = 0; + pctl.pci.dev.dev = 0; + pctl.pci.dev.func = 0; + pctl.pci.caps = NULL; + + i = 0; + for (;;) { + pctl.pci.id.vendor = PCI_VENDOR_INTEL; + pctl.pci.id.device = PCI_ANY; + pctl.pci.id.subvendor = PCI_ANY; + pctl.pci.id.subdevice = PCI_ANY; + + err = platformctl(&pctl); + + if (err == -ENODEV) { + break; + } + + if (err < 0) { + fprintf(stderr, "phy: pctl_get failed\n"); + return err; + } + + if (pctl.pci.dev.progif != EHCI_PROGIF) { + pctl.pci.dev.func++; + continue; + } + + hcd_info = realloc(hcd_info, sizeof(hcd_info_t) * (i + 1)); + if (hcd_info == NULL) { + return -ENOMEM; + } + memset((void *)(hcd_info + i), 0, sizeof(hcd_info_t)); + + sprintf(hcd_info[i].type, "ehci"); + hcd_info[i].hcdaddr = pctl.pci.dev.resources[0].base; + + /* 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; + + i++; + pctl.pci.dev.func++; + } + + *info = hcd_info; + + return i; +} + + +static void phy_config(hcd_t *hcd) +{ + platformctl_t pctl; + int err; + uint8_t eecp; + short bus = hcd->info->pci_devId.bus; + short dev = hcd->info->pci_devId.dev; + short func = hcd->info->pci_devId.func; + + pctl.action = pctl_set; + pctl.type = pctl_pcicfg; + pctl.pcicfg.dev.bus = bus; + pctl.pcicfg.dev.dev = dev; + pctl.pcicfg.dev.func = func; + pctl.pcicfg.cfg = pci_cfg_busmaster; + pctl.pcicfg.enable = 1; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: setting busmaster failed: %d\n", err); + } + + pctl.pcicfg.cfg = pci_cfg_memoryspace; + pctl.pcicfg.enable = 1; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: setting memoryspace failed: %d\n", err); + } + + pctl.pcicfg.cfg = pci_cfg_interruptdisable; + pctl.pcicfg.enable = 0; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: enabling interrupts failed: %d\n", err); + } + + eecp = (*(uint32_t *)((char *)hcd->base + 0x8) >> 8) & 0xFF; + + if (eecp >= 0x40) { + /* Take ownership of the controller from BIOS */ + pctl.action = pctl_set; + pctl.type = pctl_usbownership; + pctl.usbownership.dev.bus = bus; + pctl.usbownership.dev.dev = dev; + pctl.usbownership.dev.func = func; + pctl.usbownership.osOwned = 1; + pctl.usbownership.eecp = eecp; + + err = platformctl(&pctl); + if (err < 0) { + fprintf(stderr, "phy: taking controller ownership from BIOS failed: %d\n", err); + } + } +} + + +void phy_enableHighSpeedDisconnect(hcd_t *hcd, int enable) +{ +} + + +int phy_init(hcd_t *hcd) +{ + off_t offs; + + hcd->phybase = NULL; + + offs = hcd->info->hcdaddr % _PAGE_SIZE; + 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; + } + hcd->base += (offs / sizeof(int)); + + phy_config(hcd); + + return 0; +}