Skip to content

Fix/xhci USB absolute mouse issues#25

Merged
Kelsidavis merged 10 commits intoKelsidavis:mainfrom
stuaxo:fix/xhci-usb-mouse
Feb 14, 2026
Merged

Fix/xhci USB absolute mouse issues#25
Kelsidavis merged 10 commits intoKelsidavis:mainfrom
stuaxo:fix/xhci-usb-mouse

Conversation

@stuaxo
Copy link
Contributor

@stuaxo stuaxo commented Feb 13, 2026

Summary

  • Fix xHCI doorbell register addressing (slot * 4 stride per spec §5.6)
  • Fix ERDP writes to clear Event Handler Busy bit (§5.5.2.3.3)
  • Fix input/device/endpoint context layout (Add flags, slot entries, EP DWord ordering, DCS, context sizing per §6.2)
  • Remove redundant EP0 ring resets and data-stage IOC
  • Fix HID interrupt transfer ring wrapping (link TRB before data TRB)
  • Fix HID enumeration: defer set_interface/set_protocol, preserve slot context
  • Switch HID polling to event-driven re-submit model
  • Add USB tablet absolute pointer support with PS/2 conflict resolution
  • Add XHCI debug logging and %04x format specifier
  • Reduce cursor drawing throttle for responsive tracking (XHCI sends too many events for the original limiter)

stuaxo and others added 10 commits February 13, 2026 21:16
Each xHCI doorbell register is 32 bits wide, so doorbell N lives at
offset DBOFF + N*4 from the BAR base. The code was using DBOFF + N,
which addressed the wrong register for every slot > 0.

Affects all doorbell writes: command ring (slot 0), EP0 control
transfers, HID interrupt submissions, MSC bulk transfers, and UASP
command/data/status transfers.

Ref: xHCI spec §5.6, OSDev xHCI wiki ("Each doorbell register is
32-bits long").

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When updating the Event Ring Dequeue Pointer, the xHCI spec (§5.5.2.3.3)
requires bit 3 (EHB) to be set in the write to acknowledge the event
and clear the busy flag. Without this, the controller considers the
event ring still busy after the first event, stalling further delivery.

The pointer is also masked to 16-byte alignment (& ~0xF) as required
by the spec, since the low 4 bits are flag fields, not address bits.

Affects xhci_poll_cmd_complete, xhci_poll_transfer_complete, and
xhci_poll_transfer_event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multiple interrelated fixes to the xHCI context data structures per
the spec (§6.2):

- Input Control Context: Add flags go in DWord1, not DWord0. DWord0
  is the Drop flags field, which was being set instead.

- Slot Context: Context Entries must be 2 (covering slot + EP0), not
  1. Root Hub Port Number is in bits [23:16] of DWord1, not the raw
  port value.

- Endpoint Context: EP Type, CErr, and Max Packet Size all belong in
  DWord1, not split across DWord0/1. CErr is set to 3 (retry up to 3
  times on error). The TR Dequeue Pointer must have the Dequeue Cycle
  State (DCS) bit set in bit 0.

- Configure Endpoint: the Add flags and Slot Context entries are
  updated to cover the highest endpoint being configured.

- Device context output buffer widened from 32 to 256 dwords per slot.
  The Device Context has up to 32 entries (§6.2.1), each 8 dwords
  (CSZ=0) or 16 dwords (CSZ=1). The old 32 dwords (128 bytes) could
  only hold 4 entries at CSZ=0, causing the controller to write past
  the buffer once endpoints beyond EP0 are configured. 256 dwords
  (1 KB) covers all 32 entries at CSZ=0; 32 KB total for 32 slots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The EP0 transfer ring index advances naturally as TRBs are enqueued,
so resetting it before every control transfer was unnecessary and
could cause the controller to re-process stale TRBs if the ring
hadn't been fully consumed.

Also removes the Interrupt On Completion (IOC) flag from data-stage
TRBs. Only the status-stage TRB needs IOC — setting it on the data
stage caused a spurious transfer event before the transfer was
actually complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Link TRB must be written before the Normal (data) TRB when the
ring is full. The old code wrote data at index 31, then tried to
write a link at index 32 — past the end of the 32-entry ring.

Now checks for wrap first: if ring_index >= 31, writes the Link TRB
at the last slot with the Toggle Cycle bit set, resets the index to
0 and flips the cycle, then enqueues the Normal TRB at the start.

Also fixes the Link TRB's cycle bit to use the current dev->cycle
rather than hardcoding it to always-set, which would cause the
controller to process stale link TRBs on the second lap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ot context

The old code issued SET_INTERFACE and SET_PROTOCOL control transfers
inside the descriptor parsing loop, before the full configuration was
known. This could interfere with the ongoing descriptor read and
caused the endpoint to be configured without the device being in the
correct state.

Defer both requests until after descriptor parsing completes and
SET_CONFIGURATION has been sent. SET_PROTOCOL (boot protocol) is
only sent for keyboards, after SET_INTERFACE.

Also copies the output Slot Context (written by the controller during
Address Device) back into the input context before Configure Endpoint.
Without this, the input context has zeroed speed/port fields and the
controller rejects the command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The old polling model processed only 4 events per call, used tick-
based throttling to limit submit rate, and cleared pending flags on
timeout. This caused the interrupt pipe to drain and stop delivering
events, especially under load.

Replace with an event-driven approach: process up to 32 events per
poll call, and re-submit the interrupt IN transfer immediately after
each completion event. This keeps the pipe continuously armed so the
controller always has a TRB to write the next report into.

Remove the last_submit_tick throttle from xhci_hid_submit and the
pending timeout loop from xhci_hid_poll_events — the host controller
now drives the cadence via completion events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
QEMU presents both a PS/2 mouse and a USB tablet simultaneously.
The PS/2 relative deltas fight the tablet's absolute coordinates,
causing erratic cursor behavior.

Add a HAL mouse-source selector (MouseSource enum in input.h) so
PS/2 mouse bytes are discarded when a USB tablet is present. The
kernel checks for an absolute pointer device after xHCI enumeration
and switches the source accordingly.

Note: this approach is QEMU-specific — both devices mirror the same
host mouse, so processing both causes per-frame jitter. On real
hardware with genuinely separate devices (e.g., PS/2 mouse + Wacom
tablet), a "last writer wins" model would be preferable.

Simplify the xHCI HID mouse report parser: remove the heuristic
that guessed whether byte 0 was a report ID based on value ranges.
The heuristic failed when the tablet reported zero coordinates with
no buttons pressed, causing the first byte to be misinterpreted.

Fix absolute coordinate rounding: add half-divisor before integer
division to round to nearest pixel instead of truncating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add diagnostic logging throughout the xHCI driver to aid
debugging during enumeration and HID polling:
- Transfer poll timeout with slot and event index
- HID interface classification (class/subclass/protocol)
- Mouse endpoint configuration success and failure
- Port status when no device is connected
- Port power settle delay after power-on

Add %04x format handler to SysLogFormatAndSend and register
the XHCI log tag at warn level.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lower the cursor redraw interval from every 50th to every 2nd
main-loop iteration. With event-driven HID polling (commit 7),
mouse reports arrive promptly and the cursor should track them
without perceptible lag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stuaxo stuaxo marked this pull request as ready for review February 13, 2026 22:36
@Kelsidavis Kelsidavis merged commit f8a02f2 into Kelsidavis:main Feb 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants