‹ projects

reverb-m1

Native wired VR on Apple silicon: Reverb G1 display bring-up and stereo demo
git clone https://umhau.org/code/reverb-m1.git
Log | Files | Refs

commit 8fc49b4af4a755acf9db8a53cdf6224c6cde7f5c
parent 7a04b49062d6c59e7a68ea132da01aa922096b11
Author: umhau <umhau@example.invalid>
Date:   Wed, 21 Oct 2026 12:00:14 +0000

Docs: recovery queue, SkyLight main-thread wait, refuted release-in-callback, DATA1=0x04, current status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Diffstat:
MNOTES.md | 14++++++++++++++
MSETUP.md | 23++++++++++++++++-------
2 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/NOTES.md b/NOTES.md @@ -46,6 +46,20 @@ Run `tools/verify.sh` (boot-args + log summaries only; no ioreg/system_profiler) before the byte order was fixed. With correct encoding HRST is rejected by the firmware (!CMD), DRST works. - HID screen_off (0x04 0x00) reliably induces the stuck state; screen_on / full wake alone do not undo it. +## App recovery path (2026-10-20, vr0 implementation, vr1 review) +- macOS SkyLight blocks the app's MAIN thread for exactly ~10.0 s right after delivering a display REMOVE + (sample: CGSPostLocalNotification -> SLSWaitThroughReconfigurationCycleForcingNotificationHandling -> + connection_fetch_updated_event_queue). Not triggered by our code; IMU callbacks on main stall with it. +- Fix: recovery state machine on its own serial queue, fed directly from the CG callback before the main hop; + rendering on a dedicated CAMetalDisplayLink thread. Forced outage 14.4 s -> 4.4 s. +- REFUTED hypothesis (vr1): "the window server waits for us to release the captured display". CGDisplayRelease inside + the REMOVE callback returns 1001 (display already gone) and outages stayed ~4.6 s. Removed. +- Helper result: DATA1 byte0 = 0x04 observed after a DRST whose IOReturn was 0 and after which the display returned. + Meaning of 0x04 unknown (TI documents 0 = success, 1 = timeout/abort, 3 = rejected). The helper conservatively + exits 1; the app relies on display ADD. +- Remaining outage = 2 s grace (avoids resetting during 0.6-0.8 s self-heals) + ~2.2 s DRST/retrain/re-add. + Millisecond continuity would need the HPD-blip ride-through below (untested, needs a reboot). + ## Candidate: ride through brief HPD drops (millisecond-level, no display REMOVE) — static, UNTESTED DCP firmware (FUD/DCP.img4, t8103) AppleDCPDPTXController config, verified in disassembly: - 0x56c0c: key `hot_unplug_delay_ms` -> boot-arg `appledptx-hot_unplug_delay_ms` (prefix + "-" + key, see above). diff --git a/SETUP.md b/SETUP.md @@ -70,10 +70,15 @@ zsh tools/build.sh # builds the app + the signe - `tools/build.sh` builds the app with `swift build`, then compiles `tools/hpmctl.c` into the same directory as the app executable (`swift build --show-bin-path`) and ad-hoc signs it with `tools/hpmctl.entitlements` (`com.apple.USBCEntitlement`). -- The app runs as a normal non-root user. When the headset display is lost and does not return within a short +- The app runs as a normal non-root user. When the headset display is lost and does not return within a 2 s grace period, it runs that helper directly to send a USB-PD Data Reset to port 2's controller. No sudo, no password: the kernel admits root OR that entitlement, and AMFI being off (`amfi_get_out_of_my_way=1`) lets an ad-hoc signature carry it. Limits: 3 s helper timeout, at most 3 resets per minute. +- Recovery runs on its own serial queue, fed directly from the CoreGraphics display callback (before any main-thread + work), because macOS's SkyLight blocks the main thread for ~10 s during a display reconfiguration + (`SLSWaitThroughReconfigurationCycleForcingNotificationHandling`). Any headset display ADD cancels a pending reset. +- The helper reports failure (exit 1) unless the controller's DATA1 result is 0. A result of 0x04 has been seen after + an accepted DRST that still restored the display; the app treats ADD, not the helper's exit code, as proof. - The app captures the headset display (no desktop on it) and uses its native unscaled 4320x2160 @ 90 Hz mode. If macOS changes the mode by itself, the app restores 90 Hz (also checked every 10 s, CoreGraphics only). - Rendering runs on a dedicated "Headset Metal rendering" thread: a `CAMetalDisplayLink` on that thread's run loop @@ -83,12 +88,16 @@ zsh tools/build.sh # builds the app + the signe - Health line every 10 s in the app log: `submitted`, `presented` (only frames Metal reports with a real presentation time), `skipped`, `fps` (presented per second), `presentAge`, `GPUms` per frame, `GPUerrors`, IMU sample count. Healthy looks like `fps=90.00 presentAge=0.01s GPUerrors=0` with `skipped` not increasing after startup. -- Status (2026-10-20): automatic recovery verified (3/3 trials at 90 Hz, then from a stuck state with the render - worker). Render worker 35 s test: rendering/stats good (89.90 presented fps, presentAge 0.01 s, GPU 1.47 ms/frame, - 0 errors, 12 skips at startup only) BUT the process crashed at shutdown (exit -11: double CAMetalDisplayLink - invalidate in MetalRenderLoop.run), so the test is NOT counted as passed. The user confirmed the 3D scene inside - the headset (twice). A 660 s soak with injected faults is in progress (`evidence/final-metal-worker-soak.txt`); - not final proof until the shutdown crash is fixed. +- Status (2026-10-20, ~[time redacted]): + - Stereo 3D output was confirmed in hardware testing. + - Shutdown crash (exit -11, double CAMetalDisplayLink invalidate) fixed; 2 clean-exit/restart trials exit 0. + - Fault soak (`evidence/final-metal-worker-soak.txt`, build before the recovery queue): 3 forced outages all + recovered; after them ~7 min untouched with zero drops, 90.00 presented fps, GPU ~1.5 ms/frame, 0 GPU errors. + - Forced-outage duration (display lost -> back): 14.4 s before the recovery queue, 4.4 s with it + (= 2 s grace + ~2.2 s reset/retrain/re-add). Releasing the capture inside the callback did not help + (CGDisplayRelease returns 1001 there; outages 4.6 s) and was removed. + - Every DRST is followed ~5.7 s later by a brief lane-sync blip (IRQ_HPD/INTERLANE retrain) that the app recaptures. + - A no-fault 660 s soak at 90 Hz is in progress; not yet final proof. ## How the boot-arg was found `amfi_get_out_of_my_way` controls code signing, not HDCP, so we searched this Mac's own kernel: