feat(renaser): Fase 8a — el compositor teselante
1// =============================================================================
2// renaser :: kernel/src/main.rs — el punto de entrada y la orquestacion
3// -----------------------------------------------------------------------------
4// Aqui no nace una terminal: nace una superficie. renaser es un kernel
5// asincrono de Espacio de Direccionamiento Unico (SASOS) que rompe con el
6// paradigma POSIX — sin TTYs, sin archivos planos, sin capas GNU.
7//
8// Este archivo es deliberadamente delgado: solo el arranque y el cableado.
9// Cada subsistema vive en su propio modulo (ver `ARCHITECTURE.md`):
10//
11// grafico — color, framebuffer fisico y lienzo de doble bufer.
12// consola — superficie de texto/imagen; rasteriza glifos con fontdue.
13// baliza — red de seguridad visual; manejadores de panico y de OOM.
14// sync — `CeldaSync`, la celda de inicializacion unica.
15// gdt — GDT + TSS + stack de emergencia del doble fallo.
16// interrupts — IDT, excepciones de CPU e interrupciones de hardware.
17// pic — el par 8259 remapeado y el temporizador (PIT).
18// drivers — descubrimiento de hardware y E/S de disco asincrona por
19// interrupcion: el bus PCI y el virtio-blk (Fases 6.1, 6.2).
20// almacen — el grafo de objetos direccionado por contenido (Fase 6.1c).
21// manifiesto — el Manifiesto de Genesis: que apps nacen del grafo (Fase 7).
22// compositor — el teselado de las ventanas con `mirada-layout` (Fase 8).
23// memory — el heap dinamico del kernel (`#[global_allocator]`).
24// async_system — el reactor cooperativo: ejecutor, tareas, wakers, teclado
25// y el reloj que marca el compas de los fotogramas (Fase 5).
26// texto — rasterizacion de tipografia vectorial (fontdue).
27// wasm — el runtime WebAssembly, la matriz de capacidades y el
28// escudo de combustible que acota el tiempo de cada app.
29// =============================================================================
30
31#![no_std] // Prohibido `std`: bajo nosotros no hay sistema operativo alguno.
32#![no_main] // El punto de entrada lo define el cargador, no la convencion C.
33#![feature(abi_x86_interrupt)] // ABI de las rutinas de excepcion (Fase 2).
34#![feature(alloc_error_handler)] // Manejador propio de agotamiento de heap (Fase 3).
35#![deny(unsafe_op_in_unsafe_fn)] // Cada operacion `unsafe` se justifica explicitamente.
36
37extern crate alloc; // El heap esta vivo: `alloc::*` queda disponible (Fase 3).
38
39use alloc::format;
40
41use bootloader_api::config::{BootloaderConfig, Mapping};
42use bootloader_api::info::{FrameBufferInfo, MemoryRegionKind, MemoryRegions, PixelFormat};
43use bootloader_api::{entry_point, BootInfo};
44use spin::Mutex;
45
46// --- Subsistemas del kernel ---
47mod almacen;
48mod async_system;
49mod baliza;
50mod compositor;
51mod consola;
52mod drivers;
53mod gdt;
54mod grafico;
55mod interrupts;
56mod manifiesto;
57mod memory;
58mod pic;
59mod sync;
60mod texto;
61mod wasm;
62
63// Reexportaciones para que los submodulos conserven rutas `crate::` estables.
64pub(crate) use consola::volcar_marco_wasm;
65pub(crate) use sync::CeldaSync;
66
67use async_system::executor::Executor;
68use baliza::BALIZA_PANICO;
69use consola::{Consola, CONSOLA};
70use grafico::{
71 codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, RegionPantalla, ALTO_MAX,
72 ANCHO_MAX,
73};
74
75/// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU.
76static CONFIG_ARRANQUE: BootloaderConfig = {
77 let mut config = BootloaderConfig::new_default();
78 // Pedimos la memoria fisica mapeada: cimiento para futuras fases.
79 config.mappings.physical_memory = Some(Mapping::Dynamic);
80 config
81};
82
83// `entry_point!` genera el simbolo `_start`, valida la firma de `kernel_main`
84// y nos transfiere el control con seguridad de tipos.
85entry_point!(kernel_main, config = &CONFIG_ARRANQUE);
86
87/// Detiene la CPU de forma definitiva: `hlt` la duerme hasta una interrupcion.
88pub(crate) fn detener() -> ! {
89 loop {
90 x86_64::instructions::hlt();
91 }
92}
93
94/// Tarea cooperativa de una aplicacion WASM. En cada pulso del reloj le concede
95/// un `tick` —un fotograma de trabajo— y cede la CPU hasta el siguiente; entre
96/// medias corren sus vecinas. Si la app falla o agota su combustible, se la
97/// DESALOJA: se tatua su region con la baliza de purpura y la tarea concluye.
98/// El ejecutor la retira del censo, su memoria se libera, el kernel sigue vivo.
99async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) {
100 loop {
101 async_system::reloj::EsperaFrame::nueva().await;
102 if let Err(falla) = app.tick() {
103 // El color de la baliza delata la causa: purpura si agoto su tiempo
104 // o aborto, amarillo si reviento su techo de memoria.
105 consola::pintar_desalojo(app.marco(), falla.color_baliza());
106 return;
107 }
108 }
109}
110
111/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el
112/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las
113/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el
114/// bloque esta listo. Deja en la consola el resultado y cuantas interrupciones
115/// de disco se atendieron — el testigo de que la E/S por sondeo quedo atras.
116async fn tarea_sonda_disco() {
117 let resultado = drivers::disco::leer_bloques(0, 1).await;
118 let Some(consola) = CONSOLA.get() else {
119 return;
120 };
121 let mut consola = consola.lock();
122 match resultado {
123 Ok(_) => consola.escribir(&format!(
124 "disco :: sonda asincrona OK -- {} IRQ de disco atendidas\n",
125 drivers::disco::pulsos_disco(),
126 )),
127 Err(motivo) => {
128 consola.escribir(&format!("disco :: sonda asincrona fallida -- {motivo}\n"))
129 }
130 }
131 consola.presentar();
132}
133
134/// Da vida a una aplicacion del userspace a partir de su `EntradaApp` del
135/// manifiesto: recupera su bytecode del grafo, la carga en el `marco` que el
136/// compositor le teselo y la despacha como tarea cooperativa del reactor. Si
137/// el bytecode falta, esta corrupto, o la carga fracasa, se salda pintando el
138/// marco con la baliza de desalojo — el kernel no se inmuta y sigue con las
139/// demas.
140fn encender_app(
141 ejecutor: &mut Executor,
142 indice: usize,
143 entrada: &manifiesto::EntradaApp,
144 marco: RegionPantalla,
145) {
146 // El tamaño NATURAL del lienzo de la app —lo que sabe pintar, fijo— lo
147 // dicta su `EntradaApp`; el compositor le asigno `marco` como ventana.
148 let natural = manifiesto::region(entrada);
149 // Recuperar el bytecode del grafo. `recuperar` recomputa el hash del
150 // objeto y verifica su integridad: un bytecode corrupto se delata aqui
151 // —y la app se niega, no se instancia un modulo en el que no se confia.
152 let bytecode = match almacen::recuperar(&entrada.bytecode) {
153 Ok(Some(objeto)) => objeto.datos,
154 _ => {
155 consola::pintar_desalojo(marco, Color::DESALOJO);
156 return;
157 }
158 };
159 // `indice` es la identidad de la app en el manifiesto: las capacidades de
160 // estado persistido (Fase 7c) la usan para hallar SU ranura `estado`.
161 match wasm::AplicacionWasm::cargar(
162 &bytecode,
163 marco,
164 natural.ancho,
165 natural.alto,
166 entrada.techo_memoria as usize,
167 indice,
168 ) {
169 Ok(app) => ejecutor.spawn(tarea_aplicacion(app)),
170 Err(_) => consola::pintar_desalojo(marco, Color::DESALOJO),
171 }
172}
173
174/// Escribe una linea en la consola global y la presenta. Atajo para los
175/// informes de arranque; no hace nada si la consola aun no existe.
176fn reportar(linea: &str) {
177 if let Some(consola) = CONSOLA.get() {
178 let mut consola = consola.lock();
179 consola.escribir(linea);
180 consola.escribir("\n");
181 consola.presentar();
182 }
183}
184
185/// FASE 7 :: puebla el userspace DESDE EL GRAFO. Carga el Manifiesto de
186/// Genesis que `boot` sembro en la imagen de disco, lo instala como el
187/// manifiesto VIVO del kernel y, por cada `EntradaApp`, enciende su
188/// aplicacion. Toda falla se reporta a la consola y NO detiene el arranque: el
189/// kernel se levanta con las apps que pueda — o con ninguna, si el grafo no
190/// tiene userspace.
191fn cargar_userspace(ejecutor: &mut Executor, ancho_pantalla: usize, alto_pantalla: usize) {
192 let manifiesto = match manifiesto::cargar() {
193 Ok(Some(m)) => Some(m),
194 // Disco sin manifiesto anclado: `boot` no lo sembro. El kernel se
195 // levanta sin userspace —pero se levanta—; en la practica, ninguna
196 // imagen forjada por `boot` llega aqui sin su Manifiesto de Genesis.
197 Ok(None) => {
198 reportar("manifiesto :: el disco no trae uno -- el kernel se levanta solo");
199 None
200 }
201 Err(motivo) => {
202 reportar(&format!("manifiesto :: carga fallida -- {motivo}"));
203 None
204 }
205 };
206
207 match &manifiesto {
208 Some(m) => reportar(&format!(
209 "manifiesto :: {} apps nacidas del grafo",
210 m.apps.len(),
211 )),
212 None => reportar("manifiesto :: sin userspace -- el kernel se levanta solo"),
213 }
214
215 if let Some(m) = manifiesto {
216 // Instalar el manifiesto VIVO ANTES de instanciar las apps: el `init`
217 // de cada app puede consultar su estado persistido (Fase 7c), y esa
218 // consulta lee del manifiesto vivo.
219 manifiesto::instalar(m.clone());
220
221 // FASE 8 :: el compositor tesela el area de apps — un marco por
222 // ventana, calculado por `mirada-layout`. Se pinta el escenario (el
223 // area y sus marcos) antes de encender las apps: el teselado se ve
224 // aunque alguna app no llegue siquiera a pintar su primer fotograma.
225 let marcos = compositor::disponer(m.apps.len(), ancho_pantalla, alto_pantalla);
226 consola::pintar_escenario(
227 compositor::area_apps(ancho_pantalla, alto_pantalla),
228 &marcos,
229 );
230 for (indice, (entrada, marco)) in m.apps.iter().zip(marcos).enumerate() {
231 encender_app(ejecutor, indice, entrada, marco);
232 }
233 }
234}
235
236/// Localiza la mayor region de RAM libre que el cargador reporto — la cantera
237/// de la que el DMA del disco tomara sus marcos fisicos.
238fn mayor_region_usable(regiones: &MemoryRegions) -> Option<(u64, u64)> {
239 regiones
240 .iter()
241 .filter(|region| matches!(region.kind, MemoryRegionKind::Usable))
242 .map(|region| (region.start, region.end))
243 .max_by_key(|&(inicio, fin)| fin - inicio)
244}
245
246/// FASE 6.1c — funda el grafo de objetos. Monta el disco virtio-blk, lee o
247/// forja el superbloque, reconstruye el indice recorriendo el log y deja
248/// constancia visual: cuantos sectores tiene el disco, cuantos objetos viven
249/// ya en el grafo y si el arranque encontro —o no— una raiz de la que tirar.
250fn informar_almacen() {
251 // Fundar el almacen ANTES de tomar el cerrojo de la consola: el montaje
252 // del disco hace E/S por sondeo y nada de ello reclama la consola.
253 let resultado = almacen::init();
254 let Some(consola) = CONSOLA.get() else {
255 return;
256 };
257 let mut consola = consola.lock();
258 match resultado {
259 Ok(resumen) => {
260 let estado = if resumen.formateado {
261 "disco formateado"
262 } else {
263 "grafo montado"
264 };
265 consola.escribir(&format!(
266 "almacen :: {} :: {} sectores :: {} objetos :: raiz {}\n",
267 estado,
268 resumen.capacidad,
269 resumen.objetos,
270 if resumen.raiz { "presente" } else { "ausente" },
271 ));
272 }
273 Err(motivo) => {
274 consola.escribir(&format!("almacen :: fallo :: {motivo}\n"));
275 }
276 }
277 // FASE 6.2 — dejar constancia de la linea de IRQ por la que el disco
278 // anunciara, ya sin sondeo, el fin de cada transferencia.
279 match drivers::disco::irq() {
280 Some(irq) => {
281 consola.escribir(&format!("disco :: virtio-blk en IRQ {irq} -- E/S asincrona\n"))
282 }
283 None => consola.escribir("disco :: IRQ no enrutada -- E/S por sondeo\n"),
284 }
285 consola.presentar();
286}
287
288// =============================================================================
289// PUNTO DE ENTRADA DEL KERNEL
290// =============================================================================
291
292fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
293 // --- 1. Recuperar el framebuffer GOP que el firmware nos confio. ---
294 let framebuffer = match boot_info.framebuffer.as_mut() {
295 Some(fb) => fb,
296 None => detener(),
297 };
298 let info: FrameBufferInfo = framebuffer.info();
299 let formato: PixelFormat = info.pixel_format;
300 let pantalla = Pantalla::adoptar(framebuffer, info);
301
302 // Datos para la sonda de disco (Fase 6.1b): el offset al que el cargador
303 // mapeo la memoria fisica y la mayor region de RAM libre para el DMA.
304 let offset_fisico = boot_info.physical_memory_offset.into_option();
305 let region_dma = mayor_region_usable(&boot_info.memory_regions);
306
307 // --- 2. Encender la baliza: la red de seguridad visual va primero. ---
308 BALIZA_PANICO.encender(
309 &pantalla,
310 codificar(formato, Color::ALERTA),
311 codificar(formato, Color::OOM),
312 );
313
314 // --- 3. Cimientos de fallos e interrupciones (Fases 2.0 y 2.1). ---
315 gdt::init();
316 interrupts::init();
317 pic::init();
318
319 // --- 4. FASE 3 :: fundar el heap. A partir de aqui, `alloc` esta vivo. ---
320 memory::init();
321
322 // --- 5. Con el heap activo, fundar lo que depende de el: el canal de
323 // scancodes, el reloj de fotogramas y la tipografia vectorial. ---
324 async_system::teclado::init();
325 async_system::reloj::init();
326 texto::init();
327
328 // --- 6. Construir el lienzo y la consola; pintar el rotulo inicial,
329 // ya rasterizado por fontdue, y publicar la consola globalmente. ---
330 let memoria = match reclamar_memoria_lienzo() {
331 Some(m) => m,
332 None => detener(),
333 };
334 let ancho_lienzo = info.width.min(ANCHO_MAX);
335 let alto_lienzo = info.height.min(ALTO_MAX);
336 let mut lienzo = Lienzo::nuevo(memoria, ancho_lienzo, alto_lienzo, formato);
337 lienzo.limpiar(Color::LIENZO_EN_REPOSO);
338
339 let mut consola = Consola::nueva(lienzo, pantalla);
340 consola.escribir("renaser :: fase 6.2 -- E/S de disco asincrona por interrupcion\n");
341 consola.presentar();
342 CONSOLA.call_once(|| Mutex::new(consola));
343
344 // --- 6.5. FASE 6.1c :: fundar el subsistema de disco y, sobre el, el grafo
345 // de objetos: enumerar el bus PCI, montar el transporte virtio-blk,
346 // y leer o forjar el superbloque del almacen direccionado por
347 // contenido. El kernel adquiere, por fin, una memoria que perdura. ---
348 match (offset_fisico, region_dma) {
349 (Some(offset), Some((inicio, fin))) => {
350 drivers::disco::init(offset, inicio, fin);
351 informar_almacen();
352 }
353 _ => {
354 if let Some(consola) = CONSOLA.get() {
355 let mut consola = consola.lock();
356 consola.escribir("virtio-blk :: omitido -- memoria fisica sin mapear\n");
357 consola.presentar();
358 }
359 }
360 }
361
362 // --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL
363 // GRAFO. El kernel ya no empotra los modulos WASM: lee el
364 // Manifiesto de Genesis que `boot` sembro en la imagen de disco e
365 // instancia cada `EntradaApp` recuperando su bytecode del grafo de
366 // objetos. Las cinco apps de genesis (dos instancias de hello, la
367 // discola, la glotona y la cronista) nacen del disco, no del
368 // binario del kernel.
369 //
370 // Las interrupciones se habilitan AHORA: el temporizador marcara el
371 // compas de los fotogramas y la IRQ del teclado difundira cada
372 // scancode a los canales que las apps consultan. ---
373 let mut ejecutor = Executor::nuevo();
374 cargar_userspace(&mut ejecutor, ancho_lienzo, alto_lienzo);
375 // FASE 6.2 :: una tarea mas del reactor — no una app WASM— que sondea el
376 // disco de forma ASINCRONA: la demostracion de que la IRQ del disco
377 // conduce la E/S sin detener a las aplicaciones visuales.
378 ejecutor.spawn(tarea_sonda_disco());
379 x86_64::instructions::interrupts::enable();
380 ejecutor.run();
381}