Repository navigation
fix(usb): EHCI/OHCI/UHCI hot-plug, hubs and failed-port retries - #106
Merged
FengHeting merged 5 commits intoOct 20, 2026
Merged
Conversation
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 <smallcuteshb@outlook.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 <smallcuteshb@outlook.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 "<parent path>.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 "<parent devpath>.<port>"). 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 <smallcuteshb@outlook.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 06255b7: 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 <smallcuteshb@outlook.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rainy101112
approved these changes
Oct 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
EHCI / OHCI / UHCI 的根端口处理有 4 个独立的 bug(xHCI 正常)。每个都先在 QEMU 中复现、再修复验证;4 个提交各修一个:
route the legacy INTx interrupt through the IO-APICpci_request_irq()没设.legacy_base/.legacy_ioapic:handler 装在 CPU 异常向量(< 32)上,且未建 IO-APIC 路由uhci: poll the root ports for connect changesgive root-port devices depth 1-EEXIST注册失败depth为 0,被当成根 Hub,子设备路径丢掉父级(3-1与 Hub 自身重名)ehci: stop retrying a failed root port until reconnectfailed_ports同一问题依据
QEMU
usb_ehci_irqtrace:插入时 PCD 已拉起(level 1, sts 0x4),内核从未读 USBSTS。写法与 e1000 / rtl8139 / rtl8169 一致:.legacy_base = IRQ_0, .legacy_ioapic = 1。Linux
uhci-hcd.cstart_rh()设HCD_FLAG_POLL_RH,注释 "All interrupts are enabled, even though RESUME won't do anything"。worker 本就每 1ms 唤醒,在循环开头读两个 PORTSC。与 xHCI(
device->depth = 1; // Root-port device: depth 1 (root hub depth 0))和 Linuxusb_alloc_dev()的 devpath 规则一致,修复后子设备名为3-1.1。depth在这三个驱动上没有其他用途。故障注入(测试时让
*_enumerate_port()在端口复位后强制失败,不在提交中):只在首次注入失败时:拔出后换盘插入可正常识别并读出数据。OHCI/UHCI 只响应变化位,且其端口复位会清掉自身产生的变化位,因此不需要此修复。
测试(QEMU q35,自动化脚本:串口驱动 guest shell + monitor 插拔;U 盘镜像写入特征文本,用
cat /dev/sdX校验读出的是对应那块盘)#105 + 本 PR:61/61 通过
冷启动(EHCI+U盘,xHCI+U盘+键盘,OHCI+Hub(U盘)+键盘,UHCI+U盘+键盘):4 块盘各自读出正确数据,5 个 input event,日志干净。
仅本 PR(不含 #105):xHCI/OHCI/UHCI 全部通过;EHCI 设备因 #105 的位定义问题无法枚举,但只失败 1 次、系统正常启动(没有提交 4 时会卡死)。两个 PR 可按任意顺序合并。
¹ QEMU 的
usb-hub只有全速,无法接到纯 EHCI 上(speed mismatch)。² 拔除 Hub 时会打印一次
usb-uhci: USB error interrupt:设备消失时进行中的传输按规范报错(QEMUhcd-uhci.cUSB_RET_NODEV→USBERR),属预期;Linux 对此不打印。备注
.config中 OHCI / UHCI 关闭,测试时临时打开;当前 develop 的virtgpu_vq.c编译不过(与本 PR 无关),测试时临时关闭CONFIG_VIRTIO_GPU。msleep()是忙等(nsleep循环cpu_relax),这是提交 4 的循环会饿死 init 的原因。属全局设计,未在本 PR 修改。🤖 Generated with Claude Code