Skip to content

fix(viewer): re-assert linuxfb display mode after HDMI hotplug#3075

Merged
vpetersson merged 5 commits into
masterfrom
fix/linuxfb-hdmi-hotplug-resolution
Jul 7, 2026
Merged

fix(viewer): re-assert linuxfb display mode after HDMI hotplug#3075
vpetersson merged 5 commits into
masterfrom
fix/linuxfb-hdmi-hotplug-resolution

Conversation

@vpetersson

Copy link
Copy Markdown
Contributor

Problem

Fixes #3052. On a 32-bit Pi 1-3 (linuxfb / Qt5) viewer, a TV on a power schedule (switches itself off and on) frequently comes back at 1024×768 instead of its native resolution. The Pi stays on the whole time; only a manual reboot restores the right resolution.

Root cause

The 1024×768 is the kernel's drm_fb_helper hard-coded fallback mode — not anything Anthias writes. Under dtoverlay=vc4-kms-v3d, the linuxfb boards never take DRM master, so the kernel's drm_fb_helper owns the display mode. When the HDMI sink wakes, the connector re-probe can win the race against the sink's EDID/DDC coming back; the connector momentarily reports no valid modes and drm_fb_helper latches the 1024×768 default. Qt's linuxfb plugin reads the framebuffer geometry once at startup and can't follow a later mode change, so the picture stays stuck.

eglfs boards (pi4 / pi5 / pi3-64 / arm64) are immune — Qt holds DRM master and keeps its own modeset committed across the hotplug. Verified on a Pi 3-64 testbed: a real ~10 s HDMI cable unplug never left 1920×1080 (connector went disconnected, but the CRTC/plane/fb held 1080p throughout).

Fix

A linuxfb-only watchdog in start_viewer.sh:

  • watches the HDMI connector for a disconnected → connected transition,
  • waits for EDID/DDC to settle (ANTHIAS_HOTPLUG_SETTLE_SECONDS, default 5),
  • re-asserts the connector's preferred mode — read live from the connector, never hard-coded, so any panel resolution is honoured — onto the framebuffer via the fbdev sysfs mode attribute,
  • restarts the viewer so Qt re-initialises against the restored mode.

All access is under /sys in the already-privileged viewer container; no DRM master is taken, so it never conflicts with Qt's fbdev use. A QT_QPA_PLATFORM guard makes it a complete no-op on eglfs/wayland boards. (Celery was considered but is the wrong home — that container isn't privileged, has no /dev/fb0 or /dev/dri, and its /sys is read-only, so the mode re-assert is impossible there.)

Validation (real Pi 3 testbed)

  • eglfs (pi3-64): a real ~10 s HDMI unplug held 1920×1080 across the whole event (1660/1660 samples) → confirmed not the affected path.
  • linuxfb (pi3): confirmed no process holds /dev/dri/card0 (so drm_fb_helper solely owns /dev/fb0), then drove a disconnect → 1024×768 → reconnect cycle. The watchdog detected the reconnect, re-asserted 1920×1080, and restarted the viewer:
    [baseline] geometry=1920x1080
    TV off          -> status=disconnected
    racy fallback   -> geometry 1024x768 (virt stays 1920x1080)
    TV on           -> watchdog: "HDMI reconnect — re-asserted 1920x1080 ...; restarting viewer."
    [after]    geometry=1920x1080   viewer RESTARTED
    

Note: the 1024×768 latch was simulated with fbset — the testbed monitor presents EDID instantly and can't reproduce the real no-EDID race that the reporter's TV triggers. Detection (connector reconnect) and recovery (re-assert preferred mode) are identical regardless of how the mode got latched.

🤖 Generated with Claude Code

On the 32-bit Pi 1-3 (linuxfb/Qt5) viewer, a TV on a power schedule
that switches itself off and on can leave the display stuck at
1024x768 instead of its native resolution (issue #3052).

Root cause is kernel-side, not anything Anthias writes: under
dtoverlay=vc4-kms-v3d the linuxfb boards never take DRM master, so the
kernel's drm_fb_helper owns the display mode. When the HDMI sink wakes,
the connector re-probe can win the race against the sink's EDID/DDC
coming back; the connector momentarily reports no valid modes and
drm_fb_helper latches its hard-coded 1024x768 default. Qt's linuxfb
plugin reads the framebuffer geometry once at startup and can't follow
the change, so the picture stays stuck until the Pi is power-cycled.

eglfs boards (pi4 / pi5 / pi3-64 / arm64) are immune: Qt holds DRM
master and keeps its own modeset committed across the hotplug. Verified
on a Pi 3-64 testbed that a real ~10 s HDMI unplug never left 1920x1080.

Fix: a linuxfb-only watchdog in start_viewer.sh watches the HDMI
connector for a disconnect->reconnect and, once EDID is readable again,
re-asserts the connector's *preferred* mode (read live from the
connector, never hard-coded, so any panel resolution is honoured) onto
the framebuffer via the fbdev sysfs `mode` attribute, then restarts the
viewer so Qt re-initialises against the restored mode. All access is
under /sys in the already-privileged viewer container; no DRM master is
taken, so it never conflicts with Qt's fbdev use. The QT_QPA_PLATFORM
guard makes it a no-op on eglfs/wayland boards.

Validated on a real Pi 3: forcing the connector through a
disconnect -> 1024x768 -> reconnect cycle, the watchdog re-asserted
1920x1080 and restarted the viewer. (The 1024x768 latch was simulated
with fbset because the testbed monitor presents EDID instantly and
can't reproduce the real no-EDID race; detection and recovery are
identical regardless of how the mode was latched.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vpetersson vpetersson requested a review from a team as a code owner June 12, 2026 12:42
@vpetersson vpetersson requested a review from Copilot June 12, 2026 12:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@vpetersson vpetersson requested a review from Copilot June 12, 2026 13:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

vpetersson and others added 2 commits June 12, 2026 14:15
- exit wait_for_framebuffer when the host has fb0 but the container's
  stale /dev doesn't, so `restart: always` re-snapshots /dev
- keep waiting quietly when genuinely headless (no sysfs fb0) to avoid
  the #2959 crash-loop
- fixes the "self-heals on hotplug" case that a running container's
  devtmpfs snapshot silently broke

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vpetersson

Copy link
Copy Markdown
Contributor Author

Added commit daa7f8d (folded from #3134): wait_for_framebuffer now exits→restarts when the host has /dev/fb0 but the container's stale devtmpfs snapshot doesn't — the headless-boot-then-plug-in case that monitor_hdmi_resolution structurally can't reach (it only runs once the viewer is up). Validated live on the Pi 2 (proceed / stale-exit / headless-wait). Heads-up: if you have the branch checked out elsewhere, git pull --rebase.

@vpetersson vpetersson requested a review from Copilot July 7, 2026 19:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

- preserve interlaced scan type instead of forcing a progressive mode
  string (interlaced-native sinks were never matched)
- log rejected /sys .../fb0/mode writes instead of returning silently,
  so a screen stuck at 1024x768 is diagnosable
- watch every HDMI connector, seeded from current status, instead of
  binding one at startup (second micro-HDMI port / late connect)
- normalize `pidof python` to a single PID so a multi-PID result can't
  disable the watchdog (also fixes the pre-existing kill -0 loop)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vpetersson

Copy link
Copy Markdown
Contributor Author

Ran a high-effort multi-agent review + on-device validation on the Pi 2 (DEVICE_TYPE=pi2, linuxfb). Pushed commit 52680a4 hardening the recovery path:

  • Interlaced scan type — was stripping the i and forcing ^U:WxHp, so interlaced-native sinks never matched. Now preserves the flag and greps ^U:WxH[pi].
  • Silent write failures — a rejected /sys/.../fb0/mode write returned 1 with no log and retried forever. Now logs so a screen stuck at 1024×768 is diagnosable.
  • Single-connector binding — bound one connector at startup; now watches every HDMI connector (second micro-HDMI port / late connect), seeded from current status.
  • pidof python multi-PID — a multi-PID result made kill -0 "$PID" fail and silently disabled the watchdog (and the pre-existing wait loop). Normalized to the first PID.

Note on the review's two top "CONFIRMED" findings (fallback mode string U:WxHp-0 rejected by the fbdev handler): these do not reproduce on the actual vc4-kms-v3d hardware. The DRM fbdev emulation registers modes as U:1024x768p-0 / U:1920x1080p-0 (a -0 refresh field, not -60), so both the grep-matched and fabricated strings are accepted. Verified live: latch to 1024×768 → echo "U:1920x1080p-0" > /sys/class/graphics/fb0/mode → recovers to 1920×1080. The mechanism works; the fixes above are robustness/observability follow-ups.

One design limitation left as-is (flagging, not fixing without a call): the 3s connector-status poll can miss a fast disconnect→reconnect, and an AVR/HDMI-switch that pins HPD connected through a TV power-cycle won't trip the edge at all. Watching fb0 geometry directly (not just connector status) would close that, but it's a larger change.

@vpetersson vpetersson requested a review from Copilot July 7, 2026 20:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

- wait_for_framebuffer: sleep+re-check before the stale-devtmpfs exit so
  a /dev/fb0 node that lags its sysfs entry on a fresh start can't cause
  restart churn
- monitor: default an unseeded connector to 'connected' so an
  unreadable-status seed race can't look like a reconnect edge and
  spuriously restart a display that never dropped
- PID: select the oldest pidof result (the viewer, launched before any
  gst_fbdev_player.py helper) instead of the newest

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vpetersson

Copy link
Copy Markdown
Contributor Author

Second-pass high-effort review + end-to-end test on the Pi 2. Commit e8142db addresses the three (PLAUSIBLE) follow-up findings:

  • wait_for_framebuffer now sleeps and re-checks /dev/fb0 before the stale-devtmpfs exit 1, so a device node that lags its sysfs entry on a fresh start can't cause restart churn (still exits after the grace cycle when genuinely stale — validated on-device).
  • monitor defaults an unseeded connector to connected (${previous[$conn]:-connected}) so an unreadable-status seed race can't look like a reconnect edge and spuriously restart a display that never dropped.
  • PID selects the oldest pidof result (the viewer, launched before any gst_fbdev_player.py helper) instead of the newest.

End-to-end validated on the real integrated stack (Pi 2, DEVICE_TYPE=pi2, linuxfb, 1080p panel): deployed the actual start_viewer.sh, drove a genuine connector disconnect→reconnect via the sysfs status interface, and observed the full chain in the viewer log:

start_viewer: HDMI reconnect — re-asserted 1920x1080 framebuffer mode; restarting viewer.

→ viewer killed → container restarted → fb back at 1920×1080. The watchdog subshell + 3s poll were confirmed live, and the reassert write-to-/sys/.../fb0/mode recovers the framebuffer from 1024×768. Testbed restored to its image entrypoint afterward.

@sonarqubecloud

sonarqubecloud Bot commented Jul 7, 2026

Copy link
Copy Markdown

@vpetersson vpetersson merged commit f0959a4 into master Jul 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] TV resolution incorrect since update

2 participants