From 3eced3ce1c97bfb03906c795decb1dfcbdcc0a06 Mon Sep 17 00:00:00 2001 From: JiTianYu391 Date: Sat, 17 Oct 2026 17:02:50 +0800 Subject: [PATCH 1/4] usb: ehci/ohci/uhci: route the legacy INTx interrupt through the IO-APIC The three legacy HCDs ask pci_request_irq() for MSI or legacy INTx but leave .legacy_base and .legacy_ioapic at 0. None of these controllers has MSI in QEMU (and few do on real hardware), so they always take the INTx path, where that means: - the handler is installed at vector = 0 + IRQ line, i.e. on a CPU exception vector (< 32); - no IO-APIC redirection entry is added, so the interrupt never reaches the CPU. The controllers still enumerate devices present at boot, because the first worker pass scans every port without an interrupt, but every later port change is lost: hot-plug and unplug do nothing. xHCI is not affected since it uses MSI/MSI-X. Fill in the two fields the same way the other INTx users do (e1000, rtl8139, rtl8169): .legacy_base = IRQ_0, .legacy_ioapic = 1. Tested in QEMU (q35): QEMU's usb_ehci_irq trace shows the PCD interrupt raised on plug while the kernel never read USBSTS; with this change EHCI and OHCI detect plug, unplug and re-plug of a USB stick. Signed-off-by: JiTianYu391 Co-Authored-By: Claude Opus 5.5 --- drivers/usb/host/ehci/ehci.c | 6 ++++-- drivers/usb/host/ohci/ohci.c | 6 ++++-- drivers/usb/host/uhci/uhci.c | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/ehci/ehci.c b/drivers/usb/host/ehci/ehci.c index ef1493bf..f0a01e39 100644 --- a/drivers/usb/host/ehci/ehci.c +++ b/drivers/usb/host/ehci/ehci.c @@ -810,8 +810,10 @@ static int ehci_probe(pci_device_cache_t *pci, uint8_t bus_number) pci_irq_state_t irq_state; pci_irq_request_t request = { - .modes = PCI_IRQ_MSI | PCI_IRQ_LEGACY, - .idt_handler = (void *)ehci_interrupt_handler, + .modes = PCI_IRQ_MSI | PCI_IRQ_LEGACY, + .idt_handler = (void *)ehci_interrupt_handler, + .legacy_base = IRQ_0, + .legacy_ioapic = 1, }; if (pci_request_irq(pci, &request, &irq_state) < 0) return -ENODEV; diff --git a/drivers/usb/host/ohci/ohci.c b/drivers/usb/host/ohci/ohci.c index 4ac5dd11..79aaa81d 100644 --- a/drivers/usb/host/ohci/ohci.c +++ b/drivers/usb/host/ohci/ohci.c @@ -826,8 +826,10 @@ static int ohci_probe(pci_device_cache_t *pci, uint8_t bus_number) pci_irq_state_t irq_state; pci_irq_request_t request = { - .modes = PCI_IRQ_MSI | PCI_IRQ_LEGACY, - .idt_handler = (void *)ohci_interrupt_handler, + .modes = PCI_IRQ_MSI | PCI_IRQ_LEGACY, + .idt_handler = (void *)ohci_interrupt_handler, + .legacy_base = IRQ_0, + .legacy_ioapic = 1, }; if (pci_request_irq(pci, &request, &irq_state) < 0) return -ENODEV; diff --git a/drivers/usb/host/uhci/uhci.c b/drivers/usb/host/uhci/uhci.c index 4bad7163..01e60aeb 100644 --- a/drivers/usb/host/uhci/uhci.c +++ b/drivers/usb/host/uhci/uhci.c @@ -875,8 +875,10 @@ static int uhci_probe(pci_device_cache_t *pci, uint8_t bus_number) pci_irq_state_t irq_state; pci_irq_request_t request = { - .modes = PCI_IRQ_MSI | PCI_IRQ_LEGACY, - .idt_handler = (void *)uhci_interrupt_handler, + .modes = PCI_IRQ_MSI | PCI_IRQ_LEGACY, + .idt_handler = (void *)uhci_interrupt_handler, + .legacy_base = IRQ_0, + .legacy_ioapic = 1, }; if (pci_request_irq(pci, &request, &irq_state) < 0) return -ENODEV; From 419db711411bcd6e95bc730bfb0c78628d58ff3e Mon Sep 17 00:00:00 2001 From: JiTianYu391 Date: Sat, 17 Oct 2026 17:02:51 +0800 Subject: [PATCH 2/4] usb: uhci: poll the root ports for connect changes UHCI has no port-change interrupt. USBSTS.RD (Resume Detect) only fires while the controller is in global suspend, yet uhci_worker() only looked at a port after uhci_interrupt_handler() saw RD. On a running controller that never happens, so after the boot-time scan no plug or unplug was ever noticed, even with working interrupts. Linux handles this the same way: start_rh() sets HCD_FLAG_POLL_RH ("All interrupts are enabled, even though RESUME won't do anything") and the root hub status is polled. The worker already wakes every 1 ms, so read the two PORTSC registers there and treat any CSC/PEC as a pending port. Tested in QEMU (q35, piix3-usb-uhci + usb-storage): plug, unplug and re-plug are all detected; before, none were. Signed-off-by: JiTianYu391 Co-Authored-By: Claude Opus 5.5 --- drivers/usb/host/uhci/uhci.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/uhci/uhci.c b/drivers/usb/host/uhci/uhci.c index 01e60aeb..131eafd4 100644 --- a/drivers/usb/host/uhci/uhci.c +++ b/drivers/usb/host/uhci/uhci.c @@ -725,8 +725,13 @@ static int uhci_worker(void *argument) { uhci_controller_t *ctrl = argument; while (ctrl->running && !kthread_should_stop()) { + /* UHCI has no port-change interrupt (RD only fires in global suspend), so poll the root ports like Linux's POLL_RH. */ + uint64_t polled = 0; + for (uint8_t p = 0; p < UHCI_MAX_PORTS; p++) + if (uhci_readw(ctrl, UHCI_PORTSC1 + p * 2) & (UHCI_PORTSC_CSC | UHCI_PORTSC_PEC)) polled |= 1ULL << p; + uint64_t flags = spin_lock_irqsave(&ctrl->lock); - uint64_t ports = ctrl->pending_ports; + uint64_t ports = ctrl->pending_ports | polled; ctrl->pending_ports = 0; if (!ports && ctrl->running && !kthread_should_stop()) wait_queue_prepare(&ctrl->worker_wait); spin_unlock_irqrestore(&ctrl->lock, flags); From 8c471f05c78edc5467df6c053a96e4a0d5aca3fa Mon Sep 17 00:00:00 2001 From: JiTianYu391 Date: Sat, 17 Oct 2026 17:22:35 +0800 Subject: [PATCH 3/4] usb: ehci/ohci/uhci: give root-port devices depth 1 usb_enumerate_device() follows the xHCI and Linux convention that the root hub is level 0: when the parent hub has depth 0 it names the child "bus-port", otherwise ".port". xhci_enumerate_port() sets depth = 1 on root-port devices, but the EHCI/OHCI/UHCI root-port devices come from calloc() and stay at depth 0. So a device behind a hub on one of these controllers lost the hub's level in its name: the device on port 1 of a hub that sits on root port 1 was named "3-1", the hub's own name, and failed to register with -EEXIST: usb-hub: 3-1 port 1 enumerate failed: -17 usb: 4-1: device model registration failed: -17 Set depth = 1 in the three root-port enumerate paths, as xHCI does, so the child becomes "3-1.1" (Linux usb_alloc_dev(): "%d-%s" with devpath "."). depth has no other user on these controllers. Tested in QEMU (q35): cold boot with a USB stick behind a usb-hub on OHCI root port 1, plus UHCI (where QEMU inserts a hub on its last free port), now enumerates every device; before, both failed with -EEXIST. Signed-off-by: JiTianYu391 Co-Authored-By: Claude Opus 5.5 --- drivers/usb/host/ehci/ehci.c | 1 + drivers/usb/host/ohci/ohci.c | 1 + drivers/usb/host/uhci/uhci.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/usb/host/ehci/ehci.c b/drivers/usb/host/ehci/ehci.c index f0a01e39..b6ebe510 100644 --- a/drivers/usb/host/ehci/ehci.c +++ b/drivers/usb/host/ehci/ehci.c @@ -546,6 +546,7 @@ static int ehci_enumerate_port(ehci_controller_t *ctrl, uint8_t port) device->speed = speed; device->bus_number = ctrl->bus_number; device->port_number = port + 1; + device->depth = 1; // Root-port device: depth 1 (root hub depth 0) device->hcd_ops = &ehci_hcd_ops; device->hc_private = ctrl; device->dev.release = ehci_usb_device_release; diff --git a/drivers/usb/host/ohci/ohci.c b/drivers/usb/host/ohci/ohci.c index 79aaa81d..7767dbfd 100644 --- a/drivers/usb/host/ohci/ohci.c +++ b/drivers/usb/host/ohci/ohci.c @@ -554,6 +554,7 @@ static int ohci_enumerate_port(ohci_controller_t *ctrl, uint8_t port) device->speed = speed; device->bus_number = ctrl->bus_number; device->port_number = port + 1; + device->depth = 1; // Root-port device: depth 1 (root hub depth 0) device->hcd_ops = &ohci_hcd_ops; device->hc_private = ctrl; device->dev.release = ohci_usb_device_release; diff --git a/drivers/usb/host/uhci/uhci.c b/drivers/usb/host/uhci/uhci.c index 131eafd4..c7cc9de2 100644 --- a/drivers/usb/host/uhci/uhci.c +++ b/drivers/usb/host/uhci/uhci.c @@ -624,6 +624,7 @@ static int uhci_enumerate_port(uhci_controller_t *ctrl, uint8_t port) device->speed = speed; device->bus_number = ctrl->bus_number; device->port_number = port + 1; + device->depth = 1; // Root-port device: depth 1 (root hub depth 0) device->hcd_ops = &uhci_hcd_ops; device->hc_private = ctrl; device->dev.release = uhci_usb_device_release; From 36df877483434ceb62c8bfd584e948116a7384c9 Mon Sep 17 00:00:00 2001 From: JiTianYu391 Date: Sat, 17 Oct 2026 17:37:52 +0800 Subject: [PATCH 4/4] usb: ehci: stop retrying a failed root port until reconnect Once the EHCI interrupt is delivered, a device that fails enumeration is retried forever. Every attempt starts with a port reset, and the reset itself raises PCD (QEMU's usb_port_reset() detaches and re-attaches the device); the worker then sees "connected, no device" and enumerates again. Because msleep() busy-waits, the loop also keeps a CPU fully busy: with a failing device on EHCI the boot never reached the shell. Do what xHCI does since 06255b70: remember the port in failed_ports when enumeration fails, ignore its further change events, and forget it only when the device goes away (CCS = 0) or a new connection is reported (CSC = 1). OHCI and UHCI do not need this: they only act on a change bit, and their port reset clears the change bits it causes. Tested in QEMU (q35) by forcing *_enumerate_port() to fail after the port reset (test-only change, not part of this commit): failures in 15 s shell EHCI before 54 no EHCI after 1 yes OHCI 1 yes UHCI 1 yes With the fault injected only on the first attempt, unplugging the device and plugging in another one enumerates it and reads its data. Signed-off-by: JiTianYu391 Co-Authored-By: Claude Opus 5.5 --- drivers/usb/host/ehci/ehci.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/ehci/ehci.c b/drivers/usb/host/ehci/ehci.c index b6ebe510..eb7312ad 100644 --- a/drivers/usb/host/ehci/ehci.c +++ b/drivers/usb/host/ehci/ehci.c @@ -70,6 +70,7 @@ typedef struct ehci_controller { usb_device_t *devices[EHCI_MAX_PORTS]; ehci_periodic_transfer_t *periodic[EHCI_MAX_PERIODIC]; uint64_t pending_ports; + uint64_t failed_ports; spinlock_t lock; raw_spinlock_t io_lock; wait_queue_t worker_wait; @@ -661,13 +662,22 @@ static int ehci_worker(void *argument) uint32_t portsc = ehci_read32(ctrl->operational, offset); uint32_t change = portsc & EHCI_PORT_CHANGE_BITS; if (change) ehci_write32(ctrl->operational, offset, portsc); + uint64_t port_mask = 1ULL << port; if (!(portsc & EHCI_PORT_CCS)) { + ctrl->failed_ports &= ~port_mask; if (ctrl->devices[port]) ehci_disconnect_port(ctrl, port); - } else if (!ctrl->devices[port] || (portsc & EHCI_PORT_CSC)) { + } else { + if (portsc & EHCI_PORT_CSC) ctrl->failed_ports &= ~port_mask; + if (ctrl->failed_ports & port_mask) continue; + if (!(portsc & EHCI_PORT_CSC) && ctrl->devices[port]) continue; if (ctrl->devices[port]) ehci_disconnect_port(ctrl, port); msleep(100); int ret = ehci_enumerate_port(ctrl, port); - if (ret != EOK) plogk("usb-ehci: Port %u enumeration failed: %d\n", port, ret); + if (ret != EOK) { + /* The port reset of this attempt raises PCD again; retry only after a new connection. */ + ctrl->failed_ports |= port_mask; + plogk("usb-ehci: Port %u enumeration failed: %d; waiting for reconnect\n", port, ret); + } } } ehci_service_periodic(ctrl);