A backend delivers completions and hotplug events through hci_intr /
hci_event from its own thread (usb_mouse: the console thread; usb_passthru:
its libusb and hotplug threads), and usb_passthru does so holding the
endpoint's xfer lock, which the vCPU takes under sc->mtx. So the callbacks
cannot take sc->mtx; give each piece of state they touch its own leaf lock:
- the endpoint's transfer ring state and queued blocks: the endpoint's xfer lock. device_doorbell() takes it before reading ep_ringaddr/ep_ccs and the stream rings; handle_transfer() and try_usb_xfer() run with it held (try_usb_xfer() used to lock it again inside handle_transfer(), which on the default error-checking mutex failed and then released the lock early). Reset/Stop Endpoint and Set TR Dequeue update the ring under it.
- the port state: port_mtx.
- the interrupter state (IMAN.IP, ERDP.EHB, USBSTS.EINT, INTx/MSI): intr_mtx, taken by assert/deassert_interrupt() and the IMAN/ERDP writes.
- USBSTS: atomic.
- the event ring: event_mtx (D59779).
The lock order is sc->mtx -> xfer lock -> { port_mtx | event_mtx | intr_mtx }.
Without this the tablet's reports (console thread) walked the transfer ring
and wrote the event ring concurrently with a vCPU doorbell: on a Windows 10
guest driven over remote desktop, fast pointer input made Windows reset the
controller again and again (Stop Endpoint then HCRST) and finally drop the
tablet.