Skip to content

dongle: dead client is never reaped after failed GIP handshake — occupied slot freezes pairing scan, controller can never reconnect or pair (permanent wedge until replug) #210

Description

@gondii

System

  • OS: Arch Linux, kernel 7.1.3-arch1-2
  • xone: 0.5.8.r0.gf2aa9fe (AUR xone-dkms-git), DKMS build matches running kernel
  • Dongle: Microsoft Xbox Wireless Adapter (045e:02e6), official, behind a USB 2.0 hub
  • Controller: Xbox Elite Series 2 (045e:0b00, firmware 5.22.16.0)
  • USB autosuspend disabled (usbcore.autosuspend=-1), TLP inactive

Summary

If a controller associates with the dongle but the GIP handshake fails to complete (e.g. because the pairing channel scan switches channels mid-handshake, or during the unstable post-firmware-load window at boot), the half-created client stays in the dongle's client table forever. The driver has no mechanism to reap it, and the occupied slot then freezes the pairing channel scan (xone_dongle_pairing_scan skips hopping when client_count > 0 — dongle.c:346 in 0.5.8). The dongle sits on one fixed channel indefinitely, so the controller can neither reconnect with its stored pairing nor re-pair via the sync button. The wedge is permanent until the driver instance is destroyed (physical replug or authorized 0/1 power-cycle).

The poweroff sysfs attribute cannot clear the ghost either: it sends a power-off packet to a client that is no longer listening, then xone_dongle_power_off_clients waits for client_count to reach 0 and returns -ETIMEDOUT. The slot stays occupied.

Observed wedged state

  • …:1.0/gip0 exists (radio up), pairing reads 0
  • active_clients reads Active clients: 1 with slot [00]* occupied
  • No gip0.* child devices under gip0/, no input device, no gip events in dmesg
  • Sync-button pairing always fails: controller flashes fast, dongle reacts, controller falls back to slow flash. With dynamic debug enabled there are zero xone_dongle_pairing_scan: channel switch lines while pairing — the scan never hops because of the occupied slot.
  • echo -1 > powerofftee: … Connection timed out, slot still occupied

How the ghost gets created (reproduction)

  1. Controller is powered on and searching while the dongle loads firmware (typical after boot or replug — made more likely by the automatic 60 s pairing window from dongle, mt76: fix cold/warm firmware init and enable automatic controller reconnection #176).
  2. Controller associates; occasionally the GIP handshake breaks (my guess: a channel switch lands between association and handshake completion — the exact race described in the comment above the client_count guard in xone_dongle_pairing_scan).
  3. Controller gives up (slow flash). Dongle keeps the dead client forever → wedged as above.

Not deterministic, but on my system it happened on roughly every second boot/replug with the controller already on. 100% avoidable by keeping the controller off until the firmware load settles, which supports the mid-load-race explanation.

Healthy trace for comparison (dynamic debug)

With the controller off during the power-cycle, everything works — stored pairing intact, no sync needed:

xone_dongle_pairing_handler: enabled=1
xone_dongle_pairing_scan: channel switch 1 -> 6
... (full 12-channel cycle) ...
xone_dongle_pairing_scan: channel switch 165 -> 1
xone_dongle_handle_event: XONE_DONGLE_EVT_ADD_CLIENT
xone-gip gip0: gip_create_adapter: registered
xone_dongle_add_client: wcid=1, address=7e:ed:83:6a:d0:cc
xone-gip gip0: gip_handle_pkt_announce: address=…, vendor=0x045e, product=0x0b00
[full handshake, input devices created ~1 s later]

In the wedged state the ADD_CLIENT happened at some earlier point but the announce/handshake never completed, and nothing ever cleans up.

Suggested fixes

  1. Reap clients whose GIP handshake does not complete within a timeout (a few seconds after ADD_CLIENT without a completed announce → remove client, decrement client_count). This directly unfreezes the pairing scan.
  2. Additionally/alternatively: let poweroff (or a new sysfs attribute) force-remove a slot locally when the client does not respond, instead of returning -ETIMEDOUT and keeping the slot.

Possibly related: #195 (association succeeds, handshake stalls after auto-sleep — different trigger, but the "half-connected client" state looks like the same underlying gap).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions