feat(renaser): pánico y OOM dejan testimonio por COM1
En pantalla solo cabe la franja roja: un grito breve, sin matiz. Para
diagnosticar colapsos en máquinas distintas a la del autor —donde no
es posible reproducirlos a mano—, los manejadores de fallo escriben
ahora una pista por COM1, además de pintar la franja.
- `baliza`: sumidero `Serie` que formatea sin tocar el heap, escribe a
0x3F8 con espera acotada (antes mudo que colgar el sistema).
- El panic-handler vuelca el mensaje y la ubicación del `panic!`.
- El alloc-error-handler vuelca el `Layout` que reventó el techo.
QEMU con `-serial stdio` enruta COM1 a la terminal de `cargo run` — la
pista llega a quien lanzó el kernel, aunque la pantalla esté en negro.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
diff --git a/renaser/kernel/src/baliza.rs b/renaser/kernel/src/baliza.rs
index 01b841e..5063eb4 100644
--- a/renaser/kernel/src/baliza.rs
+++ b/renaser/kernel/src/baliza.rs
@@ -7,12 +7,54 @@
// una franja de advertencia incluso cuando el resto del kernel ya no es fiable.
// =============================================================================
+use core::fmt::Write;
use core::panic::PanicInfo;
use core::ptr;
use core::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering};
use crate::grafico::{escribir_pixel_volatil, Pantalla};
+// =============================================================================
+// TESTIMONIO POR EL PUERTO SERIE — para diagnosticar colapsos sin pantalla
+// -----------------------------------------------------------------------------
+// En pantalla solo cabe la franja roja: un grito breve, sin matiz. Pero los
+// manejadores de fallo escriben TAMBIEN al puerto serie COM1 —que QEMU enruta
+// a la terminal de `cargo run` con `-serial stdio`—. Asi, cuando el kernel
+// colapsa fuera del Proxmox del autor, deja una pista legible de la causa.
+// =============================================================================
+
+/// Puerto de datos de COM1.
+const SERIE_DATOS: u16 = 0x3F8;
+/// Registro de estado de linea de COM1 — bit 5: el transmisor esta libre.
+const SERIE_LSR: u16 = 0x3FD;
+
+/// Envia un byte por COM1, con una espera acotada por si el firmware no nos lo
+/// dejo configurado. Si se agota la paciencia, calla — antes mudo que cuelgue.
+fn serie_escribir(byte: u8) {
+ for _ in 0..1_000_000 {
+ // SEGURIDAD: 0x3FD es el registro de estado de linea de COM1, fijo en
+ // la arquitectura PC; leerlo es inocuo.
+ let lsr = unsafe { x86_64::instructions::port::Port::<u8>::new(SERIE_LSR).read() };
+ if lsr & 0x20 != 0 {
+ // SEGURIDAD: 0x3F8 es el puerto de datos de COM1.
+ unsafe { x86_64::instructions::port::Port::<u8>::new(SERIE_DATOS).write(byte) };
+ return;
+ }
+ }
+}
+
+/// Sumidero de impresion al puerto serie — formatea sin tocar el heap.
+struct Serie;
+
+impl Write for Serie {
+ fn write_str(&mut self, s: &str) -> core::fmt::Result {
+ for &b in s.as_bytes() {
+ serie_escribir(b);
+ }
+ Ok(())
+ }
+}
+
/// Datos del framebuffer expuestos a los manejadores de fallo. Todo son
/// atomicos: la baliza es intrinsecamente `Sync`, sin cerrojos.
pub(crate) struct BalizaPanico {
@@ -102,27 +144,49 @@ pub(crate) static BALIZA_PANICO: BalizaPanico = BalizaPanico::apagada();
// MANEJADORES DE FALLO — cuando el sistema colapsa, lo DIBUJA
// =============================================================================
-/// Si renaser colapsa, tatuamos una franja ROJA en lo alto del framebuffer.
+/// Si renaser colapsa, tatuamos una franja ROJA en lo alto del framebuffer Y
+/// dejamos por COM1 un testimonio del panico: su mensaje, su lugar — la pista
+/// que en pantalla no cabe.
#[panic_handler]
-fn al_colapsar(_info: &PanicInfo) -> ! {
+fn al_colapsar(info: &PanicInfo) -> ! {
x86_64::instructions::interrupts::disable();
BALIZA_PANICO.pintar_banda(
0,
BALIZA_PANICO.franja(),
BALIZA_PANICO.pixel_alerta.load(Ordering::Relaxed),
);
+ let _ = writeln!(Serie);
+ let _ = writeln!(Serie, "*** renaser :: panico ***");
+ if let Some(lugar) = info.location() {
+ let _ = writeln!(
+ Serie,
+ " en {}:{}:{}",
+ lugar.file(),
+ lugar.line(),
+ lugar.column()
+ );
+ }
+ let _ = writeln!(Serie, " {}", info.message());
crate::detener()
}
-/// Si el heap se agota, tatuamos una franja NARANJA: un fallo distinto al
-/// colapso, y distinguible de un vistazo.
+/// Si el heap se agota, tatuamos una franja NARANJA y dejamos en el serie la
+/// disposicion que reviento el techo: tamaño y alineamiento.
#[alloc_error_handler]
-fn al_agotar_memoria(_disposicion: core::alloc::Layout) -> ! {
+fn al_agotar_memoria(disposicion: core::alloc::Layout) -> ! {
x86_64::instructions::interrupts::disable();
BALIZA_PANICO.pintar_banda(
0,
BALIZA_PANICO.franja(),
BALIZA_PANICO.pixel_oom.load(Ordering::Relaxed),
);
+ let _ = writeln!(Serie);
+ let _ = writeln!(Serie, "*** renaser :: agotamiento de memoria ***");
+ let _ = writeln!(
+ Serie,
+ " layout: tamaño={} alineamiento={}",
+ disposicion.size(),
+ disposicion.align()
+ );
crate::detener()
}