diff --git a/utils/plymouth/Makefile b/utils/plymouth/Makefile new file mode 100644 index 0000000..e32b79a --- /dev/null +++ b/utils/plymouth/Makefile @@ -0,0 +1,85 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=plymouth +PKG_VERSION:=26.134.222 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://gitlab.freedesktop.org/plymouth/plymouth/-/archive/$(PKG_VERSION) +PKG_HASH:=053687d4e8bb1320c14dafc014ebf4e45427f1cf417ce76b51102fbd61a3350a + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=GPL-2.0-or-later +PKG_LICENSE_FILES:=COPYING + +PKG_BUILD_DEPENDS:=gettext-full/host + +PKG_INSTALL:=1 +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/meson.mk + +MESON_ARGS += \ + -Ddrm=true \ + -Dudev=enabled \ + -Dgtk=disabled \ + -Dpango=disabled \ + -Dfreetype=disabled \ + -Ddocs=false \ + -Dsystemd-integration=false \ + -Dupstart-monitoring=false \ + -Dtracing=$(if $(CONFIG_DEBUG),true,false) \ + -Drelease-file=/etc/openwrt_release \ + -Drunstatedir=/var/run + +define Package/plymouth/Default + SECTION:=utils + CATEGORY:=Utilities + URL:=https://www.freedesktop.org/wiki/Software/Plymouth/ +endef + +define Package/plymouth +$(call Package/plymouth/Default) + TITLE:=Graphical boot splash on a KMS device + DEPENDS:=+libpng +libdrm +libudev +libevdev +libxkbcommon +xkeyboard-config +endef + +define Package/plymouth/description + Draws a boot splash directly on a DRM/KMS device and owns the display + until the application that follows it takes over, so the console stays + silent from reset to user interface. +endef + +define Package/plymouth/conffiles +/etc/plymouth/plymouthd.conf +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include + $(CP) $(PKG_INSTALL_DIR)/usr/include/plymouth-1 $(1)/usr/include/ + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libply*.so* $(1)/usr/lib/ + $(INSTALL_DIR) $(1)/usr/lib/pkgconfig + $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/*.pc $(1)/usr/lib/pkgconfig/ +endef + +define Package/plymouth/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libply*.so* $(1)/usr/lib/ + $(INSTALL_DIR) $(1)/usr/lib/plymouth + $(CP) $(PKG_INSTALL_DIR)/usr/lib/plymouth/* $(1)/usr/lib/plymouth/ + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/plymouth $(1)/usr/bin/ + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/plymouthd $(1)/usr/sbin/ + $(INSTALL_DIR) $(1)/usr/share/plymouth + $(CP) $(PKG_INSTALL_DIR)/usr/share/plymouth/plymouthd.defaults \ + $(1)/usr/share/plymouth/ + $(INSTALL_DIR) $(1)/etc/plymouth + $(INSTALL_DATA) ./files/plymouthd.conf $(1)/etc/plymouth/plymouthd.conf + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/plymouth.init $(1)/etc/init.d/plymouth +endef + +$(eval $(call BuildPackage,plymouth)) diff --git a/utils/plymouth/files/plymouth.init b/utils/plymouth/files/plymouth.init new file mode 100644 index 0000000..9bb8e57 --- /dev/null +++ b/utils/plymouth/files/plymouth.init @@ -0,0 +1,72 @@ +#!/bin/sh /etc/rc.common + +START=11 +STOP=06 + +PIDFILE=/var/run/plymouth.pid + +running() { + /usr/bin/plymouth --ping 2>/dev/null +} + +boot() { + start "$@" +} + +start() { + # The KMS device is not always card0; a render-only node can + # enumerate first. + ls /dev/dri/card* >/dev/null 2>&1 || return 0 + running && return 0 + + # plymouth only draws a graphical splash when the kernel was asked + # for one, and requiring that of every board's boot script would put + # a plymouth-ism in front of everyone. Ask for it here instead. + # Where the console is a serial port plymouth drops to text on it + # and never opens a renderer, so leave those alone. + /usr/sbin/plymouthd --mode=boot --pid-file="$PIDFILE" \ + --ignore-serial-consoles --attach-to-session \ + --kernel-command-line="$(cat /proc/cmdline) splash" || return 1 + /usr/bin/plymouth show-splash +} + +# Give the display up without going away. The daemon keeps the device +# open and the mode configured, so whatever draws next inherits a live +# crtc, and the splash can return later without a second modeset. +release() { + running || return 0 + /usr/bin/plymouth deactivate +} + +stop() { + running || return 0 + /usr/bin/plymouth quit --retain-splash + rm -f "$PIDFILE" +} + +# procd runs the K scripts as "shutdown", so this is the start of that +# sequence rather than the end. Take the display back from whoever has +# it, reusing the daemon that has been there since boot. +shutdown() { + ls /dev/dri/card* >/dev/null 2>&1 || return 0 + [ -x /usr/sbin/plymouthd ] || return 0 + + if running; then + /usr/bin/plymouth reactivate + /usr/bin/plymouth change-mode --shutdown + /usr/bin/plymouth show-splash + return 0 + fi + + /usr/sbin/plymouthd --mode=shutdown --pid-file="$PIDFILE" \ + --ignore-serial-consoles \ + --kernel-command-line="$(cat /proc/cmdline) splash" || return 0 + /usr/bin/plymouth show-splash +} + +reload() { + /usr/bin/plymouth update --status=reload +} + +EXTRA_COMMANDS="release" +EXTRA_HELP=" release Drop the display but keep the daemon running" diff --git a/utils/plymouth/files/plymouthd.conf b/utils/plymouth/files/plymouthd.conf new file mode 100644 index 0000000..ae558b4 --- /dev/null +++ b/utils/plymouth/files/plymouthd.conf @@ -0,0 +1,3 @@ +[Daemon] +ShowDelay=0 +DeviceTimeout=8 diff --git a/utils/plymouth/patches/100-libply-provide-rpmatch-for-non-glibc.patch b/utils/plymouth/patches/100-libply-provide-rpmatch-for-non-glibc.patch new file mode 100644 index 0000000..27c9b55 --- /dev/null +++ b/utils/plymouth/patches/100-libply-provide-rpmatch-for-non-glibc.patch @@ -0,0 +1,42 @@ +From dd1ec1448548beee9b9c6a6b1a627580a087b179 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Thu, 24 Sep 2026 21:07:31 +0100 +Subject: [PATCH] libply: provide rpmatch where the C library lacks it + +rpmatch() is a GNU extension. Building against musl fails with an +implicit declaration, and the call is only used to read a yes or no +answer for a boolean command line option, so a plain first-letter test +covers what the parser needs. + +Signed-off-by: Daniel Golle +--- + src/libply/ply-command-parser.c | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +--- a/src/libply/ply-command-parser.c ++++ b/src/libply/ply-command-parser.c +@@ -32,6 +32,24 @@ + #include "ply-list.h" + #include "ply-utils.h" + ++#ifndef __GLIBC__ ++/* rpmatch() is a GNU extension; the C library need not provide it. */ ++static int ++rpmatch (const char *response) ++{ ++ if (response == NULL) ++ return -1; ++ ++ if (response[0] == 'y' || response[0] == 'Y') ++ return 1; ++ ++ if (response[0] == 'n' || response[0] == 'N') ++ return 0; ++ ++ return -1; ++} ++#endif ++ + typedef union + { + bool as_boolean; diff --git a/utils/plymouth/patches/101-main-build-crash-handler-without-execinfo.patch b/utils/plymouth/patches/101-main-build-crash-handler-without-execinfo.patch new file mode 100644 index 0000000..52e3c40 --- /dev/null +++ b/utils/plymouth/patches/101-main-build-crash-handler-without-execinfo.patch @@ -0,0 +1,46 @@ +From 29124ae4d8307bc8332b4678eb8cd4434b4bb872 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Thu, 24 Sep 2026 21:08:57 +0100 +Subject: [PATCH] main: keep the crash handler building without execinfo + +backtrace() and execinfo.h are glibc extensions, so the crash handler +fails to build against musl. Guard both the include and the body of +write_backtrace, and record that the backtrace is unavailable, leaving +the maps dump and the rest of the crash report intact. + +Signed-off-by: Daniel Golle +--- + src/main.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/src/main.c ++++ b/src/main.c +@@ -2202,7 +2202,9 @@ dump_debug_buffer_to_file (void) + + #include + #include ++#ifdef __GLIBC__ + #include ++#endif + + #define BACKTRACE_SIZE 1024 + #define MAPS_SIZE 8192 +@@ -2252,6 +2254,7 @@ write_maps (int output_fd) + static void + write_backtrace (int output_fd) + { ++#ifdef __GLIBC__ + void *addresses[BACKTRACE_SIZE]; + int number_of_addresses; + +@@ -2264,6 +2267,10 @@ write_backtrace (int output_fd) + backtrace_symbols_fd (addresses + BACKTRACE_FRAMES_TO_SKIP, + number_of_addresses - BACKTRACE_FRAMES_TO_SKIP, + output_fd); ++#else ++ write (output_fd, "backtrace: unavailable\n", ++ strlen ("backtrace: unavailable\n")); ++#endif + } + + static void diff --git a/utils/plymouth/patches/110-drm-probe-connectors-as-drm-master.patch b/utils/plymouth/patches/110-drm-probe-connectors-as-drm-master.patch new file mode 100644 index 0000000..38cefc8 --- /dev/null +++ b/utils/plymouth/patches/110-drm-probe-connectors-as-drm-master.patch @@ -0,0 +1,105 @@ +From 0cdc68b17ed3290a233e699dc31e973650b216ee Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 25 Sep 2026 11:22:40 +0100 +Subject: [PATCH] drm: Probe connectors while holding DRM master + +The DRM renderer drops master immediately after opening the device and +takes it back only when the splash is activated, so the connector +enumeration in query_device () runs as a client that is not DRM master. + +The kernel refuses a forced probe from such a client and returns the +cached connection status instead: + + User-space requested a forced probe on [CONNECTOR:84:HDMI-A-1] but is + not the DRM master, demoting to read-only probe + +On a display nothing has driven yet that status is unknown, so no output +counts as connected, no controller is assigned, and the renderer gives +up with "Could not initialize heads". The splash therefore appears only +on a screen that some other client has already lit, which on a system +without fbdev emulation never happens. + +This commit holds master for the duration of the connector enumeration, +dropping it again unless the backend was already active. The probe then +detects for real, reads the EDID and brings up a sink that had gone to +low power. On an rk3588 board with no other DRM client running, this +takes the HDMI connector from unknown to connected at 1920x1080. + +Signed-off-by: Daniel Golle +--- + src/plugins/renderers/drm/plugin.c | 57 ++++++++++++++++++++++-------- + 1 file changed, 43 insertions(+), 14 deletions(-) + +--- a/src/plugins/renderers/drm/plugin.c ++++ b/src/plugins/renderers/drm/plugin.c +@@ -1421,6 +1421,46 @@ check_simpledrm_resolution (ply_renderer + return true; + } + ++/* Probe every connector, holding DRM master so the kernel performs a real ++ * detect rather than handing back the cached connection status. ++ */ ++static bool ++probe_outputs (ply_renderer_backend_t *backend, ++ ply_output_t *outputs, ++ int outputs_len, ++ bool force, ++ bool *changed) ++{ ++ bool was_master; ++ bool ret = true; ++ int i; ++ ++ was_master = backend->is_active; ++ if (!was_master) ++ drmSetMaster (backend->device_fd); ++ ++ backend->connected_count = 0; ++ for (i = 0; i < outputs_len; i++) { ++ get_output_info (backend, backend->resources->connectors[i], &outputs[i]); ++ ++ if (!force && !check_simpledrm_resolution (backend, &outputs[i])) { ++ ret = false; ++ break; ++ } ++ ++ if (check_if_output_has_changed (backend, &outputs[i])) ++ *changed = true; ++ ++ if (outputs[i].connected) ++ backend->connected_count++; ++ } ++ ++ if (!was_master) ++ drmDropMaster (backend->device_fd); ++ ++ return ret; ++} ++ + /* Update our outputs array to match the hardware state and + * create and/or remove heads as necessary. + * Returns true if any heads were modified. +@@ -1447,20 +1487,9 @@ create_heads_for_active_connectors (ply_ + outputs = calloc (backend->resources->count_connectors, sizeof(*outputs)); + outputs_len = backend->resources->count_connectors; + +- backend->connected_count = 0; +- for (i = 0; i < outputs_len; i++) { +- get_output_info (backend, backend->resources->connectors[i], &outputs[i]); +- +- if (!force && !check_simpledrm_resolution (backend, &outputs[i])) { +- free (outputs); +- return false; +- } +- +- if (check_if_output_has_changed (backend, &outputs[i])) +- changed = true; +- +- if (outputs[i].connected) +- backend->connected_count++; ++ if (!probe_outputs (backend, outputs, outputs_len, force, &changed)) { ++ free (outputs); ++ return false; + } + + /* Step 2: diff --git a/utils/plymouth/patches/111-escape-needs-a-visible-vt.patch b/utils/plymouth/patches/111-escape-needs-a-visible-vt.patch new file mode 100644 index 0000000..b49399a --- /dev/null +++ b/utils/plymouth/patches/111-escape-needs-a-visible-vt.patch @@ -0,0 +1,122 @@ +From 7d73b0fd2746cd24c758dd75fc73ac92b5162d0b Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 25 Sep 2026 13:20:37 +0100 +Subject: [PATCH] Ignore the escape key when the VT shows nothing + +Escape toggles between the splash and the details view, guarded on +there being a VT console to show the details on. The guard asks only +whether the local console terminal is a VT, which it is whenever the +kernel has CONFIG_VT and a /dev/tty1 node exists, even where no console +driver draws that VT. + +On a system built without a framebuffer console that leaves only the +dummy driver bound, so escape blanks the screen: the splash is torn +down, the details view writes to a VT nobody renders, and the display +stays dark until escape is pressed again. + +Require a bound console driver other than the dummy one before treating +a VT as somewhere the details view can be read. + +Signed-off-by: Daniel Golle +--- + src/main.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 68 insertions(+) + +--- a/src/main.c ++++ b/src/main.c +@@ -36,6 +36,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -56,6 +57,7 @@ + #include "ply-progress.h" + #include "ply-kmsg-reader.h" + ++#define VT_CONSOLE_DIRECTORY "/sys/class/vtconsole" + #define BOOT_DURATION_FILE PLYMOUTH_TIME_DIRECTORY "/boot-duration" + #define SHUTDOWN_DURATION_FILE PLYMOUTH_TIME_DIRECTORY "/shutdown-duration" + +@@ -1638,6 +1640,70 @@ toggle_between_splash_and_details (state + } + } + ++static bool ++vt_console_driver_draws (const char *directory) ++{ ++ char path[PATH_MAX]; ++ char name[64]; ++ FILE *file; ++ bool draws; ++ ++ snprintf (path, sizeof(path), "%s/bind", directory); ++ file = fopen (path, "r"); ++ ++ if (file == NULL) ++ return false; ++ ++ draws = (fgetc (file) == '1'); ++ fclose (file); ++ ++ if (!draws) ++ return false; ++ ++ snprintf (path, sizeof(path), "%s/name", directory); ++ file = fopen (path, "r"); ++ ++ if (file == NULL) ++ return false; ++ ++ if (fgets (name, sizeof(name), file) == NULL) ++ name[0] = '\0'; ++ ++ fclose (file); ++ ++ return strstr (name, "dummy") == NULL; ++} ++ ++/* The dummy console driver puts nothing on screen, so a VT it is the only ++ * driver for cannot show the details view. ++ */ ++static bool ++vt_console_is_visible (void) ++{ ++ char directory[PATH_MAX]; ++ struct dirent *entry; ++ bool visible = false; ++ DIR *consoles; ++ ++ consoles = opendir (VT_CONSOLE_DIRECTORY); ++ ++ if (consoles == NULL) ++ return true; ++ ++ while (!visible && (entry = readdir (consoles)) != NULL) { ++ if (strncmp (entry->d_name, "vtcon", strlen ("vtcon")) != 0) ++ continue; ++ ++ snprintf (directory, sizeof(directory), "%s/%s", ++ VT_CONSOLE_DIRECTORY, entry->d_name); ++ visible = vt_console_driver_draws (directory); ++ } ++ ++ closedir (consoles); ++ ++ return visible; ++} ++ + static void + on_escape_pressed (state_t *state) + { +@@ -1647,6 +1713,8 @@ on_escape_pressed (state_t *state) + if (state->local_console_terminal != NULL) { + if (!ply_terminal_is_vt (state->local_console_terminal)) + has_vt_consoles = false; ++ else if (!vt_console_is_visible ()) ++ has_vt_consoles = false; + } else { + has_vt_consoles = false; + } diff --git a/utils/plymouth/test-version.sh b/utils/plymouth/test-version.sh new file mode 100755 index 0000000..0716630 --- /dev/null +++ b/utils/plymouth/test-version.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# Neither binary reports a version: upstream's command parser has no +# --version and prints only a usage summary. Check that both still +# identify themselves and exit cleanly. +/usr/bin/plymouth --help 2>&1 | grep -q '^Splash control client$' || exit 1 +/usr/sbin/plymouthd --help 2>&1 | grep -q '^Splash server$' || exit 1 + +exit 0