diff options
Diffstat (limited to 'sys/dev/usb/xhci.c')
-rw-r--r-- | sys/dev/usb/xhci.c | 463 |
1 files changed, 0 insertions, 463 deletions
diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c deleted file mode 100644 index f221843..0000000 --- a/sys/dev/usb/xhci.c +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (c) 2023-2024 Ian Marco Moffett and the Osmora Team. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Hyra nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include <sys/driver.h> -#include <dev/pci/pci.h> -#include <sys/syslog.h> -#include <sys/timer.h> -#include <dev/usb/xhciregs.h> -#include <dev/usb/xhcivar.h> -#include <vm/physseg.h> -#include <vm/dynalloc.h> -#include <vm/vm.h> -#include <string.h> -#include <assert.h> - -__MODULE_NAME("xhci"); -__KERNEL_META("$Hyra$: xhci.c, Ian Marco Moffett, " - "xHCI driver"); - -#define pr_trace(fmt, ...) kprintf("xhci: " fmt, ##__VA_ARGS__) -#define pr_error(...) pr_trace(__VA_ARGS__) - -static struct pci_device *hci_dev; -static struct timer driver_tmr; - -static inline uint32_t * -xhci_get_portsc(struct xhci_hc *hc, uint8_t portno) -{ - if (portno > hc->maxports) { - portno = hc->maxports; - } - return XHCI_BASE_OFF(hc->opregs, 0x400 + (0x10 * (portno - 1))); -} - -/* - * Set event ring segment table base - * address. - * - * @hc: Host controler descriptor. - * @pa: Physical address. - */ -static inline void -xhci_set_erst_base(struct xhci_hc *hc, uintptr_t pa) -{ - struct xhci_caps *caps = XHCI_CAPS(hc->base); - void *runtime_base = XHCI_RTS(hc->base, caps->rtsoff); - uintptr_t *erstba; - - /* - * The spec states that the Event Ring Segment Table - * Base Address register is at 'runtime_base + 0x30 + - * (32 * interrupter)'. See the xHCI spec, section 5.5.2.3.2 - */ - erstba = XHCI_BASE_OFF(runtime_base, 0x30); - *erstba = pa; -} - -/* - * Submit a command by pushing a TRB to the - * command ring. - * - * @hc: Host controller descriptor. - * @trb: Transfer Request Block of command. - */ -static int -xhci_submit_cmd(struct xhci_hc *hc, struct xhci_trb trb) -{ - volatile uint32_t *cmd_db; - struct xhci_caps *caps = XHCI_CAPS(hc->base); - - /* Push the TRB to the command ring */ - hc->cmd_ring[hc->cmd_ptr++] = trb.dword0; - hc->cmd_ring[hc->cmd_ptr++] = trb.dword1; - hc->cmd_ring[hc->cmd_ptr++] = trb.dword2; - hc->cmd_ring[hc->cmd_ptr++] = trb.dword3 | hc->cycle; - hc->cmd_count++; - - /* Ring the command doorbell */ - cmd_db = XHCI_CMD_DB(hc->base, caps->dboff); - *cmd_db = 0; - - if (hc->cmd_count >= XHCI_CMDRING_LEN - 1) { - /* - * Create raw link TRB and ring the doorbell. We want the - * xHC to flip its cycle bit so it doesn't confuse existing - * entries (that we'll overwrite) in the ring with current - * entries, so we set the Toggle Cycle bit. - * - * See the xHCI spec, section 6.4.4.1 for information regarding - * the format of link TRBs. - */ - hc->cmd_ring[hc->cmd_ptr++] = VIRT_TO_PHYS(hc->cmd_ring) & 0xFFFFFFFF; - hc->cmd_ring[hc->cmd_ptr++] = VIRT_TO_PHYS(hc->cmd_ring) >> 32; - hc->cmd_ring[hc->cmd_ptr++] = 0; - hc->cmd_ring[hc->cmd_ptr++] = hc->cycle | (XHCI_LINK << 10) | __BIT(1); - *cmd_db = 0; - - /* Reset command state and flip cycle */ - hc->cmd_ptr = 0; - hc->cmd_count = 0; - hc->cycle = ~hc->cycle; - } - - return 0; -} - -/* - * Parse xHCI extended caps - */ -static int -xhci_parse_ecp(struct xhci_hc *hc) -{ - struct xhci_caps *caps = XHCI_CAPS(hc->base); - struct xhci_proto *proto; - uint32_t *p, val, dword2; - uint32_t cap_ptr = XHCI_ECP(caps->hccparams1); - - p = XHCI_BASE_OFF(hc->base, cap_ptr*4); - while (cap_ptr != 0) { - val = *p; - dword2 = *((uint32_t *)XHCI_BASE_OFF(p, 8)); - - /* Get the next cap */ - p = XHCI_BASE_OFF(p, XHCI_PROTO_NEXT(val) * 4); - cap_ptr = XHCI_PROTO_NEXT(val); - - if (XHCI_PROTO_ID(val) != XHCI_ECAP_PROTO) { - /* Not a Supported Protocol Capability */ - continue; - } - - if (hc->protocnt > XHCI_MAX_PROTOS) { - /* Too many protocols */ - break; - } - - proto = &hc->protos[hc->protocnt++]; - proto->major = XHCI_PROTO_MAJOR(val); - proto->port_count = XHCI_PROTO_PORTCNT(dword2); - proto->port_start = XHCI_PROTO_PORTOFF(dword2); - } - - return 0; -} - -/* - * Set of xHCI scratchpad buffers. - */ -static int -xhci_init_scratchpads(struct xhci_hc *hc) -{ - struct xhci_caps *caps = XHCI_CAPS(hc->base); - uint16_t max_bufs_lo, max_bufs_hi, max_bufs; - uintptr_t *buffer_array, tmp; - - max_bufs_lo = XHCI_MAX_SP_LO(caps->hcsparams2); - max_bufs_hi = XHCI_MAX_SP_HI(caps->hcsparams2); - max_bufs = (max_bufs_hi << 5) | max_bufs_lo; - if (max_bufs == 0) { - /* - * Some emulators like QEMU don't need any - * scratchpad buffers so we can just return - * early. - */ - return 0; - } - - pr_trace("Need %d scratchpad buffers\n", max_bufs); - - /* Allocate buffer array */ - buffer_array = dynalloc_memalign(sizeof(uintptr_t)*max_bufs, 0x1000); - if (buffer_array == NULL) { - pr_error("Failed to allocate scratchpad buffer array\n"); - return -1; - } - - memset(buffer_array, 0, sizeof(uintptr_t)*max_bufs); - - /* Fill the buffer array */ - for (size_t i = 0; i < max_bufs; ++i) { - tmp = vm_alloc_pageframe(1); - - if (tmp == 0) { - /* TODO: Shutdown, free memory */ - pr_error("Failed to fill scratchpad buffer array\n"); - return -1; - } - - buffer_array[i] = tmp; - } - - hc->dcbaap[0] = VIRT_TO_PHYS(buffer_array); - return 0; -} - -/* - * Init USB ports on the root hub. - */ -static int -xhci_init_ports(struct xhci_hc *hc) -{ - struct xhci_caps *caps = XHCI_CAPS(hc->base); - size_t maxports = XHCI_MAXPORTS(caps->hcsparams1); - uint32_t *portsc; - - for (size_t i = 1; i < maxports; ++i) { - portsc = xhci_get_portsc(hc, i); - if (__TEST(*portsc, XHCI_PORTSC_CCS)) { - pr_trace("Device connected on port %d, resetting...\n", i); - *portsc |= XHCI_PORTSC_PR; - } - } - - return 0; -} - -/* - * Poll USBSTS.HCH to be val - * - * Returns 0 on success. Non-zero values returned - * indicate a hang. - */ -static int -xhci_poll_hch(struct xhci_hc *hc, uint8_t val) -{ - struct xhci_opregs *opregs = hc->opregs; - size_t time_waiting = 0; - - while ((opregs->usbsts & USBSTS_HCH) != val) { - if (time_waiting >= XHCI_TIMEOUT) { - /* Hang */ - return -1; - } - - time_waiting += 50; - driver_tmr.msleep(50); - } - - return 0; -} - -/* - * Start up the host controller by setting - * the USBCMD run/stop bit. - */ -static int -xhci_start_hc(struct xhci_hc *hc) -{ - struct xhci_opregs *opregs = hc->opregs; - int status; - - opregs->usbcmd |= USBCMD_RUN; - - if ((status = xhci_poll_hch(hc, 0)) != 0) { - return status; - } - - return 0; -} - -static int -xhci_reset_hc(struct xhci_hc *hc) -{ - struct xhci_opregs *opregs = hc->opregs; - size_t time_waiting = 0; /* In ms */ - - pr_trace("Resetting host controller...\n"); - - /* - * Set USBCMD.HCRST to reset the controller and - * wait for it to become zero. - */ - opregs->usbcmd |= USBCMD_HCRST; - while (1) { - if (!__TEST(opregs->usbcmd, USBCMD_HCRST)) { - /* Reset is complete */ - break; - } - if (time_waiting >= XHCI_TIMEOUT) { - pr_error("Hang while polling USBCMD.HCRST to be zero\n"); - return -1; - } - driver_tmr.msleep(50); - time_waiting += 50; - } - return 0; -} - -/* - * Allocate the Device Context Base Address - * Array. - * - * Returns the physical address and sets - * hc->dcbaap to the virtual address. - */ -static uintptr_t -xhci_alloc_dcbaa(struct xhci_hc *hc) -{ - size_t dcbaa_size; - - dcbaa_size = sizeof(uintptr_t) * hc->maxslots; - hc->dcbaap = dynalloc_memalign(dcbaa_size, 0x1000); - __assert(hc->dcbaap != NULL); - - return VIRT_TO_PHYS(hc->dcbaap); -} - -/* - * Allocates command ring and sets hc->cmd_ring - * to the virtual address. - * - * Returns the physical address. - */ -static uintptr_t -xhci_alloc_cmdring(struct xhci_hc *hc) -{ - size_t cmdring_size; - - cmdring_size = XHCI_TRB_SIZE * XHCI_CMDRING_LEN; - hc->cmd_ring = dynalloc_memalign(cmdring_size, 0x1000); - __assert(hc->cmd_ring != NULL); - - return VIRT_TO_PHYS(hc->cmd_ring); -} - -/* - * Sets up the event ring. - */ -static void -xhci_init_evring(struct xhci_hc *hc) -{ - struct xhci_caps *caps = XHCI_CAPS(hc->base); - struct xhci_evring_segment *seg; - uint64_t *erdp, *erstba; - uint32_t *erst_size; - void *runtime = XHCI_RTS(hc->base, caps->rtsoff); - size_t size; - - size = XHCI_EVRING_LEN * XHCI_TRB_SIZE; - seg = dynalloc_memalign(size, 64); - memset(seg, 0, size); - - /* Set the size of the event ring segment table */ - erst_size = XHCI_BASE_OFF(runtime, 0x28); - *erst_size = 1; - - /* Setup the event ring segment */ - memset(seg, 0, size); - seg->base = VIRT_TO_PHYS(seg); - seg->size = XHCI_EVRING_LEN; - - /* Setup the event ring dequeue pointer */ - erdp = XHCI_BASE_OFF(runtime, 0x38); - *erdp = seg->base; - - /* Point ERSTBA to our event ring segment */ - erstba = XHCI_BASE_OFF(runtime, 0x30); - *erstba = VIRT_TO_PHYS(seg); - - hc->event_ring = PHYS_TO_VIRT(seg->base); -} - -static int -xhci_init_hc(struct xhci_hc *hc) -{ - struct xhci_caps *caps; - struct xhci_opregs *opregs; - - /* Get some information from the controller */ - caps = XHCI_CAPS(hc->base); - hc->caplen = caps->caplength; - hc->maxslots = XHCI_MAXSLOTS(caps->hcsparams1); - hc->maxports = XHCI_MAXSLOTS(caps->hcsparams1); - - opregs->config |= hc->maxslots; - - /* Fetch the opregs */ - opregs = XHCI_OPBASE(hc->base, hc->caplen); - hc->opregs = XHCI_OPBASE(hc->base, hc->caplen); - - if (xhci_reset_hc(hc) != 0) { - return -1; - } - - /* Set cmdring state */ - hc->cycle = 1; - hc->cmd_ptr = 0; - hc->cmd_count = 0; - - /* Allocate resources and tell the HC about them */ - opregs->dcbaa_ptr = xhci_alloc_dcbaa(hc); - xhci_init_scratchpads(hc); - xhci_init_evring(hc); - opregs->cmd_ring = xhci_alloc_cmdring(hc); - - /* We're ready, start up the HC and ports */ - xhci_start_hc(hc); - xhci_parse_ecp(hc); - xhci_init_ports(hc); - return 0; -} - -static int -xhci_init(void) -{ - uintptr_t bar0, bar1, base; - struct xhci_hc hc; - struct pci_lookup hc_lookup = { - .pci_class = 0x0C, - .pci_subclass = 0x03 - }; - - /* Find the host controller on the bus */ - hci_dev = pci_get_device(hc_lookup, PCI_CLASS | PCI_SUBCLASS); - if (hci_dev == NULL) { - return -1; - } - - bar0 = hci_dev->bar[0] & ~7; - bar1 = hci_dev->bar[1] & ~7; - base = __COMBINE32(bar1, bar0); - hc.base = PHYS_TO_VIRT(base); - pr_trace("xHCI HC base @ 0x%p\n", base); - - if (req_timer(TIMER_GP, &driver_tmr) != 0) { - pr_error("Failed to fetch general purpose timer\n"); - return -1; - } - if (driver_tmr.msleep == NULL) { - pr_error("Timer does not have msleep()\n"); - return -1; - } - - return xhci_init_hc(&hc); -} - -DRIVER_EXPORT(xhci_init); |