From 190cc622a2f57ca21d8ffc85be94c32fb518b04d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 9 Mar 2010 19:43:52 -0500 Subject: [PATCH 1/1] Add USB EHCI controller support. Initial support for EHCI high-speed USB controllers. --- Makefile | 2 +- src/config.h | 2 + src/usb-ehci.c | 751 +++++++++++++++++++++++++++++++++++++++++++++++++ src/usb-ehci.h | 173 ++++++++++++ src/usb-hub.c | 3 +- src/usb-hub.h | 2 + src/usb-ohci.c | 6 +- src/usb-uhci.c | 8 +- src/usb.c | 76 ++++- src/usb.h | 12 +- 10 files changed, 1013 insertions(+), 22 deletions(-) create mode 100644 src/usb-ehci.c create mode 100644 src/usb-ehci.h diff --git a/Makefile b/Makefile index 82651d8..d2c0ffd 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ OUT=out/ SRCBOTH=misc.c pmm.c stacks.c output.c util.c block.c floppy.c ata.c mouse.c \ kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \ pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \ - usb.c usb-uhci.c usb-ohci.c usb-hid.c usb-msc.c + usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c SRC16=$(SRCBOTH) system.c disk.c apm.c font.c SRC32FLAT=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \ acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \ diff --git a/src/config.h b/src/config.h index a81c4ac..5316f22 100644 --- a/src/config.h +++ b/src/config.h @@ -36,6 +36,8 @@ #define CONFIG_USB_UHCI 1 // Support USB OHCI controllers #define CONFIG_USB_OHCI 1 +// Support USB EHCI controllers +#define CONFIG_USB_EHCI 1 // Support USB disks #define CONFIG_USB_MSC 1 // Support USB hubs diff --git a/src/usb-ehci.c b/src/usb-ehci.c new file mode 100644 index 0000000..6f23722 --- /dev/null +++ b/src/usb-ehci.c @@ -0,0 +1,751 @@ +// Code for handling EHCI USB controllers. +// +// Copyright (C) 2010 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "pci.h" // pci_bdf_to_bus +#include "config.h" // CONFIG_* +#include "ioport.h" // outw +#include "usb-ehci.h" // struct ehci_qh +#include "pci_ids.h" // PCI_CLASS_SERIAL_USB_UHCI +#include "pci_regs.h" // PCI_BASE_ADDRESS_0 +#include "usb.h" // struct usb_s +#include "farptr.h" // GET_FLATPTR +#include "usb-hub.h" // struct usbhub_s +#include "usb-uhci.h" // init_uhci +#include "usb-ohci.h" // init_ohci + +struct companion_s { + u16 bdf; + u16 type; +}; + +struct usb_ehci_s { + struct usb_s usb; + struct ehci_caps *caps; + struct ehci_regs *regs; + struct ehci_qh *async_qh; + struct companion_s companion[8]; + int checkports; + int legacycount; +}; + + +/**************************************************************** + * Root hub + ****************************************************************/ + +#define EHCI_TIME_POSTPOWER 20 +#define EHCI_TIME_POSTRESET 2 + +// Start processing of companion controllers for full/low speed devices +static void +ehci_startcompanion(struct usb_ehci_s *cntl) +{ + if (! cntl->legacycount) + // No full/low speed devices found. + return; + int i; + for (i=0; icompanion); i++) { + u16 type = cntl->companion[i].type; + if (type == USB_TYPE_UHCI) + uhci_init(cntl->companion[i].bdf, cntl->usb.busid + i); + else if (type == USB_TYPE_OHCI) + ohci_init(cntl->companion[i].bdf, cntl->usb.busid + i); + else + return; + } +} + +static void +init_ehci_port(void *data) +{ + struct usbhub_s *hub = data; + u32 port = hub->port; // XXX - find better way to pass port + struct usb_ehci_s *cntl = container_of(hub->cntl, struct usb_ehci_s, usb); + + u32 *portreg = &cntl->regs->portsc[port]; + u32 portsc = readl(portreg); + + // Power up port. + if (!(portsc & PORT_POWER)) { + portsc |= PORT_POWER; + writel(portreg, portsc); + msleep(EHCI_TIME_POSTPOWER); + portsc = readl(portreg); + } + + if (!(portsc & PORT_CONNECT)) + // No device present + goto done; + + if ((portsc & PORT_LINESTATUS_MASK) == PORT_LINESTATUS_KSTATE) { + // low speed device + cntl->legacycount++; + writel(portreg, portsc | PORT_OWNER); + goto done; + } + + // XXX - if just powered up, need to wait for USB_TIME_ATTDB? + + // Reset port + portsc = (portsc & ~PORT_PE) | PORT_RESET; + writel(portreg, portsc); + msleep(USB_TIME_DRSTR); + mutex_lock(&cntl->usb.resetlock); + portsc &= ~PORT_RESET; + writel(portreg, portsc); + msleep(EHCI_TIME_POSTRESET); + + portsc = readl(portreg); + if (!(portsc & PORT_CONNECT)) + // No longer connected + goto resetfail; + if (!(portsc & PORT_PE)) { + // full speed device + cntl->legacycount++; + writel(portreg, portsc | PORT_OWNER); + goto resetfail; + } + struct usb_pipe *pipe = usb_set_address(hub, port, USB_HIGHSPEED); + if (!pipe) + goto resetfail; + mutex_unlock(&cntl->usb.resetlock); + + // Configure port + int count = configure_usb_device(pipe); + free_pipe(pipe); + if (! count) + // Disable port + writel(portreg, portsc & ~PORT_PE); + hub->devcount += count; +done: + if (! --cntl->checkports) + ehci_startcompanion(cntl); + hub->threads--; + return; + +resetfail: + mutex_unlock(&cntl->usb.resetlock); + goto done; +} + +// Find any devices connected to the root hub. +static int +check_ehci_ports(struct usb_ehci_s *cntl) +{ + ASSERT32FLAT(); + + // Launch a thread for every port. + struct usbhub_s hub; + memset(&hub, 0, sizeof(hub)); + hub.cntl = &cntl->usb; + int ports = cntl->checkports; + hub.threads = ports; + int i; + for (i=0; iregs->usbcmd); + writel(&cntl->regs->usbcmd, (cmd & ~(CMD_ASE | CMD_PSE)) | CMD_HCRESET); + u64 end = calc_future_tsc(250); + for (;;) { + cmd = readl(&cntl->regs->usbcmd); + if (!(cmd & CMD_HCRESET)) + break; + if (check_time(end)) { + warn_timeout(); + goto fail; + } + } + + // Disable interrupts (just to be safe). + writel(&cntl->regs->usbintr, 0); + + // Set schedule to point to primary intr queue head + memset(intr_qh, 0, sizeof(*intr_qh)); + intr_qh->next = EHCI_PTR_TERM; + intr_qh->info2 = (0x01 << QH_SMASK_SHIFT); + intr_qh->token = QTD_STS_HALT; + intr_qh->qtd_next = intr_qh->alt_next = EHCI_PTR_TERM; + int i; + for (i=0; ilinks); i++) + fl->links[i] = (u32)intr_qh | EHCI_PTR_QH; + writel(&cntl->regs->periodiclistbase, (u32)fl); + + // Set async list to point to primary async queue head + memset(async_qh, 0, sizeof(*async_qh)); + async_qh->next = (u32)async_qh | EHCI_PTR_QH; + async_qh->info1 = QH_HEAD; + async_qh->token = QTD_STS_HALT; + async_qh->qtd_next = async_qh->alt_next = EHCI_PTR_TERM; + cntl->async_qh = async_qh; + writel(&cntl->regs->asynclistbase, (u32)async_qh); + + // Enable queues + writel(&cntl->regs->usbcmd, cmd | CMD_ASE | CMD_PSE | CMD_RUN); + + // Set default of high speed for root hub. + writel(&cntl->regs->configflag, 1); + cntl->checkports = readl(&cntl->caps->hcsparams) & HCS_N_PORTS_MASK; + + // Find devices + int count = check_ehci_ports(cntl); + free_pipe(cntl->usb.defaultpipe); + if (count) + // Success + return; + + // No devices found - shutdown and free controller. + writel(&cntl->regs->usbcmd, cmd & ~CMD_RUN); + msleep(4); // 2ms to stop reading memory - XXX +fail: + free(fl); + free(intr_qh); + free(async_qh); + free(cntl); +} + +int +ehci_init(u16 bdf, int busid, int compbdf) +{ + if (! CONFIG_USB_EHCI) + return -1; + + u32 baseaddr = pci_config_readl(bdf, PCI_BASE_ADDRESS_0); + struct ehci_caps *caps = (void*)(baseaddr & PCI_BASE_ADDRESS_MEM_MASK); + u32 hcc_params = readl(&caps->hccparams); + if (hcc_params & HCC_64BIT_ADDR) { + dprintf(1, "No support for 64bit EHCI\n"); + return -1; + } + + struct usb_ehci_s *cntl = malloc_tmphigh(sizeof(*cntl)); + memset(cntl, 0, sizeof(*cntl)); + cntl->usb.busid = busid; + cntl->usb.type = USB_TYPE_EHCI; + cntl->caps = caps; + cntl->regs = (void*)caps + readb(&caps->caplength); + + dprintf(3, "EHCI init on dev %02x:%02x.%x (regs=%p)\n" + , pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf) + , pci_bdf_to_fn(bdf), cntl->regs); + + pci_config_maskw(bdf, PCI_COMMAND, 0, PCI_COMMAND_MASTER); + + // XXX - check for and disable SMM control? + + // Find companion controllers. + int count = 0; + int max = pci_to_bdf(pci_bdf_to_bus(bdf) + 1, 0, 0); + for (;;) { + if (compbdf < 0 || compbdf >= bdf) + break; + u32 code = pci_config_readl(compbdf, PCI_CLASS_REVISION) >> 8; + if (code == PCI_CLASS_SERIAL_USB_UHCI) { + cntl->companion[count].bdf = compbdf; + cntl->companion[count].type = USB_TYPE_UHCI; + count++; + } else if (code == PCI_CLASS_SERIAL_USB_OHCI) { + cntl->companion[count].bdf = compbdf; + cntl->companion[count].type = USB_TYPE_OHCI; + count++; + } + compbdf = pci_next(compbdf+1, &max); + } + + run_thread(configure_ehci, cntl); + return 0; +} + + +/**************************************************************** + * End point communication + ****************************************************************/ + +static int +ehci_wait_qh(struct usb_ehci_s *cntl, struct ehci_qh *qh) +{ + // XXX - 500ms just a guess + u64 end = calc_future_tsc(500); + for (;;) { + if (qh->qtd_next & EHCI_PTR_TERM) + // XXX - confirm + return 0; + if (check_time(end)) { + warn_timeout(); + return -1; + } + yield(); + } +} + +// Wait for next USB async frame to start - for ensuring safe memory release. +static void +ehci_waittick(struct usb_ehci_s *cntl) +{ + if (MODE16) { + msleep(10); + return; + } + // Wait for access to "doorbell" + barrier(); + u32 cmd, sts; + u64 end = calc_future_tsc(100); + for (;;) { + sts = readl(&cntl->regs->usbsts); + if (!(sts & STS_IAA)) { + cmd = readl(&cntl->regs->usbcmd); + if (!(cmd & CMD_IAAD)) + break; + } + if (check_time(end)) { + warn_timeout(); + return; + } + yield(); + } + // Ring "doorbell" + writel(&cntl->regs->usbcmd, cmd | CMD_IAAD); + // Wait for completion + for (;;) { + sts = readl(&cntl->regs->usbsts); + if (sts & STS_IAA) + break; + if (check_time(end)) { + warn_timeout(); + return; + } + yield(); + } + // Ack completion + writel(&cntl->regs->usbsts, STS_IAA); +} + +struct ehci_pipe { + struct ehci_qh qh; + struct ehci_qtd *next_td, *tds; + void *data; + struct usb_pipe pipe; +}; + +void +ehci_free_pipe(struct usb_pipe *p) +{ + if (! CONFIG_USB_EHCI) + return; + dprintf(7, "ehci_free_pipe %p\n", p); + struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe); + struct usb_ehci_s *cntl = container_of( + pipe->pipe.cntl, struct usb_ehci_s, usb); + + struct ehci_qh *start = cntl->async_qh; + struct ehci_qh *pos = start; + for (;;) { + struct ehci_qh *next = (void*)(pos->next & ~EHCI_PTR_BITS); + if (next == start) { + // Not found?! Exit without freeing. + warn_internalerror(); + return; + } + if (next == &pipe->qh) { + pos->next = next->next; + ehci_waittick(cntl); + free(pipe); + return; + } + pos = next; + } +} + +struct usb_pipe * +ehci_alloc_control_pipe(struct usb_pipe *dummy) +{ + if (! CONFIG_USB_EHCI) + return NULL; + struct usb_ehci_s *cntl = container_of( + dummy->cntl, struct usb_ehci_s, usb); + dprintf(7, "ehci_alloc_control_pipe %p\n", &cntl->usb); + + // Allocate a queue head. + struct ehci_pipe *pipe = memalign_tmphigh(EHCI_QH_ALIGN, sizeof(*pipe)); + if (!pipe) { + warn_noalloc(); + return NULL; + } + memset(pipe, 0, sizeof(*pipe)); + memcpy(&pipe->pipe, dummy, sizeof(pipe->pipe)); + pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM; + pipe->qh.token = QTD_STS_HALT; + + // Add queue head to controller list. + struct ehci_qh *async_qh = cntl->async_qh; + pipe->qh.next = async_qh->next; + barrier(); + async_qh->next = (u32)&pipe->qh | EHCI_PTR_QH; + return &pipe->pipe; +} + +static int +fillTDbuffer(struct ehci_qtd *td, u16 maxpacket, const void *buf, int bytes) +{ + u32 dest = (u32)buf; + u32 *pos = td->buf; + while (bytes) { + if (pos >= &td->buf[ARRAY_SIZE(td->buf)]) + // More data than can transfer in a single qtd - only use + // full packets to prevent a babble error. + return ALIGN_DOWN(dest - (u32)buf, maxpacket); + u32 count = bytes; + u32 max = 0x1000 - (dest & 0xfff); + if (count > max) + count = max; + *pos = dest; + bytes -= count; + dest += count; + pos++; + } + return dest - (u32)buf; +} + +int +ehci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize + , void *data, int datasize) +{ + ASSERT32FLAT(); + if (! CONFIG_USB_EHCI) + return -1; + dprintf(5, "ehci_control %p\n", p); + if (datasize > 4*4096 || cmdsize > 4*4096) { + // XXX - should support larger sizes. + warn_noalloc(); + return -1; + } + struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe); + struct usb_ehci_s *cntl = container_of( + pipe->pipe.cntl, struct usb_ehci_s, usb); + + u16 maxpacket = pipe->pipe.maxpacket; + int speed = pipe->pipe.speed; + + // Setup fields in qh + pipe->qh.info1 = ( + (1 << QH_MULT_SHIFT) | (speed != USB_HIGHSPEED ? QH_CONTROL : 0) + | (maxpacket << QH_MAXPACKET_SHIFT) + | QH_TOGGLECONTROL + | (speed << QH_SPEED_SHIFT) + | (pipe->pipe.ep << QH_EP_SHIFT) + | (pipe->pipe.devaddr << QH_DEVADDR_SHIFT)); + pipe->qh.info2 = ((1 << QH_MULT_SHIFT) + | (pipe->pipe.tt_port << QH_HUBPORT_SHIFT) + | (pipe->pipe.tt_devaddr << QH_HUBADDR_SHIFT)); + + // Setup transfer descriptors + struct ehci_qtd *tds = memalign_tmphigh(EHCI_QTD_ALIGN, sizeof(*tds) * 3); + if (!tds) { + warn_noalloc(); + return -1; + } + memset(tds, 0, sizeof(*tds) * 3); + struct ehci_qtd *td = tds; + + td->qtd_next = (u32)&td[1]; + td->alt_next = EHCI_PTR_TERM; + td->token = (ehci_explen(cmdsize) | QTD_STS_ACTIVE + | QTD_PID_SETUP | ehci_maxerr(3)); + fillTDbuffer(td, maxpacket, cmd, cmdsize); + td++; + + if (datasize) { + td->qtd_next = (u32)&td[1]; + td->alt_next = EHCI_PTR_TERM; + td->token = (QTD_TOGGLE | ehci_explen(datasize) | QTD_STS_ACTIVE + | (dir ? QTD_PID_IN : QTD_PID_OUT) | ehci_maxerr(3)); + fillTDbuffer(td, maxpacket, data, datasize); + td++; + } + + td->qtd_next = EHCI_PTR_TERM; + td->alt_next = EHCI_PTR_TERM; + td->token = (QTD_TOGGLE | QTD_STS_ACTIVE + | (dir ? QTD_PID_OUT : QTD_PID_IN) | ehci_maxerr(3)); + + // Transfer data + barrier(); + pipe->qh.qtd_next = (u32)tds; + barrier(); + pipe->qh.token = 0; + int ret = ehci_wait_qh(cntl, &pipe->qh); + pipe->qh.token = QTD_STS_HALT; + if (ret) { + pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM; + // XXX - halt qh? + ehci_waittick(cntl); + } + free(tds); + return ret; +} + +struct usb_pipe * +ehci_alloc_bulk_pipe(struct usb_pipe *dummy) +{ + // XXX - this func is same as alloc_control except for malloc_low + if (! CONFIG_USB_EHCI) + return NULL; + struct usb_ehci_s *cntl = container_of( + dummy->cntl, struct usb_ehci_s, usb); + dprintf(7, "ehci_alloc_bulk_pipe %p\n", &cntl->usb); + + // Allocate a queue head. + struct ehci_pipe *pipe = memalign_low(EHCI_QH_ALIGN, sizeof(*pipe)); + if (!pipe) { + warn_noalloc(); + return NULL; + } + memset(pipe, 0, sizeof(*pipe)); + memcpy(&pipe->pipe, dummy, sizeof(pipe->pipe)); + pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM; + pipe->qh.token = QTD_STS_HALT; + + // Add queue head to controller list. + struct ehci_qh *async_qh = cntl->async_qh; + pipe->qh.next = async_qh->next; + barrier(); + async_qh->next = (u32)&pipe->qh | EHCI_PTR_QH; + return &pipe->pipe; +} + +static int +ehci_wait_td(struct ehci_qtd *td) +{ + u64 end = calc_future_tsc(5000); // XXX - lookup real time. + u32 status; + for (;;) { + status = td->token; + if (!(status & QTD_STS_ACTIVE)) + break; + if (check_time(end)) { + warn_timeout(); + return -1; + } + yield(); + } + if (status & QTD_STS_HALT) { + dprintf(1, "ehci_wait_td error - status=%x\n", status); + return -2; + } + return 0; +} + +#define STACKQTDS 4 + +int +ehci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize) +{ + if (! CONFIG_USB_EHCI) + return -1; + struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe); + dprintf(7, "ehci_send_bulk qh=%p dir=%d data=%p size=%d\n" + , &pipe->qh, dir, data, datasize); + + // Allocate 4 tds on stack (16byte aligned) + u8 tdsbuf[sizeof(struct ehci_qtd) * STACKQTDS + EHCI_QTD_ALIGN - 1]; + struct ehci_qtd *tds = (void*)ALIGN((u32)tdsbuf, EHCI_QTD_ALIGN); + memset(tds, 0, sizeof(*tds) * STACKQTDS); + + // Setup fields in qh + u16 maxpacket = GET_FLATPTR(pipe->pipe.maxpacket); + SET_FLATPTR(pipe->qh.info1 + , ((1 << QH_MULT_SHIFT) + | (maxpacket << QH_MAXPACKET_SHIFT) + | (GET_FLATPTR(pipe->pipe.speed) << QH_SPEED_SHIFT) + | (GET_FLATPTR(pipe->pipe.ep) << QH_EP_SHIFT) + | (GET_FLATPTR(pipe->pipe.devaddr) << QH_DEVADDR_SHIFT))); + SET_FLATPTR(pipe->qh.info2 + , ((1 << QH_MULT_SHIFT) + | (GET_FLATPTR(pipe->pipe.tt_port) << QH_HUBPORT_SHIFT) + | (GET_FLATPTR(pipe->pipe.tt_devaddr) << QH_HUBADDR_SHIFT))); + barrier(); + SET_FLATPTR(pipe->qh.qtd_next, (u32)MAKE_FLATPTR(GET_SEG(SS), tds)); + barrier(); + SET_FLATPTR(pipe->qh.token, GET_FLATPTR(pipe->qh.token) & QTD_TOGGLE); + + int tdpos = 0; + while (datasize) { + struct ehci_qtd *td = &tds[tdpos++ % STACKQTDS]; + int ret = ehci_wait_td(td); + if (ret) + goto fail; + + struct ehci_qtd *nexttd_fl = MAKE_FLATPTR(GET_SEG(SS) + , &tds[tdpos % STACKQTDS]); + + int transfer = fillTDbuffer(td, maxpacket, data, datasize); + td->qtd_next = (transfer==datasize ? EHCI_PTR_TERM : (u32)nexttd_fl); + td->alt_next = EHCI_PTR_TERM; + barrier(); + td->token = (ehci_explen(transfer) | QTD_STS_ACTIVE + | (dir ? QTD_PID_IN : QTD_PID_OUT) | ehci_maxerr(3)); + + data += transfer; + datasize -= transfer; + } + int i; + for (i=0; iqh.qtd_next, EHCI_PTR_TERM); + SET_FLATPTR(pipe->qh.alt_next, EHCI_PTR_TERM); + // XXX - halt qh? + struct usb_ehci_s *cntl = container_of( + GET_FLATPTR(pipe->pipe.cntl), struct usb_ehci_s, usb); + ehci_waittick(cntl); + return -1; +} + +struct usb_pipe * +ehci_alloc_intr_pipe(struct usb_pipe *dummy, int frameexp) +{ + if (! CONFIG_USB_EHCI) + return NULL; + struct usb_ehci_s *cntl = container_of( + dummy->cntl, struct usb_ehci_s, usb); + dprintf(7, "ehci_alloc_intr_pipe %p %d\n", &cntl->usb, frameexp); + + if (frameexp > 10) + frameexp = 10; + int maxpacket = dummy->maxpacket; + // Determine number of entries needed for 2 timer ticks. + int ms = 1<pipe, dummy, sizeof(pipe->pipe)); + pipe->next_td = pipe->tds = tds; + pipe->data = data; + + pipe->qh.info1 = ( + (1 << QH_MULT_SHIFT) + | (maxpacket << QH_MAXPACKET_SHIFT) + | (pipe->pipe.speed << QH_SPEED_SHIFT) + | (pipe->pipe.ep << QH_EP_SHIFT) + | (pipe->pipe.devaddr << QH_DEVADDR_SHIFT)); + pipe->qh.info2 = ((1 << QH_MULT_SHIFT) + | (pipe->pipe.tt_port << QH_HUBPORT_SHIFT) + | (pipe->pipe.tt_devaddr << QH_HUBADDR_SHIFT) + | (0x01 << QH_SMASK_SHIFT) + | (0x1c << QH_CMASK_SHIFT)); + pipe->qh.qtd_next = (u32)tds; + + int i; + for (i=0; iqtd_next = (i==count-1 ? (u32)tds : (u32)&td[1]); + td->alt_next = EHCI_PTR_TERM; + td->token = (ehci_explen(maxpacket) | QTD_STS_ACTIVE + | QTD_PID_IN | ehci_maxerr(3)); + td->buf[0] = (u32)data + maxpacket * i; + } + + // Add to interrupt schedule. + struct ehci_framelist *fl = (void*)readl(&cntl->regs->periodiclistbase); + if (frameexp == 0) { + // Add to existing interrupt entry. + struct ehci_qh *intr_qh = (void*)(fl->links[0] & ~EHCI_PTR_BITS); + pipe->qh.next = intr_qh->next; + barrier(); + intr_qh->next = (u32)&pipe->qh | EHCI_PTR_QH; + } else { + int startpos = 1<<(frameexp-1); + pipe->qh.next = fl->links[startpos]; + barrier(); + for (i=startpos; ilinks); i+=ms) + fl->links[i] = (u32)&pipe->qh | EHCI_PTR_QH; + } + + return &pipe->pipe; +fail: + free(pipe); + free(tds); + free(data); + return NULL; +} + +int +ehci_poll_intr(struct usb_pipe *p, void *data) +{ + ASSERT16(); + if (! CONFIG_USB_EHCI) + return -1; + struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe); + struct ehci_qtd *td = GET_FLATPTR(pipe->next_td); + u32 token = GET_FLATPTR(td->token); + if (token & QTD_STS_ACTIVE) + // No intrs found. + return -1; + // XXX - check for errors. + + // Copy data. + int maxpacket = GET_FLATPTR(pipe->pipe.maxpacket); + int pos = td - GET_FLATPTR(pipe->tds); + void *tddata = GET_FLATPTR(pipe->data) + maxpacket * pos; + memcpy_far(GET_SEG(SS), data + , FLATPTR_TO_SEG(tddata), (void*)FLATPTR_TO_OFFSET(tddata) + , maxpacket); + + // Reenable this td. + struct ehci_qtd *next = (void*)(GET_FLATPTR(td->qtd_next) & ~EHCI_PTR_BITS); + SET_FLATPTR(pipe->next_td, next); + SET_FLATPTR(td->buf[0], (u32)tddata); + barrier(); + SET_FLATPTR(td->token, (ehci_explen(maxpacket) | QTD_STS_ACTIVE + | QTD_PID_IN | ehci_maxerr(3))); + + return 0; +} diff --git a/src/usb-ehci.h b/src/usb-ehci.h new file mode 100644 index 0000000..bb8df52 --- /dev/null +++ b/src/usb-ehci.h @@ -0,0 +1,173 @@ +#ifndef __USB_EHCI_H +#define __USB_EHCI_H + +// usb-ehci.c +int ehci_init(u16 bdf, int busid, int compbdf); +struct usb_pipe; +void ehci_free_pipe(struct usb_pipe *p); +struct usb_pipe *ehci_alloc_control_pipe(struct usb_pipe *dummy); +int ehci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize + , void *data, int datasize); +struct usb_pipe *ehci_alloc_bulk_pipe(struct usb_pipe *dummy); +int ehci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize); +struct usb_pipe *ehci_alloc_intr_pipe(struct usb_pipe *dummy, int frameexp); +int ehci_poll_intr(struct usb_pipe *p, void *data); + + +/**************************************************************** + * ehci structs and flags + ****************************************************************/ + +struct ehci_caps { + u8 caplength; + u8 reserved_01; + u16 hciversion; + u32 hcsparams; + u32 hccparams; + u64 portroute; +} PACKED; + +#define HCC_64BIT_ADDR 1 + +#define HCS_N_PORTS_MASK 0xf + +struct ehci_regs { + u32 usbcmd; + u32 usbsts; + u32 usbintr; + u32 frindex; + u32 ctrldssegment; + u32 periodiclistbase; + u32 asynclistbase; + u32 reserved[9]; + u32 configflag; + u32 portsc[0]; +} PACKED; + +#define CMD_PARK (1<<11) +#define CMD_PARK_CNT(c) (((c)>>8)&3) +#define CMD_LRESET (1<<7) +#define CMD_IAAD (1<<6) +#define CMD_ASE (1<<5) +#define CMD_PSE (1<<4) +#define CMD_HCRESET (1<<1) +#define CMD_RUN (1<<0) + +#define STS_ASS (1<<15) +#define STS_PSS (1<<14) +#define STS_RECL (1<<13) +#define STS_HALT (1<<12) +#define STS_IAA (1<<5) +#define STS_FATAL (1<<4) +#define STS_FLR (1<<3) +#define STS_PCD (1<<2) +#define STS_ERR (1<<1) +#define STS_INT (1<<0) + +#define FLAG_CF (1<<0) + +#define PORT_WKOC_E (1<<22) +#define PORT_WKDISC_E (1<<21) +#define PORT_WKCONN_E (1<<20) +#define PORT_TEST_PKT (0x4<<16) +#define PORT_LED_OFF (0<<14) +#define PORT_LED_AMBER (1<<14) +#define PORT_LED_GREEN (2<<14) +#define PORT_LED_MASK (3<<14) +#define PORT_OWNER (1<<13) +#define PORT_POWER (1<<12) +#define PORT_LINESTATUS_MASK (3<<10) +#define PORT_LINESTATUS_KSTATE (1<<10) +#define PORT_RESET (1<<8) +#define PORT_SUSPEND (1<<7) +#define PORT_RESUME (1<<6) +#define PORT_OCC (1<<5) +#define PORT_OC (1<<4) +#define PORT_PEC (1<<3) +#define PORT_PE (1<<2) +#define PORT_CSC (1<<1) +#define PORT_CONNECT (1<<0) +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) + + +#define EHCI_QH_ALIGN 64 // Can't span a 4K boundary, so increase to 64 + +struct ehci_qh { + u32 next; + u32 info1; + u32 info2; + u32 current; + + u32 qtd_next; + u32 alt_next; + u32 token; + u32 buf[5]; + // u32 buf_hi[5]; +} PACKED; + +#define QH_CONTROL (1 << 27) +#define QH_MAXPACKET_SHIFT 16 +#define QH_MAXPACKET_MASK (0x7ff << QH_MAXPACKET_SHIFT) +#define QH_HEAD (1 << 15) +#define QH_TOGGLECONTROL (1 << 14) +#define QH_SPEED_SHIFT 12 +#define QH_SPEED_MASK (0x3 << QH_SPEED_SHIFT) +#define QH_EP_SHIFT 8 +#define QH_EP_MASK (0xf << QH_EP_SHIFT) +#define QH_DEVADDR_SHIFT 0 +#define QH_DEVADDR_MASK (0x7f << QH_DEVADDR_SHIFT) + +#define QH_SMASK_SHIFT 0 +#define QH_SMASK_MASK (0xff << QH_SMASK_SHIFT) +#define QH_CMASK_SHIFT 8 +#define QH_CMASK_MASK (0xff << QH_CMASK_SHIFT) +#define QH_HUBADDR_SHIFT 16 +#define QH_HUBADDR_MASK (0x7f << QH_HUBADDR_SHIFT) +#define QH_HUBPORT_SHIFT 23 +#define QH_HUBPORT_MASK (0x7f << QH_HUBPORT_SHIFT) +#define QH_MULT_SHIFT 30 +#define QH_MULT_MASK (0x3 << QH_MULT_SHIFT) + +#define EHCI_PTR_BITS 0x001F +#define EHCI_PTR_TERM 0x0001 +#define EHCI_PTR_QH 0x0002 + + +#define EHCI_QTD_ALIGN 32 + +struct ehci_qtd { + u32 qtd_next; + u32 alt_next; + u32 token; + u32 buf[5]; + //u32 buf_hi[5]; +} PACKED; + +#define QTD_TOGGLE (1 << 31) +#define QTD_LENGTH_SHIFT 16 +#define QTD_LENGTH_MASK (0x7fff << QTD_LENGTH_SHIFT) +#define QTD_CERR_SHIFT 10 +#define QTD_CERR_MASK (0x3 << QTD_CERR_SHIFT) +#define QTD_IOC (1 << 15) +#define QTD_PID_OUT (0x0 << 8) +#define QTD_PID_IN (0x1 << 8) +#define QTD_PID_SETUP (0x2 << 8) +#define QTD_STS_ACTIVE (1 << 7) +#define QTD_STS_HALT (1 << 6) +#define QTD_STS_DBE (1 << 5) +#define QTD_STS_BABBLE (1 << 4) +#define QTD_STS_XACT (1 << 3) +#define QTD_STS_MMF (1 << 2) +#define QTD_STS_STS (1 << 1) +#define QTD_STS_PING (1 << 0) + +#define ehci_explen(len) (((len) << QTD_LENGTH_SHIFT) & QTD_LENGTH_MASK) + +#define ehci_maxerr(err) (((err) << QTD_CERR_SHIFT) & QTD_CERR_MASK) + + +struct ehci_framelist { + u32 links[1024]; +} PACKED; + +#endif // usb-ehci.h diff --git a/src/usb-hub.c b/src/usb-hub.c index ce40099..9effbc3 100644 --- a/src/usb-hub.c +++ b/src/usb-hub.c @@ -126,7 +126,8 @@ init_hub_port(void *data) // Set address of port struct usb_pipe *pipe = usb_set_address( - hub->cntl, !!(sts.wPortStatus & USB_PORT_STAT_LOW_SPEED)); + hub, port, ((sts.wPortStatus & USB_PORT_STAT_SPEED_MASK) + >> USB_PORT_STAT_SPEED_SHIFT)); if (!pipe) goto resetfail; mutex_unlock(&hub->cntl->resetlock); diff --git a/src/usb-hub.h b/src/usb-hub.h index 399df57..0994320 100644 --- a/src/usb-hub.h +++ b/src/usb-hub.h @@ -60,6 +60,8 @@ struct usb_port_status { #define USB_PORT_STAT_RESET 0x0010 #define USB_PORT_STAT_L1 0x0020 #define USB_PORT_STAT_POWER 0x0100 +#define USB_PORT_STAT_SPEED_SHIFT 9 +#define USB_PORT_STAT_SPEED_MASK (0x3 << USB_PORT_STAT_SPEED_SHIFT) #define USB_PORT_STAT_LOW_SPEED 0x0200 #define USB_PORT_STAT_HIGH_SPEED 0x0400 #define USB_PORT_STAT_TEST 0x0800 diff --git a/src/usb-ohci.c b/src/usb-ohci.c index 5fb7fec..8bf8d75 100644 --- a/src/usb-ohci.c +++ b/src/usb-ohci.c @@ -61,7 +61,7 @@ init_ohci_port(void *data) goto resetfail; // Set address of port - struct usb_pipe *pipe = usb_set_address(&cntl->usb, !!(sts & RH_PS_LSDA)); + struct usb_pipe *pipe = usb_set_address(hub, port, !!(sts & RH_PS_LSDA)); if (!pipe) goto resetfail; mutex_unlock(&cntl->usb.resetlock); @@ -378,7 +378,7 @@ ohci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize struct usb_ohci_s *cntl = container_of( pipe->pipe.cntl, struct usb_ohci_s, usb); int maxpacket = pipe->pipe.maxpacket; - int lowspeed = pipe->pipe.lowspeed; + int lowspeed = pipe->pipe.speed; int devaddr = pipe->pipe.devaddr | (pipe->pipe.ep << 7); // Setup transfer descriptors @@ -435,7 +435,7 @@ ohci_alloc_intr_pipe(struct usb_pipe *dummy, int frameexp) if (frameexp > 5) frameexp = 5; int maxpacket = dummy->maxpacket; - int lowspeed = dummy->lowspeed; + int lowspeed = dummy->speed; int devaddr = dummy->devaddr | (dummy->ep << 7); // Determine number of entries needed for 2 timer ticks. int ms = 1<usb, !!(status & USBPORTSC_LSDA)); + hub, port, !!(status & USBPORTSC_LSDA)); if (!pipe) goto resetfail; mutex_unlock(&cntl->usb.resetlock); @@ -334,7 +334,7 @@ uhci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize pipe->pipe.cntl, struct usb_uhci_s, usb); int maxpacket = pipe->pipe.maxpacket; - int lowspeed = pipe->pipe.lowspeed; + int lowspeed = pipe->pipe.speed; int devaddr = pipe->pipe.devaddr | (pipe->pipe.ep << 7); // Setup transfer descriptors @@ -447,7 +447,7 @@ uhci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize) dprintf(7, "uhci_send_bulk qh=%p dir=%d data=%p size=%d\n" , &pipe->qh, dir, data, datasize); int maxpacket = GET_FLATPTR(pipe->pipe.maxpacket); - int lowspeed = GET_FLATPTR(pipe->pipe.lowspeed); + int lowspeed = GET_FLATPTR(pipe->pipe.speed); int devaddr = (GET_FLATPTR(pipe->pipe.devaddr) | (GET_FLATPTR(pipe->pipe.ep) << 7)); int toggle = GET_FLATPTR(pipe->toggle) ? TD_TOKEN_TOGGLE : 0; @@ -515,7 +515,7 @@ uhci_alloc_intr_pipe(struct usb_pipe *dummy, int frameexp) if (frameexp > 10) frameexp = 10; int maxpacket = dummy->maxpacket; - int lowspeed = dummy->lowspeed; + int lowspeed = dummy->speed; int devaddr = dummy->devaddr | (dummy->ep << 7); // Determine number of entries needed for 2 timer ticks. int ms = 1<bInterval; - if (period <= 0) - period = 1; - int frameexp = __fls(period); + int frameexp; + if (pipe->speed != USB_HIGHSPEED) + frameexp = (period <= 0) ? 0 : __fls(period); + else + frameexp = (period <= 4) ? 0 : period - 4; switch (pipe->type) { default: case USB_TYPE_UHCI: return uhci_alloc_intr_pipe(&dummy, frameexp); case USB_TYPE_OHCI: return ohci_alloc_intr_pipe(&dummy, frameexp); + case USB_TYPE_EHCI: + return ehci_alloc_intr_pipe(&dummy, frameexp); } } @@ -131,6 +146,8 @@ usb_poll_intr(struct usb_pipe *pipe_fl, void *data) return uhci_poll_intr(pipe_fl, data); case USB_TYPE_OHCI: return ohci_poll_intr(pipe_fl, data); + case USB_TYPE_EHCI: + return ehci_poll_intr(pipe_fl, data); } } @@ -226,9 +243,10 @@ set_configuration(struct usb_pipe *pipe, u16 val) // Assign an address to a device in the default state on the given // controller. struct usb_pipe * -usb_set_address(struct usb_s *cntl, int lowspeed) +usb_set_address(struct usbhub_s *hub, int port, int speed) { ASSERT32FLAT(); + struct usb_s *cntl = hub->cntl; dprintf(3, "set_address %p\n", cntl); if (cntl->maxaddr >= USB_MAXADDR) return NULL; @@ -245,7 +263,18 @@ usb_set_address(struct usb_s *cntl, int lowspeed) if (!defpipe) return NULL; } - defpipe->lowspeed = lowspeed; + defpipe->speed = speed; + if (hub->pipe) { + if (hub->pipe->speed == USB_HIGHSPEED) { + defpipe->tt_devaddr = hub->pipe->devaddr; + defpipe->tt_port = port; + } else { + defpipe->tt_devaddr = hub->pipe->tt_devaddr; + defpipe->tt_port = hub->pipe->tt_port; + } + } else { + defpipe->tt_devaddr = defpipe->tt_port = 0; + } msleep(USB_TIME_RSTRCY); @@ -339,6 +368,7 @@ usb_setup(void) usb_keyboard_setup(); // Look for USB controllers + int ehcibdf = -1; int count = 0; int bdf, max; foreachpci(bdf, max) { @@ -347,13 +377,37 @@ usb_setup(void) if (code >> 8 != PCI_CLASS_SERIAL_USB) continue; + if (bdf > ehcibdf) { + // Check to see if this device has an ehci controller + ehcibdf = bdf; + u32 ehcicode = code; + int found = 0; + for (;;) { + if (ehcicode == PCI_CLASS_SERIAL_USB_EHCI) { + // Found an ehci controller. + int ret = ehci_init(ehcibdf, count++, bdf); + if (ret) + // Error + break; + count += found; + bdf = ehcibdf; + code = 0; + break; + } + if (ehcicode >> 8 == PCI_CLASS_SERIAL_USB) + found++; + ehcibdf = pci_next(ehcibdf+1, &max); + if (ehcibdf < 0 + || pci_bdf_to_busdev(ehcibdf) != pci_bdf_to_busdev(bdf)) + // No ehci controller found. + break; + ehcicode = pci_config_readl(ehcibdf, PCI_CLASS_REVISION) >> 8; + } + } + if (code == PCI_CLASS_SERIAL_USB_UHCI) - uhci_init(bdf, count); + uhci_init(bdf, count++); else if (code == PCI_CLASS_SERIAL_USB_OHCI) - ohci_init(bdf, count); - else - continue; - - count++; + ohci_init(bdf, count++); } } diff --git a/src/usb.h b/src/usb.h index 10f824f..b5cbf2e 100644 --- a/src/usb.h +++ b/src/usb.h @@ -9,8 +9,10 @@ struct usb_pipe { u8 type; u8 ep; u8 devaddr; - u8 lowspeed; + u8 speed; u16 maxpacket; + u8 tt_devaddr; + u8 tt_port; }; // Common information for usb controllers. @@ -24,6 +26,11 @@ struct usb_s { #define USB_TYPE_UHCI 1 #define USB_TYPE_OHCI 2 +#define USB_TYPE_EHCI 3 + +#define USB_FULLSPEED 0 +#define USB_LOWSPEED 1 +#define USB_HIGHSPEED 2 #define USB_MAXADDR 127 @@ -168,7 +175,8 @@ struct usb_endpoint_descriptor { // usb.c void usb_setup(void); -struct usb_pipe *usb_set_address(struct usb_s *cntl, int lowspeed); +struct usbhub_s; +struct usb_pipe *usb_set_address(struct usbhub_s *hub, int port, int speed); int configure_usb_device(struct usb_pipe *pipe); int send_default_control(struct usb_pipe *pipe, const struct usb_ctrlrequest *req , void *data); -- 2.25.1