No description
  • Shell 56.2%
  • C 41.7%
  • Makefile 2.1%
Find a file
gabrielgad 8103cea7eb
All checks were successful
CI / build (push) Successful in 8s
build: give the helpers a Makefile and CI
xpad360 is a runtime dependency of gadgetd, which spawns one process per seat
pad via --xpad360 /usr/local/bin/xpad360. It had neither a build system nor CI:
the build was a gcc line in a README comment, so the binary in production was
traceable to no commit and checked by nothing. splitux-together v0.13.0 closed
that same gap for gadgetd and tightened the coupling between the two, which
left this the weak link.

Makefile carries the three helper builds that were prose. `make check` rebuilds
with -Werror -Wextra, which they pass today; that is worth blocking on here
because these drive raw_gadget and dummy_hcd, where a sloppy pointer or a
truncated read wedges the host rather than returning a wrong answer.

CI builds under those flags, asserts the outputs are real ELFs, and parses the
shell scripts. It does NOT try to bind a gadget: that needs dummy_hcd,
/dev/raw-gadget and root, none of which exist in a build container. A tag now
produces an artifact so a deployed xpad360 traces to a commit.

Runner: label xpad-gadget-ci, an ephemeral podman poller on the lab following
the fleet convention (deps baked into the image, no `container:` directive).
2026-08-08 14:11:07 -04:00
.github/workflows build: give the helpers a Makefile and CI 2026-08-08 14:11:07 -04:00
vm xpad360: gadget-slam and VM stress harness 2026-07-26 18:25:05 -04:00
.gitignore xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
eps_dump.c xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
evread.c xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
gadget-slam.sh xpad360: gadget-slam and VM stress harness 2026-07-26 18:25:05 -04:00
Makefile build: give the helpers a Makefile and CI 2026-08-08 14:11:07 -04:00
README.md xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
smoke-feed.sh xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
smoke-host.sh xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
smoke-oc2-play.sh xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
smoke-oc2-shots.sh xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
smoke-oc2.sh xpad-gadget v0.1.0: userspace USB Xbox 360 controller emulator 2026-06-24 06:26:25 -04:00
xpad360.c xpad360: gadget-slam and VM stress harness 2026-07-26 18:25:05 -04:00

xpad-gadget — userspace USB Xbox 360 controller emulator

Presents a USB device (VID 045e / PID 028e, interface class FF / subclass 5D / proto 01) over /dev/raw-gadget bound to the dummy_hcd UDC, so the in-kernel xpad driver binds it and creates a real USB-attached evdev node named "Microsoft X-Box 360 pad".

Why

Overcooked 2 (Unity / old InControl) binds gamepads by name AND then NULL-deref crashes on any virtual (uinput/uhid) controller — it only accepts a genuinely USB-attached xpad device. splitux-together's seat pad is uinput, so OC2 can't use it. This emulates a real USB Xbox 360 pad in userspace so xpad binds it; the seat input is then fed in as Xbox 360 USB report packets. See memory joydev-replacement-cuse-jsshim.md for the full root-cause history.

⚠️ Safety

raw_gadget + dummy_hcd is a kernel fuzzing-grade interface. A buggy gadget can hard-panic the host kernel (it did once, during development — full reboot). All development happens in a throwaway VM that boots the host CachyOS kernel, where a panic only kills the guest. Do not iterate gadget descriptor/control-flow changes on bare metal.

Files

  • xpad360.c — the emulator. Build: gcc -O2 -Wall -o xpad360 xpad360.c -lpthread -lm.
    • --demo : auto-wiggle sticks + tap A (visual input proof).
    • default : neutral; drive via stdin text commands (A/B/X/Y/LB/RB/START/UP/... , LX <n>, LT <n>, REL, Q).
    • --buggy: deliberately reproduce the original bare-metal failure (bad IN endpoint addr 0x05 + abrupt exit on ep0 error) — forensic use only.
  • eps_dump.c — dumps the dummy_hcd endpoint table via EPS_INFO (debugging).
  • vm/ — the throwaway test VM harness:
    • build.sh [init] [out.cpio.gz] — builds static xpad360 + RAM-only initramfs from host files (busybox + the host's .ko modules). No network, no disk.
    • run.sh [serial.log] [timeout] — boots /boot/vmlinuz-linux-cachyos in KVM (-cpu host, panic=1 -no-reboot), captures serial (incl. any panic).
    • init — default 3-pass stress test (bind / TERM / KILL / re-bind).
    • init-forensic — reproduces the exact -71/EBUSY host signature, then recovers.
    • init-ffstress.sh + ff_consumer.c — desktop-consumer rumble/read stress.

Status (2026-06-23)

VM-validated on kernel 7.0.12-1-cachyos: xpad binds the emulated pad (real USB sysfs parent …/dummy_hcd.0/usb1/1-1/1-1:1.0/…), input flows (~2976 B/2s via the demo), and it survives graceful + abrupt (kill -9) teardown, re-bind, wedged-UDC recovery, and a force-feedback consumer hammering during teardown — no panics. The original host crash's exact error signature was reproduced in the VM and is survivable; the host panic itself did not reproduce (likely timing/heap or a live-desktop interaction the headless VM lacks) → residual non-zero risk for a bare-metal run remains.

Endpoint addressing note

dummy_hcd's EPS_INFO.addr is the bare endpoint number (direction is in caps). Fixed-number endpoints are 1,2,5,6,7,10,11,12,15; the ep-X endpoints (addr 0xff) take any address. We advertise IN 0x83 / OUT 0x04 (numbers 3/4, used by no fixed endpoint) so EP_ENABLE resolves to a generic ANY endpoint with no type ambiguity. Control OUT status stages MUST use EP0_READ (not EP0_WRITE, which returns EBUSY) — this was half of the original bug.

Next

  1. Careful one-shot bare-metal validation of the known-good binary (quiet desktop / TTY, fresh modprobe), confirm xpad binds on the host, then launch OC2 local:gamepad against it and confirm it boots + reads input WITHOUT crashing.
  2. Integrate into seat-streamer: port VirtualGamepad to drive this raw-gadget USB device (feed seat state as Xbox 360 reports); keep kbd/mouse on uinput.