Stock-console support is awaiting physical validation, not verified
compatibility. The default adafruit:nrf52:feather52840 build passed with
Adafruit nRF52 core 1.7.0. The updated firmware has been flashed and passed
physical PC USB authentication tests; console acceptance remains unverified
after a reported failure with the earlier build. Native regressions and the PC
probe use synthetic challenges, not real-console captures.
MODE_XBOX (1) implements a wired Xbox 360 USB personality and libxsm3 retail
authentication in software on the existing nRF52840 OpenPuck. No donor
controller, USB host adapter, second board, or additional wiring is required.
The Steam Controller still connects by OpenPuck's RF protocol; this is not an
Xbox 360 wireless receiver. Original Xbox mode (MODE_XBOX_OG, 10) is separate.
Identity: Microsoft 045E:028E, bcdDevice=0x0114, bcdUSB=0x0200, device
class/subclass/protocol FF/FF/FF. A fixed 153-byte configuration contains
four vendor interfaces:
| Interface | Class/subclass/protocol | Role |
|---|---|---|
| 0 | FF/5D/01 |
Control: 20-byte gamepad input on 0x81, rumble/LED OUT on 0x02 |
| 1 | FF/5D/03 |
Audio, no accessory emulation |
| 2 | FF/5D/02 |
Plugin, no accessory emulation |
| 3 | FF/FD/13 |
XSM3 security via EP0 |
The security string at index 4 is 178 bytes (header + 88 UTF-16 code units, no trailing terminator). A linker wrapper avoids Adafruit's shorter string buffer; the full multi-packet USB transfer still needs hardware validation. USB serial and XSM3 identification share a stable 12-character serial derived from the nRF52840 hardware ID, independent of RF bond count or USB resets.
Authentication handles vendor requests to interface 3 (wIndex=3 or 0x0103)
and device-recipient requests (wIndex=0):
| Request | Direction | Purpose / full payload length |
|---|---|---|
0x81 |
IN | Identification, 29 bytes; starts a fresh session |
0x82 |
OUT | Init challenge, exactly 34 bytes |
0x86 |
IN | Status, 2 bytes: state 1 pending, state 2 ready |
0x83 |
IN | Completed response, 46 bytes for init or 22 for verify |
0x87 |
OUT | Verify challenge, exactly 22 bytes; requires a completed session |
Invalid actual transfer lengths, packet length fields, checksums, and MACs fail closed: no successful readiness or stale response is published. Hardware RNG or worker startup failure also fails closed. USB RESET invalidates the session generation; an in-flight crypto operation may finish, but its old result is discarded rather than reused for a new host/session.
Crypto runs in a dedicated idle-priority FreeRTOS task with a 2048-word (8 KiB) stack, not on the small USB callback stack. The main loop lends it 1 ms only while authentication is pending/running, between RF polls and never inside an RX window. This keeps crypto out of USB callbacks but does not establish real-time timing on hardware.
All three linker wrappers are required: tud_vendor_control_xfer_cb for vendor
routing, tud_descriptor_string_cb for the full security string, and
usbd_control_xfer_cb for actual EP0 received lengths. Use make build or the
complete direct Arduino command in BUILD_AND_DEPLOY.md.
Non-hardware checks, from the repository root:
python3 tools/test-xinput.py --check-fixtures
make check
make build
The native harness checks production auth, descriptor/string callbacks, input, rumble, controller selection, and reset/failure handling against independent OpenSSL/Python-generated synthetic exchanges. It does not prove physical enumeration, actual TinyUSB transfers, linker wrapping, FreeRTOS scheduling, hardware RNG, or stock-console compatibility. See ../tests/xinput/README.md for coverage and limits.
Separately, a physically connected ProMicro nRF52840 (045E:028E) passed
PC PyUSB transport testing against the independent OpenSSL/Python oracle on
2026-09-17 after flashing the keepalive fix: descriptor/string checks passed,
init became ready in 64.3 ms, zero-data OUT 0x84 passed in 1.1 ms,
and all three verifies became ready in 28.3–29.0 ms. Responses were checked
using the controller nonce recovered from the hardware reply. This confirms
the fix for the older build's reproducible OUT 0x84 stall.
Stock-console compatibility remains
unverified, with a user-reported console failure; these PC results do not
establish console acceptance.
For an explicit manual repeat, use
../tools/check-xinput-usb.py with --run
(optionally --bus and --address, in decimal). It requires PyUSB, a libusb
backend, USB permissions, and OpenSSL; it installs nothing. It refuses ambiguous
selection or unexpected descriptors/strings before authenticating, but those
checks cannot prove device provenance: connect only the intended OpenPuck, not
a genuine controller. Authentication invalidates any active session;
unplug/replug afterward even on failure. It never detaches drivers, sets the
USB configuration, resets, or flashes the device. Default success requires
zero-data OUT 0x84 after init and before all three verifies.
--skip-keepalive is only an old-build diagnostic, explicitly reports the
skip, and does not validate the fix. The default script run produced the
physical PC test results above.
Record firmware revision, board/core version, console model/dashboard version, USB port/cable, and results. Keep captures of failed as well as successful runs.
0x81 → 0x82 → 0x86 → 0x83, then 0x87 with
subsequent status/response reads. Confirm no ready response before crypto
completes, and repeat authentication where requested by the host.xusb/XInput) and Linux (xpad/SDL),
input and rumble, plus back-4+A configuration and return to Xbox. Confirm
Xbox mode exposes no mouse, WebUSB, CDC, or wake HID.Retain the copyright notices, attribution, disclaimers, and license texts in source and binary distributions as required. Review the vendored revision and local modifications in ../OpenPuck/src/libxsm3/NOTICE.md and the full ../OpenPuck/src/libxsm3/LICENSE.txt. For statically linked firmware, satisfy the applicable LGPL source and relinking obligations: provide the corresponding library source (including modifications) and the object files or complete buildable source and build materials needed to relink with a modified library, using a distribution method permitted by the license. A binary plus a notice alone is not sufficient.