fix(viewer): wait for /dev/fb0 instead of crash-looping when headless - #2959
Merged
Merged
Conversation
- On the linuxfb path (pi2/pi3) Qt aborts with heap corruption
("malloc(): unaligned tcache chunk detected") when /dev/fb0 is absent,
crash-looping the container and spamming the logs.
- Under full KMS (vc4-kms-v3d) the framebuffer only exists while a
display is connected, so headless / powered-off / slow-HDMI boards
have none.
- Add wait_for_framebuffer() to start_viewer.sh: on the linuxfb path,
wait for /dev/fb0 before launching the viewer, self-healing when a
display is (re)connected. Guarded on QT_QPA_PLATFORM=linuxfb, so it's
a no-op on the cage (wayland) and eglfs (KMS) boards.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
vpetersson
marked this pull request as ready for review
May 31, 2026 11:35
5 tasks
vpetersson
added a commit
that referenced
this pull request
Jun 1, 2026
…esn't crash-loop (#2962) Pi 4 renders under QT_QPA_PLATFORM=eglfs (since #2904), but the headless-display guard added in #2959 only covers the linuxfb /dev/fb0 path (pi2/pi3). On a headless eglfs board Qt's eglfs plugin finds no connected DRM connector, logs "no screens available", and exits before the viewer emits its D-Bus handshake — so load_browser() raises and the viewer container crash-loops. Confirmed on 4 live pi4 devices running 2026.5.2. - Add wait_for_eglfs_display(): before the KMS-card detection, wait until a DRM connector reads "connected" (or "unknown"); a genuinely headless board idles quietly and self-heals on hotplug, mirroring the linuxfb /dev/fb0 wait. - Correct stale comments that still claimed Pi 4 runs under linuxfb. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
vpetersson
added a commit
that referenced
this pull request
Jul 7, 2026
- 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) <[email protected]>
vpetersson
added a commit
that referenced
this pull request
Jul 7, 2026
* fix(viewer): re-assert linuxfb display mode after HDMI hotplug 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) <[email protected]> * fix(viewer): restart to recover /dev/fb0 hotplugged after headless boot - 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) <[email protected]> * fix(viewer): harden HDMI-hotplug resolution recovery (review follow-ups) - 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) <[email protected]> * fix(viewer): address second-pass review nits on HDMI-hotplug recovery - 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) <[email protected]> --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Issues Fixed
Headless / display-off 32-bit Pi (pi2/pi3) devices crash-loop the viewer
and show nothing. Diagnosed live on the pi3 fleet.
Qt's
linuxfbplatform opens/dev/fb0at startup and can't recover ifit's absent. Under full KMS (
dtoverlay=vc4-kms-v3d) the framebufferonly exists while a display is connected, so a device that boots with no
panel attached — display powered off, TV switched on after the Pi, slow
HDMI handshake, panel swapped — has no
/dev/fb0. Qt then doesn't failcleanly: it logs
Unable to figure out framebuffer device/no screens availableand aborts with heap corruption (malloc(): unaligned tcache chunk detected), so the container crash-loops.This is not a boot-time race. When a display is present at boot,
/dev/fb0is created by the kernel at driver-bind (~t=23 s), ~30 sbefore the container engine even starts — it's always there in time. The
problem is purely the display-absent-at-boot case, and crucially the
framebuffer is dynamic: the vc4 fbdev layer creates
/dev/fb0when aconnector transitions to connected after boot (verified — see below).
Description
Add
wait_for_framebuffer()tobin/start_viewer.sh: on thelinuxfbpath, if
/dev/fb0is missing, wait for it instead of launching into aguaranteed heap-corruption crash. When a display is attached/powered on,
the KMS driver creates
/dev/fb0and the viewer starts immediately —replacing a noisy crash-loop (which only recovers on a later restart that
happens to land after the display appears) with a quiet idle that
recovers the instant the framebuffer exists.
Makes no assumptions about which connector the display is on or its
resolution. Guarded on
QT_QPA_PLATFORM=linuxfb, so it's a no-op on thecage (
wayland) and eglfs (KMS) boards, which render straight to/dev/driand have no/dev/fb0dependency.Companion to the UID/GID-pinning fix (independent). The analogous
"headless" behaviour on the eglfs/cage boards is a separate concern, out
of scope here.
Validation (on live pi3 hardware)
dmesgshowsvc4drmfb fb0created at monotonic t≈23 s;balena.service(thecontainer engine) starts at t≈53 s. fb0 is present ~30 s before any
container.
disconnected, no/dev/fb0), forcing the connector connected(
echo on > /sys/class/drm/card0-HDMI-A-1/status) created/dev/fb0at t≈1074 s post-boot (
dmesg:vc4drmfb fb0). So a displayattached after boot does make the framebuffer appear — which is exactly
what the wait recovers on.
deploy to a headless device, confirm it idles on the new log line and
starts the viewer when a display is connected.
Checklist