Describe the bug
On Linux, a Valve Index HMD serves its EDID exactly once per power cycle. The first time the DisplayPort link is torn down (which happens at the end of every connector detection, and on every suspend and shutdown) the headset stops answering EDID reads for the rest of that power cycle. Every read afterwards fails, the connector falls back to a synthesized 640x480 mode with non_desktop=0, and no OpenXR runtime can use it. The failure is observed at the DRM layer (empty EDID, non_desktop=0 in sysfs), below any runtime, so it is not specific to SteamVR or Monado.
The state survives a full host power-off, because the headset is powered by its breakout box rather than the host. This is why booting with the headset attached fails every time: the previous session's teardown already wedged it.
Crucially, only the EDID path is affected. The DisplayPort AUX channel keeps working normally. This points at the headset's EDID/DDC circuitry (the I2C EEPROM behind the AUX-to-I2C bridge), not at the link or the host.
This is not GPU or driver-specific
The same failure is documented on NVIDIA hardware, persisting across driver versions 570.86.16, 570.133.07, and 580.65.06. Additionally, independently confirmed on AMD in that same thread with the reporter concluding the fault likely originates in the headset itself:
https://forums.developer.nvidia.com/t/valve-index-initialized-in-unusable-state-on-boot/324710
The report below is from my AMD system, but the behavior appears to be identical across vendors, which is consistent with a defect in the headset firmware/hardware rather than in any one graphics driver.
To Reproduce
Set p to the headset's DisplayPort connector name and the paths are written in terms of it. For my system I use DP-1 but be sure to substitute yours (ls /sys/class/drm/ lists the connectors):
p=DP-1 # the headset's connector. CHANGE to match your system
c=$(echo /sys/class/drm/card*-$p) # connector sysfs dir (e.g. `/sys/class/drm/card1-DP-1`)
pci=$(basename "$(readlink -f "${c%-$p}/device")") # GPU PCI address (e.g. `0000:2b:00.0`)
d=/sys/kernel/debug/dri/$pci/$p # connector debugfs dir (e.g. `/sys/kernel/debug/dri/0000:2b:00.0/DP-1`)
aux=/dev/$(basename "$c"/drm_dp_aux*) # its AUX chardev, derived from the connector (e.g. `/dev/drm_dp_aux0`)
Ensure the headset starts from a baseline healthy state:
# Suppress DRM detection so the hotplug from plugging in does not consume the
# sink's one good EDID read. Note: the edid file in sysfs is cached; you must force a real
# re-probe to see the true state.
echo off | sudo tee $c/status
# Power-cycle the headset: unplug from the breakout box, reconnect.
# At this point the headset should be healthy.
Trigger the issue by reading the EDID multiple times:
# Confirm nothing probed it — no new "EDID err", cache still empty:
wc -c <$c/edid # Output = 0. Reading cached EDID
journalctl -k -n 5 | grep -i edid
# First probe: reads correctly.
echo detect | sudo tee $c/status
echo 1 | sudo tee $d/trigger_hotplug
wc -c <$c/edid # Output = 256
# Second probe. The ONLY thing that happened in between is the link teardown at
# the end of the first probe
sleep 2 # See timing note below
echo 1 | sudo tee $d/trigger_hotplug
wc -c <$c/edid # Output = 0
journalctl -k -n 5 | grep -i edid # EDID err: 2 (it never recovers this power cycle)
Timing note: a re-probe issued immediately after the first can still return the valid 256-byte EDID (whether from hotplug debounce or the sink wedging with a short lag). Give it a brief pause before the confirming probe. Trust a fresh EDID err: 2 / No EDID read in dmesg over the cached edid byte count.
Evidence that AUX is fine and D3 is not the cause
The link teardown that wedges the sink does two things: it writes the sink to D3, then disables the PHY. Two controls isolate which half matters, and each one must start from a healthy headset. Native DPCD reads on $aux go straight to the receiver and answer in any state, so the baseline read below needs no special setup.
D3 alone does not wedge it. Re-establish the baseline healthy state, then drive the sink D0 → D3 by hand (leaving the PHY up) confirming with a read-back that the power state actually changes:
# Redo the "baseline healthy" steps from the reproducer above
# Perform native DPCD read on a healthy headset for reference
sudo dd if=$aux bs=1 count=16 2>/dev/null | xxd # 00000000: 1114 c401 0100 0100 0202 0000 0000 0000
# Force D0 (awake), then confirm the write
printf '\x01' | sudo dd of=$aux bs=1 seek=$((0x600)) conv=notrunc
sudo dd if=$aux bs=1 skip=$((0x600)) count=1 2>/dev/null | xxd # 00000000: 01
# Force D3 (power down), then confirm the write
printf '\x02' | sudo dd of=$aux bs=1 seek=$((0x600)) conv=notrunc # force D3, PHY stays up
sudo dd if=$aux bs=1 skip=$((0x600)) count=1 2>/dev/null | xxd # 00000000: 02
# Probe the EDID still reads. The D0 to D3 transition did not cause the wedge
echo detect | sudo tee $c/status
echo 1 | sudo tee $d/trigger_hotplug
wc -c <$c/edid # Output = 256
A PHY disable does wedge it and AUX survives. Re-establish the baseline healthy state, then let one detection read the EDID; the teardown at the end of that probe disables the PHY:
# Redo the "baseline healthy" steps from the reproducer above
echo detect | sudo tee $c/status
echo 1 | sudo tee $d/trigger_hotplug ; wc -c <$c/edid # 256: first read OK
sleep 2
echo 1 | sudo tee $d/trigger_hotplug ; wc -c <$c/edid # 0: PHY disable wedged EDID (EDID err: 2)
# Native DPCD read on the now-wedged sink (identical reference above)
sudo dd if=$aux bs=1 count=16 2>/dev/null | xxd # 00000000: 1114 c401 0100 0100 0202 0000 0000 0000
sudo dd if=$aux bs=1 skip=$((0x600)) count=1 2>/dev/null | xxd # power state: D0 = 01 / D3 = 02 (responds either way)
Both controls leave the sink in D3; only the second disabled the PHY, and only the second wedged it, so the trigger is the PHY disable, not D3. The DPCD reads are byte-identical before and after: the receiver stays up, only the EDID EEPROM (I2C-over-AUX to 0x50) stops answering, never native DPCD/AUX. The sink loses its EDID bridge because the link went electrically idle, which DisplayPort sinks should tolerate.
Note that trigger_hotplug does two things in one step: it forces a real re-probe (invalidating the cached edid file) and it ends in the PHY-disabling teardown. Unfortunately, there is no userspace way to separate them, which is why the controls above isolate D3 from the PHY disable through the direct DPCD writes.
System Information
- HMD: Valve Index (USB 28de:2300)
- GPU: AMD Navi 48 (Radeon RX 9070 XT), amdgpu, Display Core 3.2.384
- Distribution: CachyOS
- Kernel: 7.2.2-1-cachyos
- Compositor: KWin (Wayland)
- Steam client: build 1788652215 (2026-09-05)
- SteamVR: build 25014636 (2026-08-29)
Recovery
The only things that restore the EDID are a power cycle of the headset:
- physically unplugging it from the breakout box and reconnecting, or
- the fixvr udev rule's HID reboot, which is effectively a software power cycle (https://github.com/MiguVT/fixvr). Sending the 64-byte HID reboot payload restarts the headset firmware; a fresh detection afterwards reads a valid EDID. Confirmed with detection suppressed so the result is not a cached read.
Nothing host-side repairs the sink short of a power cycle: USB reset, re-enumeration, and writing D0 to DPCD 0x600 all leave the EDID EEPROM unreadable.
There is, however, a host-side way to make the headset fully usable without repairing the sink: inject a known-good EDID so nothing ever asks the dead EEPROM (see below). After either a power cycle or that injection, the headset works end-to-end (verified by using both Monado and SteamVR). The AUX channel and link training are fine; only the EDID fetch was ever broken. See "Host-side workaround" for more details.
Expected behavior / Potential fix
If the headset kept its EDID/DDC circuitry responsive across a DisplayPort PHY disable (as, I believe, every other DP sink does) none of the downstream Linux breakage would occur. The request is for Valve to investigate whether this can be fixed in headset firmware.
It's not possible to tell from the outside whether the EDID EEPROM losing state on PHY disable is correctable in the firmware or is a hardware power-sequencing property of the tether/breakout design. Valve's knowledge of the hardware would settle that.
Host-side workaround
Since the AUX channel and link stay healthy, injecting the headset's EDID is enough to use it as the dead EEPROM is never consulted. To do this, an EDID dump from the Valve Index HMD in a healthy state is required, along with these two kernel parameters where DP-1 is replaced with the port used by the HMD on the host system:
drm.edid_firmware=DP-1:edid/valve-index-hmd
video=DP-1:e
video=DP-1:e forces the connector on and ensures the EDID override is honored. drm.edid_firmware supplies the EDID override (blob kept in the initramfs). Confirmed at boot with the headset attached and never power-cycled: the sink is wedged underneath (a runtime probe with the override defeated reads 0 bytes) yet the headset is fully functional in Monado and SteamVR. This is a host-side mitigation for the symptom.
Specifically for my CachyOS system using systemd-boot this looks like:
# Boot the system with the Valve Index unplugged and powered off from the host system. After the system has fully booted, plug in the headset and continue with the rest of the instructions
p=DP-1 # the headset's connector. CHANGE to match your system
c=$(echo /sys/class/drm/card*-$p) # connector sysfs dir (e.g. `/sys/class/drm/card1-DP-1`)
# Dump the headset's EDID while it is healthy
cat ${c:?}/edid >valve-index-hmd
if [ "$(wc -c <valve-index-hmd)" -eq 256 ]; then
edid-decode <valve-index-hmd
sudo install -Dm644 valve-index-hmd /usr/lib/firmware/edid/valve-index-hmd
else
echo "DP-1 EDID is $(wc -c <valve-index-hmd) bytes, not 256 — re-plug the headset and retry"
fi
# Add the blob in the initramfs
# Add the EDID dump file to FILES=() in /etc/mkinitcpio.conf, e.g.:
# FILES=(/usr/lib/firmware/edid/valve-index-hmd)
sudo mkinitcpio -P
# Add these two kernel parameters (both are required) to LINUX_OPTIONS in /etc/sdboot-manage.conf
# - drm.edid_firmware=DP-1:edid/valve-index-hmd # serve this blob instead of probing
# - video=DP-1:e # force the connector on
sudo sdboot-manage gen
# Reboot with the headset attached, then verify
tr ' ' '\n' </proc/cmdline | grep -E 'video=|edid_firmware'
cat "${c:?}/status" "${c:?}/enabled" # connected / enabled
wc -c <"${c:?}/edid" # Should be 256
This report was investigated and written with the assistance of Claude (Opus 4.8). All commands, tests, and measurements were validated by me on the affected hardware; Claude helped interpret the results and draft the initial writeup.
Describe the bug
On Linux, a Valve Index HMD serves its EDID exactly once per power cycle. The first time the DisplayPort link is torn down (which happens at the end of every connector detection, and on every suspend and shutdown) the headset stops answering EDID reads for the rest of that power cycle. Every read afterwards fails, the connector falls back to a synthesized 640x480 mode with
non_desktop=0, and no OpenXR runtime can use it. The failure is observed at the DRM layer (empty EDID,non_desktop=0in sysfs), below any runtime, so it is not specific to SteamVR or Monado.The state survives a full host power-off, because the headset is powered by its breakout box rather than the host. This is why booting with the headset attached fails every time: the previous session's teardown already wedged it.
Crucially, only the EDID path is affected. The DisplayPort AUX channel keeps working normally. This points at the headset's EDID/DDC circuitry (the I2C EEPROM behind the AUX-to-I2C bridge), not at the link or the host.
This is not GPU or driver-specific
The same failure is documented on NVIDIA hardware, persisting across driver versions 570.86.16, 570.133.07, and 580.65.06. Additionally, independently confirmed on AMD in that same thread with the reporter concluding the fault likely originates in the headset itself:
https://forums.developer.nvidia.com/t/valve-index-initialized-in-unusable-state-on-boot/324710
The report below is from my AMD system, but the behavior appears to be identical across vendors, which is consistent with a defect in the headset firmware/hardware rather than in any one graphics driver.
To Reproduce
Set
pto the headset's DisplayPort connector name and the paths are written in terms of it. For my system I useDP-1but be sure to substitute yours (ls /sys/class/drm/lists the connectors):Ensure the headset starts from a baseline healthy state:
Trigger the issue by reading the EDID multiple times:
Timing note: a re-probe issued immediately after the first can still return the valid 256-byte EDID (whether from hotplug debounce or the sink wedging with a short lag). Give it a brief pause before the confirming probe. Trust a fresh
EDID err: 2/No EDID readin dmesg over the cachededidbyte count.Evidence that AUX is fine and D3 is not the cause
The link teardown that wedges the sink does two things: it writes the sink to D3, then disables the PHY. Two controls isolate which half matters, and each one must start from a healthy headset. Native DPCD reads on
$auxgo straight to the receiver and answer in any state, so the baseline read below needs no special setup.D3 alone does not wedge it. Re-establish the baseline healthy state, then drive the sink D0 → D3 by hand (leaving the PHY up) confirming with a read-back that the power state actually changes:
A PHY disable does wedge it and AUX survives. Re-establish the baseline healthy state, then let one detection read the EDID; the teardown at the end of that probe disables the PHY:
Both controls leave the sink in D3; only the second disabled the PHY, and only the second wedged it, so the trigger is the PHY disable, not D3. The DPCD reads are byte-identical before and after: the receiver stays up, only the EDID EEPROM (I2C-over-AUX to 0x50) stops answering, never native DPCD/AUX. The sink loses its EDID bridge because the link went electrically idle, which DisplayPort sinks should tolerate.
Note that
trigger_hotplugdoes two things in one step: it forces a real re-probe (invalidating the cachededidfile) and it ends in the PHY-disabling teardown. Unfortunately, there is no userspace way to separate them, which is why the controls above isolate D3 from the PHY disable through the direct DPCD writes.System Information
Recovery
The only things that restore the EDID are a power cycle of the headset:
Nothing host-side repairs the sink short of a power cycle: USB reset, re-enumeration, and writing D0 to DPCD 0x600 all leave the EDID EEPROM unreadable.
There is, however, a host-side way to make the headset fully usable without repairing the sink: inject a known-good EDID so nothing ever asks the dead EEPROM (see below). After either a power cycle or that injection, the headset works end-to-end (verified by using both Monado and SteamVR). The AUX channel and link training are fine; only the EDID fetch was ever broken. See "Host-side workaround" for more details.
Expected behavior / Potential fix
If the headset kept its EDID/DDC circuitry responsive across a DisplayPort PHY disable (as, I believe, every other DP sink does) none of the downstream Linux breakage would occur. The request is for Valve to investigate whether this can be fixed in headset firmware.
It's not possible to tell from the outside whether the EDID EEPROM losing state on PHY disable is correctable in the firmware or is a hardware power-sequencing property of the tether/breakout design. Valve's knowledge of the hardware would settle that.
Host-side workaround
Since the AUX channel and link stay healthy, injecting the headset's EDID is enough to use it as the dead EEPROM is never consulted. To do this, an EDID dump from the Valve Index HMD in a healthy state is required, along with these two kernel parameters where
DP-1is replaced with the port used by the HMD on the host system:video=DP-1:eforces the connector on and ensures the EDID override is honored.drm.edid_firmwaresupplies the EDID override (blob kept in the initramfs). Confirmed at boot with the headset attached and never power-cycled: the sink is wedged underneath (a runtime probe with the override defeated reads 0 bytes) yet the headset is fully functional in Monado and SteamVR. This is a host-side mitigation for the symptom.Specifically for my CachyOS system using systemd-boot this looks like:
This report was investigated and written with the assistance of Claude (Opus 4.8). All commands, tests, and measurements were validated by me on the affected hardware; Claude helped interpret the results and draft the initial writeup.