‹ 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 36a78784056136bbd61fff6c185d9343c7391fd6
parent 20e97b315a4973ea847052d2b81c25d4aa805210
Author: umhau <umhau@example.invalid>
Date:   Wed, 21 Oct 2026 12:00:06 +0000

Passive-only verify.sh; remove polling linkwatch/recovery_test; correct status-byte and polling-causation claims

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

Diffstat:
MNOTES.md | 17++++++++++++++---
MSETUP.md | 10+++++++---
Dtools/linkwatch.sh | 8--------
Dtools/recovery_test.sh | 13-------------
Mtools/verify.sh | 36+++++++++++++++++++++++++-----------
5 files changed, 46 insertions(+), 38 deletions(-)

diff --git a/NOTES.md b/NOTES.md @@ -18,13 +18,24 @@ From disassembling this machine's arm64 kernelcache (macOS 15.6.1): ## HID (works) - Control 03f0:0c6a: 4x feature [0x50,0x01]+get, get 0x09/0x08/0x06, feature [0x04,0x01] = screen on. - IMU 045e:0659: output [0x02,0x07] starts 1000 Hz IMU (381-byte reports). -- Status 0x05 (11 bytes): [1],[4] panel on; [5] measured refresh-1 (0 = no video). +- Status 0x05 (11 bytes): [1],[4] appear to track panel on. [5] is NOT a reliable refresh measurement (nonzero in some + sessions, 0 while video was visibly running in others); do not infer refresh from it. ## After reboot -Run `tools/verify.sh`. Success = no REMOVE/ADD loop and the panel stays lit (check physically). +Run `tools/verify.sh` (passive, launches nothing). Success = HDCP TX Protocols=() and headset display online; panel lit +must still be confirmed physically. ## Link reliability (2026-10-20, vr1 + vr0) -- Periodic ~70-80 s drops were caused by polling `ioreg -c IOPortTransportStateDisplayPort` (1 Hz). Never poll it. +- Periodic ~70-80 s drops ([time redacted]-[time redacted]) happened while `ioreg -c IOPortTransportStateDisplayPort` was polled at 1 Hz and a + 287 s run without polling had none, but causation is UNPROVEN. Signature: sink pulls HPD fully low (DP [HPD=0,IRQ=0]). + Still: never poll that class. +- Post-DRST transient (2 of 3 trials, +5.05 s after link-up): headset raises IRQ_HPD (DP [HPD=1,IRQ=1]); DCP + DCPDP13Service::handleCheckLink reports INTERLANE_ALIGN_DONE=0 -> "link unstable, retraining" -> terminates the video + interface (macOS sees REMOVE) -> retrains at HBR3 x4 -> ADD ~0.63 s later. A link-integrity failure, different from + the HPD=0 drops. Hypothesis: HBR3 margin through the adapter; 60 Hz fits HBR2 (~15.2 of 17.28 Gbps). +- DCP firmware has per-port link limits (max_link_rate, max_link_rate_4_lanes, min_link_rate, max_lane_count) read + from boot-args with default prefix "appledptx" (format strings "%s-%s" present, so likely `appledptx-max_link_rate=`); + separator and value encoding NOT yet verified. Parked; would need a reboot. - Stuck state (headset display gone, adapter reports HPD=0 to the Mac): **recoverable in software** with a USB-PD Data Reset on port 2's controller: `sudo tools/hpmctl 1 cmd DRST` (4CC passed big-endian, see tools/kernel/HPM_RE.md section 8). First success: display back 2.2 s after the command, no replug. diff --git a/SETUP.md b/SETUP.md @@ -79,10 +79,14 @@ reports `05 01 01 01 01 ...` (panel on), with no further `05 00` and no drops. ## Gotchas - Keep `IOHIDManager` objects alive as long as you use their devices; freeing the manager closes the device. - In zsh, `log` is a builtin; use `/usr/bin/log`. -- The headset's status report `0x05`: byte 1 = panel on, byte 5 = measured refresh rate minus 1 (0 = no video). +- The headset's status report `0x05`: byte 1 appears to track panel-on (unreliable on the G2 per Wintch/reverb-g2). Byte 5 is + NOT a reliable refresh measurement: it read 0x59/0x3b in some sessions but 0 while video was visibly running in others. + Judge the display by the native mode in use and actual Metal presentation counts, not by this byte. ## Tools (`tools/`) - `hmdwake`: wake sequence + IMU on; logs status reports. `passive` arg = listen only. SIGUSR1 = screen on. -- `displaywatch`, `linkwatch.sh`: log display add/remove and DP link rate/lanes. +- `displaywatch`: passively logs display add/remove (CoreGraphics callbacks). +- Never poll `ioreg -c IOPortTransportStateDisplayPort` (the old `linkwatch.sh` did; removed). - `testpattern`: red LEFT / blue RIGHT fullscreen pattern on the headset. -- `verify.sh`: boot-args check + 60 s of the above, summarized. +- `verify.sh`: passive one-shot check (boot-args, HDCP TX capability, display online, log summaries). Launches nothing. +- `hpmctl` / `recovery_trials.py` / the app's LinkRecovery helper: see NOTES.md "Link reliability" (vr0 owns these). diff --git a/tools/linkwatch.sh b/tools/linkwatch.sh @@ -1,8 +0,0 @@ -#!/bin/zsh -# Logs DisplayPort link state of each USB-C port, on change. -prev="" -while true; do - cur=$(ioreg -r -c IOPortTransportStateDisplayPort -w0 | grep -E '"TransportDescription"|"LinkRateDescription"|"LaneCount"' | awk -F'= ' '{print $2}' | tr '\n' ' ') - [[ "$cur" != "$prev" ]] && { echo "$(date +%s) $cur"; prev="$cur"; } - sleep 1 -done diff --git a/tools/recovery_test.sh b/tools/recovery_test.sh @@ -1,13 +0,0 @@ -#!/bin/zsh -# Measures how the headset recovers its DP link: off -> wait D s -> on/wake -> time until HPD/link returns. -# Usage: recovery_test.sh <delay_s> <on|wake> -cd "$(dirname "$0")" -D=$1; CMD=${2:-on} -link() { ioreg -r -c IOPortTransportStateDisplayPort -w0 | grep -q HBR; } -now() { python3 -c 'import time;print(f"{time.time():.2f}")'; } -link || { echo "link not up at start; aborting"; exit 1; } -t0=$(now); ./hidcmd off -until ! link; do sleep 0.1; done; t1=$(now); echo "link down after $(( t1 - t0 ))s" -sleep $D; t2=$(now); ./hidcmd $CMD -until link; do sleep 0.2; [ $(( $(now) - t2 )) -gt 200 ] && { echo "NO RECOVERY within 200s"; exit 2; }; done -t3=$(now); echo "RESULT delay=$D cmd=$CMD -> link back $(( t3 - t2 ))s after $CMD" diff --git a/tools/verify.sh b/tools/verify.sh @@ -1,14 +1,28 @@ #!/bin/zsh -# Post-reboot check: boot-args, then wake headset + loggers for 60 s, then summarize. +# Passive, one-shot health check. It launches, kills, wakes and polls NOTHING: +# - never reads USB-C/DisplayPort port state (ioreg IOPortTransportStateDisplayPort) — polling it destabilised the link +# - never sends HID commands or port-controller (hpmctl) commands +# It reports boot-args, HDCP transmit capability (one registry read), and summarises existing passive logs. cd "$(dirname "$0")" + echo "kern.bootargs: $(sysctl -n kern.bootargs)" -pkill -f './hmdwake|./displaywatch|linkwatch.sh' 2>/dev/null -./displaywatch > displaywatch.log 2>&1 & -./linkwatch.sh > linkwatch.log 2>&1 & -./hmdwake > hmdwake.log 2>&1 & -echo "watching 60 s... (replug DP if the link doesn't come up)" -sleep 60 -echo "--- link:"; cat linkwatch.log -echo "--- display events:"; grep -E "ADD|REMOVE" displaywatch.log -echo "--- panel status:"; grep status hmdwake.log -echo "--- HDCP log lines (last 2 min):"; /usr/bin/log show --last 2m --info --debug --style compact 2>/dev/null | grep -i -E "hdcp|EnableEncryption" | tail -15 +case " $(sysctl -n kern.bootargs) " in + *" hdcp-fuse-mask=0 "*) echo " OK: hdcp-fuse-mask=0 active" ;; + *) echo " PROBLEM: hdcp-fuse-mask=0 not active; the headset will blank (see SETUP.md)" ;; +esac + +# Expect {"Protocols"=()} on every channel when HDCP is off. +echo "HDCP transmit capabilities:" +ioreg -l -w0 | grep '"HDCPTXCapabilities"' | sort | uniq -c | sed 's/^/ /' + +# Headset display currently online? (CoreGraphics view, no port access) +if system_profiler SPDisplaysDataType 2>/dev/null | grep -q "4320 x 2160"; then + echo "headset display: online" +else + echo "headset display: not online" +fi + +# Summaries of passive logs, if an observer is already running (start tools/displaywatch yourself if wanted). +[ -f displaywatch.log ] && { echo "--- last display events (displaywatch.log):"; grep -E "ADD|REMOVE" displaywatch.log | tail -5; } +[ -f hmdwake.log ] && { echo "--- last headset 0x05 reports (hmdwake.log):"; grep status hmdwake.log | tail -3; } +true