feat(renaser): Fase 7 — apertura: plan y andamiaje del Manifiesto de Génesis
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// memory — el heap dinamico del kernel (`#[global_allocator]`).
23// async_system — el reactor cooperativo: ejecutor, tareas, wakers, teclado
24// y el reloj que marca el compas de los fotogramas (Fase 5).
25// texto — rasterizacion de tipografia vectorial (fontdue).
26// wasm — el runtime WebAssembly, la matriz de capacidades y el
27// escudo de combustible que acota el tiempo de cada app.
28// =============================================================================
29
30#![no_std] // Prohibido `std`: bajo nosotros no hay sistema operativo alguno.
31#![no_main] // El punto de entrada lo define el cargador, no la convencion C.
32#![feature(abi_x86_interrupt)] // ABI de las rutinas de excepcion (Fase 2).
33#![feature(alloc_error_handler)] // Manejador propio de agotamiento de heap (Fase 3).
34#![deny(unsafe_op_in_unsafe_fn)] // Cada operacion `unsafe` se justifica explicitamente.
35
36extern crate alloc; // El heap esta vivo: `alloc::*` queda disponible (Fase 3).
37
38use alloc::format;
39
40use bootloader_api::config::{BootloaderConfig, Mapping};
41use bootloader_api::info::{FrameBufferInfo, MemoryRegionKind, MemoryRegions, PixelFormat};
42use bootloader_api::{entry_point, BootInfo};
43use spin::Mutex;
44
45// --- Subsistemas del kernel ---
46mod almacen;
47mod async_system;
48mod baliza;
49mod consola;
50mod drivers;
51mod gdt;
52mod grafico;
53mod interrupts;
54mod manifiesto;
55mod memory;
56mod pic;
57mod sync;
58mod texto;
59mod wasm;
60
61// Reexportaciones para que los submodulos conserven rutas `crate::` estables.
62pub(crate) use consola::volcar_marco_wasm;
63pub(crate) use sync::CeldaSync;
64
65use async_system::executor::Executor;
66use baliza::BALIZA_PANICO;
67use consola::{Consola, CONSOLA};
68use grafico::{
69 codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, RegionPantalla, ALTO_MAX,
70 ANCHO_MAX,
71};
72
73/// El modulo WASM del userspace, empotrado en el binario del kernel para esta
74/// fase de pruebas. Es un `.wasm` puro, compilado aparte para `wasm32`. La
75/// Fase 5 lo instancia DOS veces —el mismo bytecode, dos regiones distintas—
76/// para demostrar la multitarea cooperativa sobre el espacio unico.
77static APP_WASM: &[u8] = include_bytes!("../assets/app.wasm");
78
79/// La aplicacion DISCOLA: un modulo WASM cuyo `tick` cae en un bucle cerrado y
80/// jamas retorna. Existe para una sola cosa — demostrar que el guardarrail de
81/// combustible la fulmina sin despeinar al kernel ni a sus vecinas.
82static DISCOLA_WASM: &[u8] = include_bytes!("../assets/discola.wasm");
83
84/// La aplicacion GLOTONA: un modulo WASM que reclama memoria lineal sin freno.
85/// Demuestra el guardarrail ESPACIAL — el techo de memoria la desaloja con la
86/// baliza amarilla, gemela de la purpura del desalojo por combustible.
87static GLOTONA_WASM: &[u8] = include_bytes!("../assets/glotona.wasm");
88
89/// La aplicacion CRONISTA: la primera ciudadana del userspace que escribe en el
90/// almacenamiento PERSISTENTE. En cada arranque graba un objeto en el grafo
91/// —enlazado al del arranque anterior—, lo corona como raiz y pinta una celda
92/// por cada arranque registrado. El disco recuerda; la cuenta sobrevive a los
93/// reinicios. Demuestra las capacidades `sys_object_*` de la Fase 6.1c.
94static CRONISTA_WASM: &[u8] = include_bytes!("../assets/cronista.wasm");
95
96/// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU.
97static CONFIG_ARRANQUE: BootloaderConfig = {
98 let mut config = BootloaderConfig::new_default();
99 // Pedimos la memoria fisica mapeada: cimiento para futuras fases.
100 config.mappings.physical_memory = Some(Mapping::Dynamic);
101 config
102};
103
104// `entry_point!` genera el simbolo `_start`, valida la firma de `kernel_main`
105// y nos transfiere el control con seguridad de tipos.
106entry_point!(kernel_main, config = &CONFIG_ARRANQUE);
107
108/// Detiene la CPU de forma definitiva: `hlt` la duerme hasta una interrupcion.
109pub(crate) fn detener() -> ! {
110 loop {
111 x86_64::instructions::hlt();
112 }
113}
114
115/// Tarea cooperativa de una aplicacion WASM. En cada pulso del reloj le concede
116/// un `tick` —un fotograma de trabajo— y cede la CPU hasta el siguiente; entre
117/// medias corren sus vecinas. Si la app falla o agota su combustible, se la
118/// DESALOJA: se tatua su region con la baliza de purpura y la tarea concluye.
119/// El ejecutor la retira del censo, su memoria se libera, el kernel sigue vivo.
120async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) {
121 loop {
122 async_system::reloj::EsperaFrame::nueva().await;
123 if let Err(falla) = app.tick() {
124 // El color de la baliza delata la causa: purpura si agoto su tiempo
125 // o aborto, amarillo si reviento su techo de memoria.
126 consola::pintar_desalojo(app.region(), falla.color_baliza());
127 return;
128 }
129 }
130}
131
132/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el
133/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las
134/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el
135/// bloque esta listo. Deja en la consola el resultado y cuantas interrupciones
136/// de disco se atendieron — el testigo de que la E/S por sondeo quedo atras.
137async fn tarea_sonda_disco() {
138 let resultado = drivers::disco::leer_bloques(0, 1).await;
139 let Some(consola) = CONSOLA.get() else {
140 return;
141 };
142 let mut consola = consola.lock();
143 match resultado {
144 Ok(_) => consola.escribir(&format!(
145 "disco :: sonda asincrona OK -- {} IRQ de disco atendidas\n",
146 drivers::disco::pulsos_disco(),
147 )),
148 Err(motivo) => {
149 consola.escribir(&format!("disco :: sonda asincrona fallida -- {motivo}\n"))
150 }
151 }
152 consola.presentar();
153}
154
155/// Da vida a una aplicacion del userspace: la carga en su region y, si lo
156/// logra, la despacha como tarea cooperativa del reactor. Una carga fallida se
157/// salda pintando su region con la baliza de desalojo — el kernel no se inmuta.
158fn encender_app(ejecutor: &mut Executor, bytecode: &'static [u8], region: RegionPantalla) {
159 match wasm::AplicacionWasm::cargar(bytecode, region) {
160 Ok(app) => ejecutor.spawn(tarea_aplicacion(app)),
161 Err(_) => consola::pintar_desalojo(region, Color::DESALOJO),
162 }
163}
164
165/// Localiza la mayor region de RAM libre que el cargador reporto — la cantera
166/// de la que el DMA del disco tomara sus marcos fisicos.
167fn mayor_region_usable(regiones: &MemoryRegions) -> Option<(u64, u64)> {
168 regiones
169 .iter()
170 .filter(|region| matches!(region.kind, MemoryRegionKind::Usable))
171 .map(|region| (region.start, region.end))
172 .max_by_key(|&(inicio, fin)| fin - inicio)
173}
174
175/// FASE 6.1c — funda el grafo de objetos. Monta el disco virtio-blk, lee o
176/// forja el superbloque, reconstruye el indice recorriendo el log y deja
177/// constancia visual: cuantos sectores tiene el disco, cuantos objetos viven
178/// ya en el grafo y si el arranque encontro —o no— una raiz de la que tirar.
179fn informar_almacen() {
180 // Fundar el almacen ANTES de tomar el cerrojo de la consola: el montaje
181 // del disco hace E/S por sondeo y nada de ello reclama la consola.
182 let resultado = almacen::init();
183 let Some(consola) = CONSOLA.get() else {
184 return;
185 };
186 let mut consola = consola.lock();
187 match resultado {
188 Ok(resumen) => {
189 let estado = if resumen.formateado {
190 "disco formateado"
191 } else {
192 "grafo montado"
193 };
194 consola.escribir(&format!(
195 "almacen :: {} :: {} sectores :: {} objetos :: raiz {}\n",
196 estado,
197 resumen.capacidad,
198 resumen.objetos,
199 if resumen.raiz { "presente" } else { "ausente" },
200 ));
201 }
202 Err(motivo) => {
203 consola.escribir(&format!("almacen :: fallo :: {motivo}\n"));
204 }
205 }
206 // FASE 6.2 — dejar constancia de la linea de IRQ por la que el disco
207 // anunciara, ya sin sondeo, el fin de cada transferencia.
208 match drivers::disco::irq() {
209 Some(irq) => {
210 consola.escribir(&format!("disco :: virtio-blk en IRQ {irq} -- E/S asincrona\n"))
211 }
212 None => consola.escribir("disco :: IRQ no enrutada -- E/S por sondeo\n"),
213 }
214 consola.presentar();
215}
216
217// =============================================================================
218// PUNTO DE ENTRADA DEL KERNEL
219// =============================================================================
220
221fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
222 // --- 1. Recuperar el framebuffer GOP que el firmware nos confio. ---
223 let framebuffer = match boot_info.framebuffer.as_mut() {
224 Some(fb) => fb,
225 None => detener(),
226 };
227 let info: FrameBufferInfo = framebuffer.info();
228 let formato: PixelFormat = info.pixel_format;
229 let pantalla = Pantalla::adoptar(framebuffer, info);
230
231 // Datos para la sonda de disco (Fase 6.1b): el offset al que el cargador
232 // mapeo la memoria fisica y la mayor region de RAM libre para el DMA.
233 let offset_fisico = boot_info.physical_memory_offset.into_option();
234 let region_dma = mayor_region_usable(&boot_info.memory_regions);
235
236 // --- 2. Encender la baliza: la red de seguridad visual va primero. ---
237 BALIZA_PANICO.encender(
238 &pantalla,
239 codificar(formato, Color::ALERTA),
240 codificar(formato, Color::OOM),
241 );
242
243 // --- 3. Cimientos de fallos e interrupciones (Fases 2.0 y 2.1). ---
244 gdt::init();
245 interrupts::init();
246 pic::init();
247
248 // --- 4. FASE 3 :: fundar el heap. A partir de aqui, `alloc` esta vivo. ---
249 memory::init();
250
251 // --- 5. Con el heap activo, fundar lo que depende de el: el canal de
252 // scancodes, el reloj de fotogramas y la tipografia vectorial. ---
253 async_system::teclado::init();
254 async_system::reloj::init();
255 texto::init();
256
257 // --- 6. Construir el lienzo y la consola; pintar el rotulo inicial,
258 // ya rasterizado por fontdue, y publicar la consola globalmente. ---
259 let memoria = match reclamar_memoria_lienzo() {
260 Some(m) => m,
261 None => detener(),
262 };
263 let mut lienzo = Lienzo::nuevo(
264 memoria,
265 info.width.min(ANCHO_MAX),
266 info.height.min(ALTO_MAX),
267 formato,
268 );
269 lienzo.limpiar(Color::LIENZO_EN_REPOSO);
270
271 let mut consola = Consola::nueva(lienzo, pantalla);
272 consola.escribir("renaser :: fase 6.2 -- E/S de disco asincrona por interrupcion\n");
273 consola.presentar();
274 CONSOLA.call_once(|| Mutex::new(consola));
275
276 // --- 6.5. FASE 6.1c :: fundar el subsistema de disco y, sobre el, el grafo
277 // de objetos: enumerar el bus PCI, montar el transporte virtio-blk,
278 // y leer o forjar el superbloque del almacen direccionado por
279 // contenido. El kernel adquiere, por fin, una memoria que perdura. ---
280 match (offset_fisico, region_dma) {
281 (Some(offset), Some((inicio, fin))) => {
282 drivers::disco::init(offset, inicio, fin);
283 informar_almacen();
284 }
285 _ => {
286 if let Some(consola) = CONSOLA.get() {
287 let mut consola = consola.lock();
288 consola.escribir("virtio-blk :: omitido -- memoria fisica sin mapear\n");
289 consola.presentar();
290 }
291 }
292 }
293
294 // --- 7. FASE 5/6 :: levantar el reactor y poblar el userspace con CINCO
295 // aplicaciones WASM concurrentes, cada una en su propia region:
296 //
297 // * App 1 — instancia de hello_wasm, a la izquierda, gobernada
298 // por el teclado.
299 // * App 2 — segunda instancia del MISMO bytecode, a la derecha:
300 // un unico modulo en la RAM unificada, dos vidas aisladas.
301 // * App discola — su `tick` es un bucle cerrado: el escudo de
302 // COMBUSTIBLE la desaloja (baliza purpura) en su 1er fotograma.
303 // * App glotona — reclama memoria sin freno: el escudo ESPACIAL
304 // la desaloja (baliza amarilla) en su 1er fotograma.
305 // * App cronista — escribe en el GRAFO DE OBJETOS persistente:
306 // cada arranque deja un objeto enlazado al anterior y pinta una
307 // celda por arranque. El disco recuerda entre reinicios.
308 //
309 // Las interrupciones se habilitan AHORA: el temporizador marcara el
310 // compas de los fotogramas y la IRQ del teclado difundira cada
311 // scancode a los canales que las apps consultan. ---
312 let mut ejecutor = Executor::nuevo();
313 encender_app(
314 &mut ejecutor,
315 APP_WASM,
316 RegionPantalla { x: 100, y: 120, ancho: 480, alto: 560 },
317 );
318 encender_app(
319 &mut ejecutor,
320 APP_WASM,
321 RegionPantalla { x: 700, y: 120, ancho: 480, alto: 560 },
322 );
323 encender_app(
324 &mut ejecutor,
325 DISCOLA_WASM,
326 RegionPantalla { x: 60, y: 700, ancho: 360, alto: 80 },
327 );
328 encender_app(
329 &mut ejecutor,
330 GLOTONA_WASM,
331 RegionPantalla { x: 460, y: 700, ancho: 360, alto: 80 },
332 );
333 encender_app(
334 &mut ejecutor,
335 CRONISTA_WASM,
336 RegionPantalla { x: 860, y: 700, ancho: 360, alto: 80 },
337 );
338 // FASE 6.2 :: una tarea mas del reactor — no una app WASM— que sondea el
339 // disco de forma ASINCRONA: la demostracion de que la IRQ del disco
340 // conduce la E/S sin detener a las aplicaciones visuales.
341 ejecutor.spawn(tarea_sonda_disco());
342 x86_64::instructions::interrupts::enable();
343 ejecutor.run();
344}