Mainline boot splash on the board, 2026-10-12
Hardware gates 2.1-2.4 of the-mainline-kernel-keeps-the-boot-splash, run
on the one board in place (no second card: the operator answered “In place
is fine”), with warm reboots standing in for power-on (the operator answered
“Warm reboot is enough”). Every film below is a warm reboot, not a cold
power-on. Camera: the bench webcam (/dev/video0, MJPEG 1280x720, 30 fps),
which now views the panel from its long edge at a grazing angle; the far
half of the panel is outside the frame.
System under test: k230-mainline-drm-shell-splash, toplevel
/nix/store/kc3iyr4cnjlswa87kkpi81qgc1is6hli-nixos-system-nixos-26.11.20260919.20b1ddd,
bundle /nix/store/kjlnrxv8vdk92lh84w2bkd1v0mx456hy-k230-coherent-shell-boot-files
(source impl/mainline-splash-2026-10-12 a03999c3). Its Image and initrd are
byte-identical to the previously installed 298a2bf6 system; it differs in
the device tree (framebuffer@10000000, 0x400000, no-map), in bootargs
(no console=tty0) and in carrying logo.xrgb (SHA-256 6c7a3608…a3af).
U-Boot is the card’s existing source-built stage 1 with patches 0004/0005.
Raw serial logs and films stay off-repo (private); this directory holds sanitized excerpts, single frames and the scripts that produced every figure.
Sequence
| Step | Tool | Result |
|---|---|---|
| Preflight, stage, one-shot candidate boot | coherent-shell-board-* pipeline | preflight.json, host-inspection.json; PASS |
| Candidate runtime | runtime-check.py candidate | candidate-runtime.json; PASS |
| U-Boot hold with ArUco target logo (film2) | tools/splash-uboot-session.py hold | 2.2 measurement |
| U-Boot hold + framebuffer poke (film3) | … hold --poke | liveness, uboot-hold-poke-serial-excerpt.txt |
| Idle-kernel boot (film6) | … idle | 2.1 photo, 2.3, idle-kernel-serial-excerpt.txt |
| Install as default, ordinary autoboot (film7) | coherent-shell-board-boot.py --install | deployment-authorization.json, ordinary-runtime.json; PASS; 2.4 |
The candidate-boot film (film1) from the one-shot boot is the second 2.4 film.
2.1 Splash on the glass before Starting kernel
splash-at-uboot-before-kernel.jpg is film6 at session t = 17.99 s. The
session transcript (idle-kernel-serial-excerpt.txt, same clock, film start
2.01 s before the session) shows U-Boot printing
2799104 bytes read … RM69A10 direct XRGB8888 logo.xrgb full-screen OSD4
before the autoboot prompt, the boot files loading from t = 13.3 s, bootm
sent at t = 26.26 s, and only then Starting kernel .... The photograph is
taken while U-Boot is still loading, 8 s before bootm.
Deviation: the task names ./tools/capture-boot.py; the capture here is
tools/splash-uboot-session.py, which also timestamps every console line on
the host clock that the film start is recorded against.
2.2 Motion of the U-Boot-lit image
U-Boot drives the panel with {3, 97, 0x17, 0x87} at 49.5 MHz (patch 0004,
the values dsi-hsfreqrange-hardcoded.md measured). The logo was temporarily
replaced by tools/panel-target.py’s target converted with
target-to-xrgb.py (SHA-256 e2bb5820…bd60), the board held at K230#, and
the panel filmed (aruco-target-at-uboot-prompt.jpg).
tools/panel-measure.py could not register: only markers 0 and 1 are in the
current camera frame (it needs three). Its --record mode also fixes the
exposure at 200, which made the first attempt too dark to find any marker.
So the measurement uses camera-px-motion.py: the same norm1/shift
routine on an unregistered camera-pixel strip across the barcode field, as
the 0x87 rows of dsi-hsfreqrange-hardcoded.md were measured (camera px,
fixed camera, no registration). A static object in the same clip is the
camera’s noise floor.
film2, t = 15..50 s at K230# (1050 frames) | U-Boot target, strip y330:360 x560:1010 | control: keyboard, y60:120 x330:640 | 0x87 run 2 (earlier doc) |
|---|---|---|---|
| motion std | 0.01 px | 0.02 px | 0.02 px |
| span | 0.10 px | 0.15 px | 0.09 px |
| frame-to-frame mean | 0.01 px | 0.03 px | 0.00 px |
| frame-to-frame max | 0.06 px | 0.14 px | 0.04 px |
| jumps > 2 px | 0 | 0 | 0 |
| brightness range | 100..100% | 99..102% | 100..100% |
The U-Boot image’s motion is at the 0x87 run-2 level and below the static
keyboard’s apparent motion, i.e. below what this camera can resolve.
Frozen-panel check (the failure the earlier document warns about): in film3
mw.l 0x10000000 0xffffffff 0x1bbc0 at the U-Boot prompt (t = 38.28 s)
turns the top 200 rows white by t = 38.52 s (fb-poke-before.jpg,
fb-poke-after.jpg), and md.l reads the change back; the panel is
scanning the splash buffer live. The original logo was restored and its
hash checked before the next boot.
2.3 Idle kernel, nothing opening the display
tools/splash-uboot-session.py idle loaded the staged candidate’s files from
mmc 1:2 (each size and CRC32 checked), imported its bootargs, appended
systemd.mask=shell.service systemd.mask=seatd.service systemd.mask=k230-drm-splash.service for that boot only, and booted.
A first idle run without the third mask showed k230-drm-splash (the
Linux-side static splash owner) opening DRM at 45.2 s, so it was repeated.
At the serial root prompt, and again after a 90 s hold
(idle-kernel-serial-excerpt.txt):
/proc/device-tree/chosenlistscanaan,stage1-splash(FLAG_PRESENT);[ 2.812261] canaan-drm display-subsystem: [drm:canaan_drm_bind] stage 1 splash: leaving fbdev unset;reserved mem: 0x0000000010000000..0x00000000103fffff (4096 KiB) nomap non-reusable framebuffer@10000000;- no
canaan_panel,canaan-panelor VO/DSI preserve line at all — no panel prepare without a DRM client; ls /dev/fb0fails;shell,seatd,k230-drm-splashinactive; no process holds a/dev/dridescriptor (DRM_CLIENTS_LISTEDwith noDRM_CLIENTline).
splash-at-idle-kernel-prompt.jpg is film6 at session t = 147.99 s, during
the hold at that prompt: the logo is still on the glass. Film6 classifies
the logo continuously from 11.43 s to 204.77 s (the reboot), with only
1-2-frame other blips at reflection sweeps.
2.4 Frame timings, splash to the compositor’s first frame
frame-classes.py reduces the panel box of each frame to an edge map and
correlates it with a logo and a shell reference frame from the same film
(room reflections sweep the glass and make brightness useless; see
film*-classes.txt).
| Film (warm reboot) | logo first | logo last | compositor first frame | between |
|---|---|---|---|---|
| film1, one-shot candidate boot | 16.37 s | 80.93 s | 80.97 s | none |
| film7, installed ordinary autoboot | 42.43 s | 121.93 s | 121.97 s | none |
Across the Linux takeover by k230-drm-splash (kernel t = 45.2 s,
mid-span) the image does not change: the first Linux frame is the logo.
Most non-logo classifications inside the spans are the intact logo under a
moving reflection or a camera refocus (film7 49.43, 63-64, 69, 105.6 s;
film1 21.23 s). Some are not — see “Transient scanout glitches” below.
The switch to the
compositor is a single 33 ms frame step (handoff-film1-strip.jpg,
handoff-film7-strip.jpg). The compositor’s first frame is the shell’s
near-black navy background; the terminal prompt draws on it ~0.7 s later
(film7 122.70 s; film1 101.2 s, which an earlier brightness reading had
mistaken for the handoff). In film7 the whole picture, including the
keyboard and USB plug off the panel, dims 5-7% at 122.03-122.6 s: that is
the camera’s auto-exposure, not the panel.
Limit: a camera cannot tell a black frame from the compositor’s near-black
background, so “no dark frame” is shown as: no intermediate state between
the last logo frame and the compositor’s first frame at 30 fps, plus the
kernel’s own preserving VO/DSI to first plane update lines.
Transient scanout glitches on the splash system (open)
Every splash-kernel boot filmed shows the stage-1 image glitching for 1-4 frames during early userspace, with the scene static (film7 75.0 s: keyboard phase-correlation shift 0.00 px while the panel box shifts):
| Film | Film time | What the frame shows | Kernel time (approx.) |
|---|---|---|---|
| film1 | 43.30, 44.10 s | logo doubled/smeared | ~17 s (initrd activation, 16.5-19.3 s) |
| film6 | 56.70-56.73, 57.90-58.00 s | logo wrapped along the panel (“230K”) | ~27-28 s |
| film7 | 75.00-75.03, 76.00-76.07 s | logo displaced and garbled | ~26.5-27.5 s |
Kernel times are film time minus an estimated kernel start (film1/film6
from the bootm stamp plus ~1.3 s; film7 from the compositor’s first frame
at 121.97 s against sway reading its config at kernel 72.6 s), so they are
good to about +-2 s. The ~27 s windows fall inside Load Kernel Modules
and udev coldplug in the real root (25.9-29.9 s); no kernel message falls on
a glitch. The image always recovers on the next frames and is never
corrupted afterwards, which points at a scanout/sync disturbance rather
than at the reserved buffer being written. These are not dark frames, so
the 2.4 criterion as written is met, but they are visible: the operator
reported “the display flickers sometimes. real close to perfect”. Cause not
established; task 4.1 of the change keeps it open.
Ordinary kernel without the reservation (the flicker the operator saw)
After the U-Boot holds, the board autobooted the then-installed 298a2bf6
system, whose device tree has no splash reservation, with logo.xrgb now on
/boot. In films 2 and 3 that kernel tears the stage-1 image on single
frames (film2 58.9, 84.3-88.9 s; film3 65.3, 90.7-95.5 s;
unreserved-kernel-tear-film2-strip.jpg) and leaves noise on the glass
(film6 260 s) until its compositor draws. Neither splash-kernel boot shows
any such frame. Installing the splash system as the default (film7, 5418
frames) removed it; the operator had reported “the display flickers
sometimes” during these trials.
Not shown
Cold power-on (operator accepted warm reboot); a second card; ArUco-registered panel-row figures (camera out of the bench geometry); any touch or finger observation.