Skip to content

fix(video): replug HDMI in software when the receiver is stuck relocking - #77

Merged
Rust-KVM merged 5 commits into
devfrom
claude/project-thread-nvyxx0
Oct 19, 2026
Merged

Rust-KVM merged 5 commits into
devfrom
claude/project-thread-nvyxx0

Conversation

@Rust-KVM

@Rust-KVM Rust-KVM commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Requested by 4c · project thread

Summary

Before: after the controlled host slept for about 15 hours and woke up, the remote desktop showed green and magenta horizontal stripes. Reconnecting and restarting the app did not help; only physically unplugging and replugging HDMI fixed it.

After: the app spots the broken state within about 6 seconds and does the replug itself, by dropping HDMI hot-plug detect for 2 seconds and raising it again. If it keeps happening it retries with backoff (30 s, 60 s, … up to 10 min) and logs each attempt.

What hardware showed (dmesg on the T6, kernel 6.1.99, rockchip_hdmirx): the receiver was in a once-a-second loop. Each lock ends in hdmirx_format_change, which queues a V4L2 source-change event even when the mode is unchanged (New format: 3840x2160p59.99 every time). v4l2src restarts the stream on each event (hdmirx_stop_streaming wait last irq timeout, return bufs!), and the restart relocks and queues the next event. Frames get cut mid-DMA. Format, stride and timings never changed. After a physical replug the lock took longer (i:117), the loop stopped and capture has been stable since.

How

  • hardware/v4l2.rs: a monitoring fd on the capture node subscribes to V4L2_EVENT_SOURCE_CHANGE. A small thread drains events every 200 ms, because the kernel keeps only the newest pending one. RelockWatch calls 6+ events within 10 s a storm. A healthy input reports none; a mode change or a replug reports a few. pulse_hotplug writes off then on to /sys/class/hdmirx/hdmirx/status (driver status_store → hdmirx_hpd_ctrl) and retries on so HPD is never left low.
  • video.rs: the supervisor acts on the storm verdict, and still rebuilds the pipeline when it dies or capture stalls with a signal present. It logs the negotiated capture caps once per pipeline.
  • Dropped from this PR: rebuilding on a format/timing change (wrong diagnosis, misfired on pixel-clock jitter 593.95/593.96 MHz), the buffer-size check, and an HID pointer-coalescing commit (e7e1916, reverted: the mouse stuttered with it on hardware and was smooth again without it).

Follow-up outside this repo: the driver should only queue the source-change event when timings actually change. That belongs on the local buildroot kernel branch.

Testing

  • cargo +nightly fmt --all
  • cargo +nightly clippy --workspace --release --all-targets -- -D warnings
  • cargo +nightly test --workspace (app/tests/capture_source.rs: storm detection, backoff, reset, ioctl parsing)
  • Frontend touched: not applicable
  • Tested on an RK3588 device: this tree (identical to f92cfc0) has run on the T6 since 2026-10-19 through a host sleep/wake with a clean picture and smooth input. The storm path itself has not fired yet, since the host relocked on its own.

Checklist

  • No #[allow(...)] suppressions and no new unwrap() / expect() in production paths
  • Hot paths stay zero-copy; periodic loops use MissedTickBehavior::Delay
  • Shared protocol types live in rkvm-proto
  • RPC method set changed → not changed
  • Module tree or build invariants changed → AGENTS.md updated

🤖 Generated with Claude Code

https://claude.ai/code/session_01GRSn3XsjYLc494XwPez2mF

v4l2src negotiates the capture format once. When the HDMI source re-locks
with different timings or a different colour format (host display sleep,
GPU retrain, re-plug), the receiver starts writing a new frame layout while
the pipeline keeps the old caps, so the encoder reads the planes wrong and
viewers get green and magenta stripes with no error on the bus. The
supervisor only restarted pipelines that had died, so this never healed.

The supervisor now also:
- polls the capture node (VIDIOC_G_FMT + VIDIOC_QUERY_DV_TIMINGS, read-only,
  one fd kept open) every 2s and rebuilds the pipeline when the format or
  timings differ from the start-of-pipeline baseline on two polls in a row;
  signal loss alone is not a change;
- rebuilds at once when a capture buffer's size differs from the first one;
- rebuilds when no raw frame arrives for 10s while a signal is present,
  backing off on repeats.
Each rebuild logs the old and new source, and the negotiated capture caps
are logged once per pipeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRSn3XsjYLc494XwPez2mF
@Rust-KVM Rust-KVM self-assigned this Oct 19, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation area: video GStreamer, MPP encoding, WebRTC media labels Oct 19, 2026
On hardware the receiver reports 593.95 and 593.96 MHz alternately on a
steady 4K60 signal, so the source watch rebuilt the pipeline every 30
seconds. Compare only the frame layout and video mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRSn3XsjYLc494XwPez2mF
Hardware showed the real failure behind the striped picture: after the host
woke from a 15-hour sleep, the rockchip_hdmirx receiver looped once a second.
Every lock ends in hdmirx_format_change, which queues a source-change event
even when the mode is unchanged; v4l2src restarts the stream on each event,
and the restart relocks and queues the next one. Frames are cut mid-DMA, and
only a physical replug ended it. Format and timings never changed, so the
source-change rebuild from the previous commits never fired, and on hardware
it misfired on pixel-clock jitter.

Replace it with what actually recovers the input:
- subscribe to V4L2 source-change events on a monitoring fd, counted on a
  small thread (the kernel keeps only the newest pending event);
- 6+ events within 10 s is a relock storm: pulse hot-plug detect through
  /sys/class/hdmirx/hdmirx/status (off, 2 s, on) so the source renegotiates,
  backing off 30 s, 60 s, ... while the storm persists;
- drop the format/timing comparison and the buffer-size check; keep the
  stall rebuild and the negotiated-caps log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRSn3XsjYLc494XwPez2mF
@Rust-KVM Rust-KVM changed the title fix(video): rebuild the pipeline when the HDMI source changes under it fix(video): replug HDMI in software when the receiver is stuck relocking Oct 19, 2026
…stall

Device logs from a host sleep/wake showed the HID writer timing out on the
absolute mouse while the host stopped polling, then the host re-enumerating
USB. Two things made input feel laggy and drifting around that:

- While the host polls slowly, every queued pointer move was replayed one by
  one, so the cursor trailed seconds behind and then raced. The writer now
  takes everything queued at once and keeps only the last position of
  consecutive absolute moves (button changes and wheel steps keep their
  place, so presses and releases land where they happened), and sums
  consecutive relative moves.
- A timeout on one device dropped every queued report, key-ups included,
  which can leave a key held on the host. Dropped reports now leave behind
  the latest keyboard and pointer state, which is resent (every 500 ms until
  the host reads again) unless newer input supersedes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRSn3XsjYLc494XwPez2mF
@github-actions github-actions Bot added area: hid Keyboard, mouse, HID-RPC area: usb USB gadget and virtual media labels Oct 19, 2026
… a host stall"

This reverts commit e7e1916. On hardware the mouse stuttered with it
deployed and was smooth again after rolling the device back to f92cfc0,
so the change goes until the cause is understood. The HDMI relock fix
stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRSn3XsjYLc494XwPez2mF
@Rust-KVM
Rust-KVM marked this pull request as ready for review October 19, 2026 16:24
@Rust-KVM
Rust-KVM merged commit 20cbbd5 into dev Oct 19, 2026
8 checks passed
@Rust-KVM
Rust-KVM deleted the claude/project-thread-nvyxx0 branch October 19, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: hid Keyboard, mouse, HID-RPC area: usb USB gadget and virtual media area: video GStreamer, MPP encoding, WebRTC media documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants