summaryrefslogtreecommitdiff
path: root/sys/dev/usb/xhci.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/usb/xhci.c')
-rw-r--r--sys/dev/usb/xhci.c172
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);
}