The usbip DualSense's ALSA card is minted mid-session-bringup while no seat session is
active, so logind's uaccess ACL never materialises and /dev/snd/controlC*/pcmC* stay
root:audio 0660 with the user in neither. WirePlumber's probe fails EACCES ("spa.alsa:
can't open control for card hw:2: Permission denied"), the card never appears in PipeWire,
no pad sink exists for winepulse to route to — which is why every mmdevapi endpoint in the
GE logs was a punktfunk-speaker and the audio ContainerIds were all GUID_NULL — and
GE-Proton's direct ALSA haptics leg (find_dualsense_haptic_alsa_path) cannot open the PCM
either. Same mechanism the hidraw rules in this file already handle, one subsystem over.
Verified live on .41 (Bazzite f44): installing the rule + udevadm trigger made WirePlumber
adopt the card mid-session — device, Default__Speaker__sink and Mic source all appeared,
under exactly the alsa_output.usb-Sony_Interactive_Entertainment_ name prefix GE matches.
GE-Proton's `hidraw_enable_dualsense_usb_haptics` never enabled the DualSense's USB haptics
mode against our usbip pad — `err:hid:hidraw_device_set_output_report id 2 write failed
error: 2 No such file or directory`, then feature report 0x08 retried forever with
EINVAL/EAGAIN. Adaptive triggers, voice-coil haptics and the speaker are all gated behind
that one enable, so nothing downstream could ever show a result. Five theories were ruled
out by log inspection; the sixth was measured on the live pad on .41 today:
write(hidraw, output 0x02, 48 B) -> 0
ioctl(HIDIOCSFEATURE 0x08, 48 B) -> 0
ioctl(HIDIOCGFEATURE 0x05, 41 B) -> 41
The vendored simulator answered every non-isochronous OUT URB through the IN constructor
with an empty buffer, i.e. `actual_length = 0` — and a debug_assert pinned that as the
rule ("OUT nothing"). vhci_hcd copies the field into `urb->actual_length` verbatim
(`usbip_pack_pdu(pdu, urb, USBIP_RET_SUBMIT, 0)` in `vhci_recv_ret_submit()`) and has no
other source for it, so `usbhid_output_report()` returned 0 as `write()`'s byte count and
`usb_control_msg()` returned 0 for the SET_REPORT data stage. winebus checks `count > 0`,
takes 0 as failure, and prints the thread's *stale* errno — the ENOENT/EINVAL/EAGAIN in the
log were never kernel verdicts. The earlier `/tmp/hidwrite.py` "150/150 ok" was the same
illusion: `os.write` returning 0 does not raise. A real usbip stub reports the real URB's
`actual_length`, which on OUT is the bytes sent.
Fix: `UsbIpResponse::usbip_ret_submit_out_success(header, accepted)` acknowledges the bytes
taken (`data.len()`, which `read_from_socket` sized from `transfer_buffer_length`) with no
payload back; the handler uses it for OUT; the assertion now pins "OUT carries no buffer",
not "OUT claims 0". Two wire-byte tests pin both directions. The Steam Controller 2 shares
this handler, so its OUT writes were being reported as 0 bytes too.
`scripts/usbip-trace-analyse.py` flagged ANY nonzero OUT actual_length as a desync — the
wrong rule (its own framing never reads a payload back on OUT) and one that would have hid
this bug and flagged the fix. It now flags an OUT reply claiming more than it was sent, or
0 against a non-empty write.