feat(host/steam): harden the gadget feature contract — fixes the evdev churn

The virtual Deck's gamepad evdev was churning (destroyed + recreated) because
Steam kept re-probing: GetControllerInfo reads HID feature reports, and the gadget
served zeros for them. Captured the real contract off a physical Deck
(packaging/linux/steam-deck-gadget/get_deck_attrs.c, hidraw HIDIOCGFEATURE — usbmon
truncates to 32B) and implemented it in steam_gadget.rs::feature_reply:

- 0x83 GET_ATTRIBUTES_VALUES: [83, 2d, 9×(attr-id, u32-LE)] — product id 0x1205, a
  per-instance unit serial (0x0a/0x04, so a gadget never collides with a real Deck
  or another gadget), and the capability attrs (0x09=0x2e, 0x0b=0x0fa0, rest 0).
- 0xAE GET_STRING_ATTRIBUTE: [ae, len, attr, ascii] — serial (attr 1) / board
  serial (attr 0).
- other commands (0x87 settings): echo the last write.

Validated on the Deck: 1 connect / 0 disconnect / 1 gamepad evdev (was constant
churn), Steam activates the gadget cleanly (no GetControllerInfo failed, no zombie)
and emits its X-Box 360 pad. usbmon on the gadget's bus confirms our state reports
(pressed button at byte 8) are delivered on the interrupt-IN and consumed by
hid-steam — so with M1/M2's byte-8→BTN_SOUTH decode the input chain is proven
end-to-end. Remaining: a foreground-game confirmation of Steam Input's XInput
mapping, then default the gadget on for SteamOS.

Workspace clippy/fmt/test green. Not pushed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 16:01:19 +00:00
parent c8e19396e4
commit 7ab8acaf55
4 changed files with 130 additions and 17 deletions
+15
View File
@@ -807,3 +807,18 @@ With the A button held in the streamed report on a `pressa` build, on the Deck:
Conclusion: input delivery + format are proven; the only gap is the gamepad-evdev transience, which is
a **feature-report-completeness** problem — exactly what the host backend fixes (serve the full Deck
feature/attribute contract so Steam stops fighting it). That's the next step, not more PoC patching.
### Feature contract hardened — the churn is fixed (2026-06-29)
The gamepad-evdev churn was Steam re-probing because the gadget served zeros for the HID feature
reports Steam's `GetControllerInfo` reads. The real contract was captured from a physical Deck
(`packaging/linux/steam-deck-gadget/get_deck_attrs.c`, hidraw `HIDIOCGFEATURE`) and implemented in
`steam_gadget.rs::feature_reply`: the **`0x83` GET_ATTRIBUTES_VALUES** blob (`[83,2d, 9×(id,u32-LE)]`
— product id `0x1205`, a per-instance unit serial, capability attrs) plus the **`0xAE`** string
attributes (serial / board serial) and a settings echo. Result on the Deck: **1 connect / 0
disconnect / 1 gamepad evdev** (was constant churn), Steam *activates* the gadget cleanly (no
`GetControllerInfo failed`, no zombie) and emits its **X-Box 360 pad 1**. usbmon on the gadget's bus
confirms our state reports (pressed button at byte 8) are delivered on the interrupt-IN and consumed
by hid-steam — so with M1/M2's byte-8→BTN_SOUTH decode, the input chain is proven end-to-end. The
only piece left is a foreground-game confirmation that Steam Input maps it onto the X-Box pad (Steam
only maps contextually), after which the gadget can default on for SteamOS hosts.