diff options
Diffstat (limited to 'sys/dev/usb/xhci.c')
-rw-r--r-- | sys/dev/usb/xhci.c | 172 |
1 files changed, 136 insertions, 36 deletions
diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c index 50ddac6..46ec4af 100644 --- a/sys/dev/usb/xhci.c +++ b/sys/dev/usb/xhci.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 Ian Marco Moffett and the Osmora Team. + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,6 +37,8 @@ #include <dev/usb/xhciregs.h> #include <dev/usb/xhcivar.h> #include <dev/pci/pci.h> +#include <dev/pci/pciregs.h> +#include <dev/acpi/acpi.h> #include <vm/physmem.h> #include <vm/dynalloc.h> #include <assert.h> @@ -55,6 +57,13 @@ static struct pci_device *hci_dev; static struct timer tmr; +static int +xhci_intr(void *sf) +{ + pr_trace("received xHCI interrupt (via PCI MSI-X)\n"); + return 1; /* handled */ +} + /* * Get port status and control register for a specific * port. @@ -139,15 +148,17 @@ xhci_parse_ecp(struct xhci_hc *hc) break; case XHCI_ECAP_USBLEGSUP: /* Begin xHC BIOS handoff to us */ - pr_trace("Establishing xHC ownership...\n"); - val |= XHCI_OS_SEM; - mmio_write32(p, val); - - /* Ensure the xHC responded correctly */ - if (xhci_poll32(p, XHCI_OS_SEM, 1) < 0) - return -EIO; - if (xhci_poll32(p, XHCI_BIOS_SEM, 0) < 0) - return -EIO; + if (!ISSET(hc->quirks, XHCI_QUIRK_HANDOFF)) { + pr_trace("establishing xHC ownership...\n"); + val |= XHCI_OS_SEM; + mmio_write32(p, val); + + /* Ensure the xHC responded correctly */ + if (xhci_poll32(p, XHCI_OS_SEM, 1) < 0) + return -EIO; + if (xhci_poll32(p, XHCI_BIOS_SEM, 0) < 0) + return -EIO; + } break; } @@ -165,6 +176,7 @@ xhci_init_scratchpads(struct xhci_hc *hc) struct xhci_caps *caps = XHCI_CAPS(hc->base); uint16_t max_bufs_lo, max_bufs_hi; uint16_t max_bufs; + size_t len; uintptr_t *bufarr, tmp; max_bufs_lo = XHCI_MAX_SP_LO(caps->hcsparams1); @@ -178,10 +190,11 @@ xhci_init_scratchpads(struct xhci_hc *hc) return 0; } - pr_trace("Using %d pages for xHC scratchpads\n"); - bufarr = dynalloc_memalign(sizeof(uintptr_t)*max_bufs, 0x1000); + len = sizeof(uint64_t) * max_bufs; + pr_trace("using %d bytes for xHC scratchpads\n", len); + bufarr = dynalloc_memalign(len, 0x1000); if (bufarr == NULL) { - pr_error("Failed to allocate scratchpad buffer array\n"); + pr_error("failed to allocate scratchpad buffer array\n"); return -1; } @@ -190,7 +203,7 @@ xhci_init_scratchpads(struct xhci_hc *hc) memset(PHYS_TO_VIRT(tmp), 0, 0x1000); if (tmp == 0) { /* TODO: Shutdown, free memory */ - pr_error("Failed to fill scratchpad buffer array\n"); + pr_error("failed to fill scratchpad buffer array\n"); return -1; } bufarr[i] = tmp; @@ -215,39 +228,66 @@ xhci_alloc_dcbaa(struct xhci_hc *hc) } /* + * Initialize MSI-X interrupts. + */ +static int +xhci_init_msix(struct xhci_hc *hc) +{ + struct msi_intr intr; + + intr.name = "xHCI MSI-X"; + intr.handler = xhci_intr; + return pci_enable_msix(hci_dev, &intr); +} + +/* * 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; + struct xhci_evring_segment *segtab; + uint64_t *erdp, *erstba, tmp; + uint32_t *erst_size, *iman, *imod; void *runtime = XHCI_RTS(hc->base, caps->rtsoff); + void *tmp_p; size_t size; - size = XHCI_EVRING_LEN * XHCI_TRB_SIZE; - seg = dynalloc_memalign(size, 64); - memset(seg, 0, size); + segtab = PHYS_TO_VIRT(vm_alloc_frame(1)); + memset(segtab, 0, DEFAULT_PAGESIZE); /* Set the size of the event ring segment table */ - erst_size = PTR_OFFSET(runtime, 0x28); - mmio_write16(erst_size, 1); + erst_size = PTR_OFFSET(runtime, XHCI_RT_ERSTSZ); + mmio_write32(erst_size, 1); + + /* Allocate the event ring segment */ + size = XHCI_EVRING_LEN * XHCI_TRB_SIZE; + tmp_p = PHYS_TO_VIRT(vm_alloc_frame(4)); + memset(tmp_p, 0, size); - /* Setup the event ring segment */ - memset(seg, 0, size); - seg->base = VIRT_TO_PHYS(seg); - seg->size = XHCI_EVRING_LEN; + /* setup the event ring segment */ + segtab->base = VIRT_TO_PHYS(tmp_p); + segtab->base = ((uintptr_t)segtab->base) + (2 * 4096) & ~0xF; + segtab->size = XHCI_EVRING_LEN; /* Setup the event ring dequeue pointer */ - erdp = PTR_OFFSET(runtime, 0x38); - mmio_write64(erdp, seg->base); + erdp = PTR_OFFSET(runtime, XHCI_RT_ERDP); + mmio_write64(erdp, segtab->base); /* Point ERSTBA to our event ring segment */ - erstba = PTR_OFFSET(runtime, 0x30); - mmio_write64(erstba, VIRT_TO_PHYS(seg)); - hc->evring = PHYS_TO_VIRT(seg->base); + erstba = PTR_OFFSET(runtime, XHCI_RT_ERSTBA); + mmio_write64(erstba, VIRT_TO_PHYS(segtab)); + hc->evring = PHYS_TO_VIRT(segtab->base); + + /* Setup interrupt moderation */ + imod = PTR_OFFSET(runtime, XHCI_RT_IMOD); + mmio_write32(imod, XHCI_IMOD_DEFAULT); + + /* Enable interrupts */ + iman = PTR_OFFSET(runtime, XHCI_RT_IMAN); + tmp = mmio_read32(iman); + mmio_write32(iman, tmp | XHCI_IMAN_IE); } /* @@ -263,6 +303,8 @@ xhci_alloc_cmdring(struct xhci_hc *hc) size = XHCI_TRB_SIZE * XHCI_CMDRING_LEN; hc->cmdring = dynalloc_memalign(size, 0x1000); + + memset(hc->cmdring, 0, size); __assert(hc->cmdring != NULL); return VIRT_TO_PHYS(hc->cmdring); } @@ -297,6 +339,27 @@ xhci_reset(struct xhci_hc *hc) } /* + * Enable or disable xHC interrupts. + */ +static void +xhci_set_intr(struct xhci_hc *hc, int enable) +{ + struct xhci_opregs *opregs = hc->opregs; + uint32_t usbcmd; + + enable &= 1; + usbcmd = mmio_read32(&opregs->usbcmd); + + if (enable == 1) { + usbcmd |= USBCMD_INTE; + } else { + usbcmd &= ~USBCMD_INTE; + } + + mmio_write32(&opregs->usbcmd, usbcmd); +} + +/* * Start up the host controller and put it into * a running state. Returns 0 on success. */ @@ -336,8 +399,8 @@ xhci_init_ports(struct xhci_hc *hc) devtype = (ISSET(portsc, XHCI_PORTSC_DR)) ? "removable" : "non-removable"; - pr_trace("Detected %s USB device on port %d\n", devtype, i); - pr_trace("Resetting port %d...\n", i); + pr_trace("detected %s USB device on port %d\n", devtype, i); + pr_trace("resetting port %d...\n", i); portsc |= XHCI_PORTSC_PR; mmio_write32(portsc_p, portsc); } @@ -358,6 +421,28 @@ xhci_init_hc(struct xhci_hc *hc) uintptr_t dcbaap, cmdring; struct xhci_caps *caps; struct xhci_opregs *opregs; + const char *vendor; + + /* + * The firmware on some Dell machines handle the + * xHCI BIOS/OS handoff very poorly. Updating the + * the OS semaphore in the USBLEGSUP register will + * result in the chipset firing off an SMI which is + * supposed to perform the actual handoff. + * + * However, Dell is stupid as always and the machine + * can get stuck in SMM which results in the machine + * locking up in a *very* bad way. In other words, the + * OS execution is literally halted and further SMIs like + * thermal, power, and fan events are deferred forever + * (no bueno!!). The best thing to do is to not perform + * a handoff if the host board is by Dell (bad Dell!!). + */ + vendor = acpi_oemid(); + if (memcmp(vendor, "DELL", 4) == 0) { + pr_trace("detected xhc handoff quirk\n"); + hc->quirks |= XHCI_QUIRK_HANDOFF; + } caps = (struct xhci_caps *)hc->base; caplength = mmio_read8(&caps->caplength); @@ -376,7 +461,7 @@ xhci_init_hc(struct xhci_hc *hc) return -1; } - pr_trace("Resetting xHC chip...\n"); + pr_trace("resetting xHC chip...\n"); if ((error = xhci_reset(hc)) != 0) { return error; } @@ -403,13 +488,27 @@ xhci_init_hc(struct xhci_hc *hc) mmio_write64(&opregs->cmd_ring, cmdring); hc->cr_cycle = 1; + xhci_init_msix(hc); xhci_init_evring(hc); xhci_parse_ecp(hc); xhci_start_hc(hc); + + /* Allow the xHC to generate interrupts */ + xhci_set_intr(hc, 1); xhci_init_ports(hc); return 0; } +static void +xhci_init_pci(void) +{ + uint32_t tmp; + + tmp = pci_readl(hci_dev, PCIREG_CMDSTATUS); + tmp |= (PCI_BUS_MASTERING | PCI_MEM_SPACE); + pci_writel(hci_dev, PCIREG_CMDSTATUS, tmp); +} + static int xhci_init(void) { @@ -432,16 +531,17 @@ xhci_init(void) /* Try to request a general purpose timer */ if (req_timer(TIMER_GP, &tmr) != TMRR_SUCCESS) { - pr_error("Failed to fetch general purpose timer\n"); + pr_error("failed to fetch general purpose timer\n"); return -ENODEV; } /* Ensure it has get_time_usec() */ if (tmr.get_time_usec == NULL) { - pr_error("General purpose timer has no get_time_usec()\n"); + pr_error("general purpose timer has no get_time_usec()\n"); return -ENODEV; } + xhci_init_pci(); return xhci_init_hc(&xhc); } |