pci_xhci_assert_interrupt() set IMAN.IP and the ERDP Event Handler Busy flag
and raised an interrupt for every event, even while EHB was already set, that
is while the guest's handler was still working through the Event Ring. Per
xHCI 4.17.2 an interrupter does not interrupt again while the event handler is
busy; it fires again when software clears EHB (an ERDP write) and the ring is
not empty.
Follow that, and have EHB mean what the spec says. While EHB is set only post
the event. When an ERDP write clears EHB, recount the ring and raise again if
events remain, so an event that arrived between the handler's last read and
its ERDP write is not lost. A controller reset returns the interrupter
registers to their defaults so an EHB left by the firmware cannot block the
first interrupt.
Crucially, set EHB only when an interrupt is actually signalled -- IMAN.IE and
USBCMD.INTE both on -- not merely when an event is posted, and signal a pending
interrupt once the guest turns IE or INTE on (xHCI 4.17.2: the interrupt is
asserted while IP, IE and INTE are all set; INTE is stored before the run
posts its port change events). Testing across guests showed this has to be
general: a guest that posts events before enabling interrupts would otherwise
latch EHB with nothing signalled and have every later event held back.
FreeBSD and Linux enable interrupts first and were already correct; Windows
enables the controller and interrupts in one USBCMD write, and macOS programs
IMAN before HCRST and enables IE only after the run -- both need the pending
interrupt delivered when interrupts come on.
Found and fixed in keelOS (keelos.dev).
Signed-off-by: Wanpeng Qian <wanpengqian@gmail.com>
Sponsored by: keelos.dev