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"