gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1389Shell 137Markdown 80Rust 802602 Definiciones

arranque-grafo: harness EFI robustos a quiet + efi-flicker-test (ADR 0010 paso 4)

Con el kernel metal cero-parpadeo (quiet), los marcadores serie kernel-side
(EFI-stub/Linux-version/EXT4) desaparecen. Los /init de pivote + /sbin/init
real ahora tee'an marcadores a /dev/ttyS0 (escritura al puerto directo, sortea
el printk ⇒ sobreviven quiet): HAMMER-EFI-DISK-PIVOT-OK + HAMMER-EFI-INIT-OK.
efi-disk-boot-test y efi-install-test pasan a apoyarse en ésos (kernel-side =
informativos). Compatible con el kernel viejo (verbose) también.

efi-flicker-test.sh: valida cero-parpadeo por captura de framebuffer OVMF-GOP
(media de brillo < umbral ⇒ pantalla negra, sin texto de boot). El seamless
i915 queda para metal Intel real.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

e80f15b6bc78cb963ac3cadd2d20205804974dba · por sergio · (hace 3 meses) · padre 72af9a802 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

5 archivos · +103 −11

diff --git a/scripts/efi-disk-boot-test.sh b/scripts/efi-disk-boot-test.sh
index bf592fd9..34633a9e 100755
--- a/scripts/efi-disk-boot-test.sh
+++ b/scripts/efi-disk-boot-test.sh
@@ -31,16 +31,18 @@ BOOT=1 KVM="$KVM" MEM="$MEM" DEADLINE="$DEADLINE" IMG="$IMG" \
 # marcadores (limpiamos escapes ANSI que mete OVMF)
 clean() { sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\r//g' "$LOG"; }
 fail=0
-# Los dos primeros bytes serie del handoff firmware→kernel se pierden a veces (EFI-stub/Linux-version):
-# son INFORMATIVOS. El veredicto se apoya en los marcadores AGUAS ABAJO — prueba concluyente de que
-# EFI-stub cargó el kernel+initrd (nada más podría montar hammer-root ni re-montar / sin esta cadena).
-info()  { if clean | grep -aqiE "$1"; then echo "  · $2"; else echo "  · ($2 — bytes serie tempranos perdidos, no bloqueante)"; fi; }
+# El kernel metal cero-parpadeo arranca con `quiet` ⇒ los marcadores kernel-side (EFI-stub, Linux
+# version, montaje/re-mount EXT4) NO llegan al serial. El veredicto se apoya en los marcadores USERSPACE
+# tee'ados a /dev/ttyS0 por el /init de pivote y el /sbin/init real — que escriben al PUERTO serie
+# directo, sorteando el printk ⇒ sobreviven `quiet`. Los kernel-side quedan como informativos.
+info()  { if clean | grep -aqiE "$1"; then echo "  · $2"; else echo "  · ($2 — suprimido por quiet o byte serie perdido, no bloqueante)"; fi; }
 check() { if clean | grep -aqiE "$1"; then echo "  ✓ $2"; else echo "  ✗ $2 (falta /$1/)"; fail=1; fi; }
 info  'EFI stub: Loaded initrd'                        'EFI-stub cargó kernel+initrd (sin GRUB/systemd-boot)'
-info  'Linux version 6\.16'                            'kernel arrancó'
-check 'HAMMER-EFI-DISK-PIVOT-OK|EXT4-fs \(vda2\): mounted' 'initramfs pivote montó hammer-root (⇒ EFI-stub cargó kernel+initrd)'
-check 'EXT4-fs \(vda3\): mounted'                      '/store montado por LABEL'
-check 'EXT4-fs \(vda2\): re-mounted'                   'switch_root → arje-zero re-montó / (PID1)'
+info  'EXT4-fs \(vda2\): mounted'                      'kernel montó hammer-root (kernel-side)'
+check 'HAMMER-EFI-DISK-PIVOT-OK'                       'initramfs pivote resolvió hammer-root y va a switch_root'
+check 'HAMMER-EFI-INIT-OK'                             'switch_root → arje-zero PID1 (init real corriendo)'
+reject_panic() { if clean | grep -aqiE 'HAMMER-EFI-FALLO|Kernel panic'; then echo "  ✗ hubo FALLO/panic de pivote"; fail=1; else echo "  ✓ sin FALLO ni panic"; fi; }
+reject_panic
 
 echo
 if [ "$fail" = 0 ]; then
diff --git a/scripts/efi-flicker-test.sh b/scripts/efi-flicker-test.sh
new file mode 100755
index 00000000..e21b88be
--- /dev/null
+++ b/scripts/efi-flicker-test.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+# efi-flicker-test.sh — valida el CERO-PARPADEO (ADR 0010 paso 4) del kernel metal, por captura de
+# framebuffer en OVMF-GOP. Arma el disco EFI real (install-image-efi.sh, cmdline `quiet` horneada +
+# FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER), lo bootea en OVMF con una GPU emulada y hace `screendump` por
+# QMP a los N s. Con deferred-takeover + quiet, fbcon NO toca el framebuffer ⇒ debe quedar NEGRO (media
+# de brillo ≈ 0) hasta que un compositor (mirada, no presente acá) abra el DRM master.
+#
+# NOTA: el *seamless* i915 (firmware GOP → simplefb → i915 sin re-modeset) sólo se valida en metal Intel
+# real; OVMF no trae i915. Lo que esto prueba es la mitad "pantalla limpia": sin texto de boot ni cursor.
+#
+# Uso:  ./scripts/efi-flicker-test.sh
+# Vars: ROOTFS KERNEL MEM (def 2048) WAIT (def 16) MEAN_MAX (def 1.5) OVMF_CODE OVMF_VARS
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
+MEM="${MEM:-2048}"; WAIT="${WAIT:-16}"; MEAN_MAX="${MEAN_MAX:-1.5}"
+OVMF_CODE="${OVMF_CODE:-/usr/share/edk2/x64/OVMF_CODE.4m.fd}"
+OVMF_VARS="${OVMF_VARS:-/usr/share/edk2/x64/OVMF_VARS.4m.fd}"
+WORK="$ROOT/work/efi-flicker-test"; mkdir -p "$WORK"
+IMG="$WORK/hammer-efi-disk.img"; SHOT="$WORK/screen.ppm"; VARS="$WORK/vars.fd"; QMP="$WORK/qmp.sock"
+[ -r "$OVMF_CODE" ] && [ -r "$OVMF_VARS" ] || { echo "no encuentro OVMF (set OVMF_CODE/OVMF_VARS)"; exit 1; }
+command -v identify >/dev/null || { echo "falta imagemagick (identify)"; exit 1; }
+
+echo "==> armando el disco EFI con install-image-efi.sh (kernel metal actual)"
+IMG="$IMG" ROOTFS="${ROOTFS:-}" KERNEL="${KERNEL:-}" ./scripts/install-image-efi.sh >"$WORK/build.log" 2>&1 \
+    || { echo "✗ build falló — ver $WORK/build.log"; tail -15 "$WORK/build.log"; exit 1; }
+
+echo "==> confirmando que el kernel trae la cmdline quiet + deferred-takeover"
+KCFG=$(ls -dt store/*-linux-metal/boot/config-* 2>/dev/null | head -1)
+grep -q 'CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y' "$KCFG" && echo "  ✓ DEFERRED_TAKEOVER=y" \
+    || echo "  ! el kernel metal AÚN no tiene DEFERRED_TAKEOVER=y (¿rebuild pendiente?)"
+
+echo "==> boot en OVMF-GOP + screendump a los ${WAIT}s"
+cp "$OVMF_VARS" "$VARS"; rm -f "$QMP"
+accel=(); [ -w /dev/kvm ] && accel=(-enable-kvm -cpu host)
+qemu-system-x86_64 -m "$MEM" -no-reboot "${accel[@]}" \
+    -drive if=pflash,unit=0,format=raw,readonly=on,file="$OVMF_CODE" \
+    -drive if=pflash,unit=1,format=raw,file="$VARS" \
+    -drive file="$IMG",format=raw,if=virtio \
+    -vga std -display none -serial file:"$WORK/serial.log" \
+    -qmp "unix:$QMP,server,nowait" >/dev/null 2>&1 &
+QPID=$!
+# QMP: capabilities → esperar → screendump
+python3 - "$QMP" "$SHOT" "$WAIT" <<'PY'
+import socket, json, sys, time, os
+qmp, shot, wait = sys.argv[1], sys.argv[2], int(sys.argv[3])
+s=None
+for _ in range(60):
+    try: s=socket.socket(socket.AF_UNIX); s.connect(qmp); break
+    except OSError: time.sleep(0.25)
+if not s: print("NO-QMP"); sys.exit(2)
+f=s.makefile("rwb",buffering=0); f.readline()
+f.write(b'{"execute":"qmp_capabilities"}\n'); f.readline()
+time.sleep(wait)
+f.write(json.dumps({"execute":"screendump","arguments":{"filename":shot}}).encode()+b"\n"); f.readline()
+time.sleep(0.5)
+PY
+kill "$QPID" 2>/dev/null || true; wait "$QPID" 2>/dev/null || true
+
+echo "==> analizando el framebuffer capturado"
+[ -r "$SHOT" ] || { echo "✗ no se capturó el framebuffer"; exit 1; }
+# media de brillo normalizada 0..255 (imagemagick reporta en % o 0..1 según versión → forzamos 0..255)
+MEAN=$(identify -verbose "$SHOT" | awk '/[Mm]ean:/ {gsub(/[()]/,""); print $2; exit}')
+# si identify reportó fracción 0..1, escalar ×255
+MEAN255=$(python3 -c "m=float('$MEAN'); print(m*255 if m<=1.0 else m)")
+GEO=$(identify -format '%wx%h' "$SHOT")
+printf "  framebuffer %s · media de brillo = %.3f / 255 (umbral cero-parpadeo: < %.1f)\n" "$GEO" "$MEAN255" "$MEAN_MAX"
+
+echo
+if python3 -c "import sys; sys.exit(0 if float('$MEAN255') < float('$MEAN_MAX') else 1)"; then
+    echo "✓✓ CERO-PARPADEO VERDE: el framebuffer queda NEGRO durante el arranque (sin texto de boot ni cursor)."
+    echo "   (El seamless i915 firmware→escritorio sólo se valida en metal Intel real.)"
+else
+    echo "✗ efi-flicker-test FALLÓ: el framebuffer tiene contenido (media ${MEAN255} ≥ ${MEAN_MAX})." >&2
+    echo "  Revisá $SHOT — probablemente texto de boot (¿kernel sin rebuild?) o getty en tty0." >&2
+    exit 1
+fi
diff --git a/scripts/efi-install-test.sh b/scripts/efi-install-test.sh
index 378bba94..100baaa3 100755
--- a/scripts/efi-install-test.sh
+++ b/scripts/efi-install-test.sh
@@ -67,9 +67,11 @@ clean() { sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\r//g' "$BLOG"; }
 fail=0
 check()  { if clean | grep -aqiE "$1"; then echo "  ✓ $2"; else echo "  ✗ $2" >&2; fail=1; fi; }
 reject() { if clean | grep -aqiE "$1"; then echo "  ✗ $2 (apareció)" >&2; fail=1; else echo "  ✓ $2"; fi; }
+# El kernel metal cero-parpadeo usa `quiet` ⇒ los marcadores válidos en serie son los USERSPACE tee'ados
+# a /dev/ttyS0 (sortean el printk). Los kernel-side EXT4 quedan como informativos.
 echo "==> asserts sobre el arranque del disco EFI instalado:"
-check  'HAMMER-EFI-DISK-PIVOT-OK|EXT4-fs \(vda2\): mounted' "initramfs de pivote montó hammer-root (⇒ EFI-stub lanzó BOOTX64.EFI)"
-check  'EXT4-fs \(vda2\): re-mounted'                        "switch_root → arje-zero re-montó / (PID1)"
+check  'HAMMER-EFI-DISK-PIVOT-OK'                            "initramfs de pivote resolvió hammer-root (⇒ EFI-stub lanzó BOOTX64.EFI)"
+check  'HAMMER-EFI-INIT-OK'                                  "switch_root → arje-zero PID1 (init real)"
 reject 'GNU GRUB'                                            "arrancó SIN GRUB (EFI-stub soberano)"
 reject 'HAMMER-EFI-FALLO|Kernel panic'                       "sin fallo de pivote ni panic"
 
diff --git a/scripts/hammer-live-install.sh b/scripts/hammer-live-install.sh
index f616859d..d97ffa03 100755
--- a/scripts/hammer-live-install.sh
+++ b/scripts/hammer-live-install.sh
@@ -160,7 +160,9 @@ INIT
     cat > "$MNT/sbin/init" <<'INIT'
 #!/bin/sh
 # Wrapper PID1 (rama UEFI): el initramfs de pivote ya montó / /store /var/lib/hammer por LABEL antes del
-# switch_root; sólo auto-sanamos un upgrade interrumpido y arrancamos el init real.
+# switch_root; sólo auto-sanamos un upgrade interrumpido y arrancamos el init real. El marcador a
+# /dev/ttyS0 sobrevive `quiet` (que suprime el printk kernel-side) ⇒ prueba serie de que llegó a PID1.
+echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > /dev/ttyS0 2>/dev/null || true
 [ -x /usr/sbin/hammer-recover ] && /usr/sbin/hammer-recover || true
 exec /usr/bin/arje-zero
 INIT
diff --git a/scripts/install-image-efi.sh b/scripts/install-image-efi.sh
index a8653cd7..b682ad82 100755
--- a/scripts/install-image-efi.sh
+++ b/scripts/install-image-efi.sh
@@ -117,6 +117,16 @@ echo "==> staging root (hardlinks del rootfs, sin /store ni /var/lib/hammer)"
 cp -al "$ROOTFS" "$ROOT_TREE"
 rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
 mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
+# /sbin/init: wrapper que emite un marcador a /dev/ttyS0 (sobrevive `quiet`, que suprime el printk
+# kernel-side) y hace exec de arje-zero. Prueba positiva de que el switch_root llegó al init real.
+rm -f "$ROOT_TREE/sbin/init"
+cat > "$ROOT_TREE/sbin/init" <<'RINIT'
+#!/bin/sh
+# PID1 real tras el switch_root del pivote (store/estado ya montados por LABEL en el initramfs).
+echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > /dev/ttyS0 2>/dev/null || true
+exec /usr/bin/arje-zero
+RINIT
+chmod +x "$ROOT_TREE/sbin/init"
 
 # --- 3) ESP FAT32: BOOTX64.EFI (=bzImage EFI-stub) + initramfs.cpio.gz, poblada con mtools de hammer ---
 echo "==> ESP FAT32 (${ESP_MB}M): \\EFI\\BOOT\\BOOTX64.EFI (=bzImage) + \\initramfs.cpio.gz"