tawasuyu

tawasuyu/brahman

Rust 463TOML 180Markdown 49JSON 176353 Definitions

⎇ 8fc1d99ddff618eaa0c2c29f4a51eaaed73443dd renaser/kernel/src/main.rs Rust · 488 lines · 20.9 KB History Who touched what raw

feat(renaser): Fase 13 — ratón, puntero y arrastre de flotantes 8fc1d99dd · sergio ·

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::boxed::Box;
40use alloc::format;
41
42use bootloader_api::config::{BootloaderConfig, Mapping};
43use bootloader_api::info::{FrameBufferInfo, MemoryRegionKind, MemoryRegions, PixelFormat};
44use bootloader_api::{entry_point, BootInfo};
45use spin::{Mutex, Once};
46
47// --- Subsistemas del kernel ---
48mod almacen;
49mod async_system;
50mod baliza;
51mod compositor;
52mod consola;
53mod drivers;
54mod gdt;
55mod grafico;
56mod interrupts;
57mod manifiesto;
58mod memory;
59mod pic;
60mod sync;
61mod texto;
62mod wasm;
63
64// Reexportacion para que los submodulos conserven rutas `crate::` estables.
65pub(crate) use sync::CeldaSync;
66
67use alloc::vec::Vec;
68use core::sync::atomic::{AtomicUsize, Ordering};
69
70use async_system::executor::Executor;
71use baliza::BALIZA_PANICO;
72use consola::{Consola, CONSOLA};
73use grafico::{
74    codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, ALTO_MAX, ANCHO_MAX,
75};
76
77/// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU.
78static CONFIG_ARRANQUE: BootloaderConfig = {
79    let mut config = BootloaderConfig::new_default();
80    // Pedimos la memoria fisica mapeada: cimiento para futuras fases.
81    config.mappings.physical_memory = Some(Mapping::Dynamic);
82    config
83};
84
85// `entry_point!` genera el simbolo `_start`, valida la firma de `kernel_main`
86// y nos transfiere el control con seguridad de tipos.
87entry_point!(kernel_main, config = &CONFIG_ARRANQUE);
88
89/// Detiene la CPU de forma definitiva: `hlt` la duerme hasta una interrupcion.
90pub(crate) fn detener() -> ! {
91    loop {
92        x86_64::instructions::hlt();
93    }
94}
95
96/// FASE 10 :: el molde de una aplicacion para los lanzamientos EN VIVO. Guarda
97/// su bytecode —cacheado en RAM al arrancar, para no volver al disco despues—
98/// y la geometria y la cuota de memoria con que instanciarla.
99struct Plantilla {
100    bytecode: Vec<u8>,
101    nat_ancho: usize,
102    nat_alto: usize,
103    techo: usize,
104}
105
106/// Las plantillas de las apps de genesis. Se fijan una vez, en el arranque;
107/// cada `Alt+N` instancia la siguiente en rotacion.
108static PLANTILLAS: Once<Vec<Plantilla>> = Once::new();
109
110/// El cursor rotatorio sobre `PLANTILLAS`: que app nace en el proximo `Alt+N`.
111static CURSOR_PLANTILLA: AtomicUsize = AtomicUsize::new(0);
112
113/// Tarea cooperativa de una aplicacion WASM. En cada pulso del reloj le concede
114/// un `tick` —un fotograma de trabajo— y cede la CPU hasta el siguiente; entre
115/// medias corren sus vecinas. Si la app falla o agota su combustible, se la
116/// DESALOJA: el compositor tatua su ventana con la baliza y la tarea concluye.
117/// El ejecutor la retira del censo, su memoria se libera, el kernel sigue vivo.
118async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) {
119    loop {
120        async_system::reloj::EsperaFrame::nueva().await;
121        // ¿El compositor pidio cerrar esta ventana (`Alt+Q`)? La tarea concluye
122        // por su propia voluntad: al retornar, `AplicacionWasm` se libera —su
123        // memoria lineal, su combustible, su canal de teclado— y el ejecutor la
124        // retira del censo. Una baja LIMPIA, sin baliza (Fase 10).
125        if compositor::ventana_cerrada(app.indice()) {
126            return;
127        }
128        if let Err(falla) = app.tick() {
129            // El color de la baliza delata la causa: purpura si agoto su tiempo
130            // o aborto, amarillo si reviento su techo de memoria. El compositor
131            // la pinta en el marco actual de la ventana y la marca como muerta.
132            compositor::desalojar(app.indice(), falla.color_baliza());
133            return;
134        }
135    }
136}
137
138/// FASE 8 :: la tarea del compositor. En cada fotograma drena la cola de mandos
139/// que el teclado dejo —ciclar el modo de teselado, mover el foco— y los
140/// aplica. Corre en el reactor cooperativo: el unico contexto donde es seguro
141/// re-teselar el escritorio y recomponer el lienzo desde las caches.
142async fn tarea_compositor() {
143    loop {
144        async_system::reloj::EsperaFrame::nueva().await;
145        compositor::atender_mandos();
146        // FASE 13 :: atender los eventos del raton (clic-para-enfocar y
147        // arrastre de flotantes), y refrescar el puntero si se movio en una
148        // vuelta tranquila en que ninguna app pinto.
149        compositor::atender_raton();
150        compositor::refrescar_puntero();
151        // FASE 10 :: atender las altas en vivo. Por cada `Alt+N` pendiente,
152        // dar a luz una aplicacion nueva — el compositor solo conto la
153        // peticion; instanciar el WASM es trabajo del orquestador.
154        for _ in 0..compositor::partos_pendientes() {
155            lanzar_app();
156        }
157    }
158}
159
160/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el
161/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las
162/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el
163/// bloque esta listo. Deja en la consola el resultado y cuantas interrupciones
164/// de disco se atendieron — el testigo de que la E/S por sondeo quedo atras.
165async fn tarea_sonda_disco() {
166    let resultado = drivers::disco::leer_bloques(0, 1).await;
167    let Some(consola) = CONSOLA.get() else {
168        return;
169    };
170    let mut consola = consola.lock();
171    match resultado {
172        Ok(_) => consola.escribir(&format!(
173            "disco :: sonda asincrona OK -- {} IRQ de disco atendidas\n",
174            drivers::disco::pulsos_disco(),
175        )),
176        Err(motivo) => {
177            consola.escribir(&format!("disco :: sonda asincrona fallida -- {motivo}\n"))
178        }
179    }
180    consola.presentar();
181}
182
183/// Da vida a una aplicacion del userspace a partir de su `EntradaApp` del
184/// manifiesto: recupera su bytecode del grafo, lo carga en la ventana `indice`
185/// del escritorio del compositor y despacha la app como tarea cooperativa del
186/// reactor. Si el bytecode falta, esta corrupto, o la carga fracasa, el
187/// compositor desaloja esa ventana — el kernel sigue con las demas.
188fn encender_app(
189    ejecutor: &mut Executor,
190    indice: usize,
191    entrada: &manifiesto::EntradaApp,
192) -> Option<Plantilla> {
193    // El tamaño NATURAL del lienzo de la app —lo que sabe pintar, fijo— lo
194    // dicta su `EntradaApp`; el compositor decide en que marco lo coloca.
195    let natural = manifiesto::region(entrada);
196    // Recuperar el bytecode del grafo. `recuperar` recomputa el hash del
197    // objeto y verifica su integridad: un bytecode corrupto se delata aqui
198    // —y la app se niega, no se instancia un modulo en el que no se confia.
199    let bytecode = match almacen::recuperar(&entrada.bytecode) {
200        Ok(Some(objeto)) => objeto.datos,
201        _ => {
202            compositor::desalojar(indice, Color::DESALOJO);
203            return None;
204        }
205    };
206    // `indice` es la identidad de la app: su ventana en el escritorio del
207    // compositor y su ranura de estado persistido (Fase 7c).
208    match wasm::AplicacionWasm::cargar(
209        &bytecode,
210        natural.ancho,
211        natural.alto,
212        entrada.techo_memoria as usize,
213        indice,
214    ) {
215        Ok(app) => ejecutor.spawn(tarea_aplicacion(app)),
216        Err(_) => compositor::desalojar(indice, Color::DESALOJO),
217    }
218    // FASE 10 :: el bytecode, ya recuperado y verificado, queda como PLANTILLA:
219    // un molde en RAM con que `Alt+N` instanciara copias en vivo, sin volver al
220    // disco —que la E/S por sondeo en mitad del reactor seria un mal vecino—.
221    Some(Plantilla {
222        bytecode,
223        nat_ancho: natural.ancho,
224        nat_alto: natural.alto,
225        techo: entrada.techo_memoria as usize,
226    })
227}
228
229/// FASE 10 :: da a luz una aplicacion EN VIVO. Elige la siguiente plantilla en
230/// rotacion, abre su ventana en el compositor —que le asigna su indice—,
231/// instancia su WASM con ese indice y engendra su tarea en el reactor ya en
232/// marcha. Si la carga falla, la ventana recien nacida se desaloja; el kernel
233/// sigue. La invoca la tarea del compositor al atender un `Alt+N`.
234fn lanzar_app() {
235    let Some(plantillas) = PLANTILLAS.get() else {
236        return;
237    };
238    if plantillas.is_empty() {
239        return;
240    }
241    // El cursor rota sobre las plantillas: cada `Alt+N` engendra la siguiente.
242    let cursor = CURSOR_PLANTILLA.fetch_add(1, Ordering::Relaxed);
243    let plantilla = &plantillas[cursor % plantillas.len()];
244
245    // La ventana nace primero: el compositor le entrega su indice —su
246    // identidad—, que el WASM necesita para hallar su ventana y su canal.
247    let indice = compositor::nacer_ventana(plantilla.nat_ancho, plantilla.nat_alto);
248    match wasm::AplicacionWasm::cargar(
249        &plantilla.bytecode,
250        plantilla.nat_ancho,
251        plantilla.nat_alto,
252        plantilla.techo,
253        indice,
254    ) {
255        // La tarea se ENGENDRA, no se hace `spawn`: el reactor ya corre y el
256        // ejecutor la adoptara en su proxima vuelta (Fase 10).
257        Ok(app) => async_system::executor::engendrar(Box::pin(tarea_aplicacion(app))),
258        Err(_) => compositor::desalojar(indice, Color::DESALOJO),
259    }
260}
261
262/// Escribe una linea en la consola global y la presenta. Atajo para los
263/// informes de arranque; no hace nada si la consola aun no existe.
264fn reportar(linea: &str) {
265    if let Some(consola) = CONSOLA.get() {
266        let mut consola = consola.lock();
267        consola.escribir(linea);
268        consola.escribir("\n");
269        consola.presentar();
270    }
271}
272
273/// FASE 7 :: puebla el userspace DESDE EL GRAFO. Carga el Manifiesto de
274/// Genesis que `boot` sembro en la imagen de disco, lo instala como el
275/// manifiesto VIVO del kernel y, por cada `EntradaApp`, enciende su
276/// aplicacion. Toda falla se reporta a la consola y NO detiene el arranque: el
277/// kernel se levanta con las apps que pueda — o con ninguna, si el grafo no
278/// tiene userspace.
279fn cargar_userspace(ejecutor: &mut Executor, ancho_pantalla: usize, alto_pantalla: usize) {
280    let manifiesto = match manifiesto::cargar() {
281        Ok(Some(m)) => Some(m),
282        // Disco sin manifiesto anclado: `boot` no lo sembro. El kernel se
283        // levanta sin userspace —pero se levanta—; en la practica, ninguna
284        // imagen forjada por `boot` llega aqui sin su Manifiesto de Genesis.
285        Ok(None) => {
286            reportar("manifiesto :: el disco no trae uno -- el kernel se levanta solo");
287            None
288        }
289        Err(motivo) => {
290            reportar(&format!("manifiesto :: carga fallida -- {motivo}"));
291            None
292        }
293    };
294
295    match &manifiesto {
296        Some(m) => reportar(&format!(
297            "manifiesto :: {} apps nacidas del grafo",
298            m.apps.len(),
299        )),
300        None => reportar("manifiesto :: sin userspace -- el kernel se levanta solo"),
301    }
302
303    if let Some(m) = manifiesto {
304        // Instalar el manifiesto VIVO ANTES de instanciar las apps: el `init`
305        // de cada app puede consultar su estado persistido (Fase 7c), y esa
306        // consulta lee del manifiesto vivo.
307        manifiesto::instalar(m.clone());
308
309        // FASE 8 :: fundar el escritorio del compositor — una ventana por app,
310        // con su cache de respaldo y su marco teselado por `mirada-layout`— y
311        // pintar el escenario antes de encender las apps: el teselado se ve
312        // aunque alguna app no llegue a pintar su primer fotograma.
313        let naturales: Vec<(usize, usize)> = m
314            .apps
315            .iter()
316            .map(|e| (e.region_ancho as usize, e.region_alto as usize))
317            .collect();
318        compositor::fundar(ancho_pantalla, alto_pantalla, &naturales);
319        compositor::componer_escenario();
320
321        let mut plantillas: Vec<Plantilla> = Vec::new();
322        for (indice, entrada) in m.apps.iter().enumerate() {
323            if let Some(plantilla) = encender_app(ejecutor, indice, entrada) {
324                plantillas.push(plantilla);
325            }
326        }
327        // FASE 10 :: fijar las plantillas de las apps. A partir de aqui, cada
328        // `Alt+N` instancia una copia viva, en rotacion.
329        PLANTILLAS.call_once(|| plantillas);
330
331        // La tarea del compositor: atiende los mandos del teclado —ciclar el
332        // teselado, mover el foco, cerrar y lanzar apps— en cada fotograma.
333        ejecutor.spawn(tarea_compositor());
334    }
335}
336
337/// Localiza la mayor region de RAM libre que el cargador reporto — la cantera
338/// de la que el DMA del disco tomara sus marcos fisicos.
339fn mayor_region_usable(regiones: &MemoryRegions) -> Option<(u64, u64)> {
340    regiones
341        .iter()
342        .filter(|region| matches!(region.kind, MemoryRegionKind::Usable))
343        .map(|region| (region.start, region.end))
344        .max_by_key(|&(inicio, fin)| fin - inicio)
345}
346
347/// FASE 6.1c — funda el grafo de objetos. Monta el disco virtio-blk, lee o
348/// forja el superbloque, reconstruye el indice recorriendo el log y deja
349/// constancia visual: cuantos sectores tiene el disco, cuantos objetos viven
350/// ya en el grafo y si el arranque encontro —o no— una raiz de la que tirar.
351fn informar_almacen() {
352    // Fundar el almacen ANTES de tomar el cerrojo de la consola: el montaje
353    // del disco hace E/S por sondeo y nada de ello reclama la consola.
354    let resultado = almacen::init();
355    let Some(consola) = CONSOLA.get() else {
356        return;
357    };
358    let mut consola = consola.lock();
359    match resultado {
360        Ok(resumen) => {
361            let estado = if resumen.formateado {
362                "disco formateado"
363            } else {
364                "grafo montado"
365            };
366            consola.escribir(&format!(
367                "almacen :: {} :: {} sectores :: {} objetos :: raiz {}\n",
368                estado,
369                resumen.capacidad,
370                resumen.objetos,
371                if resumen.raiz { "presente" } else { "ausente" },
372            ));
373        }
374        Err(motivo) => {
375            consola.escribir(&format!("almacen :: fallo :: {motivo}\n"));
376        }
377    }
378    // FASE 6.2 — dejar constancia de la linea de IRQ por la que el disco
379    // anunciara, ya sin sondeo, el fin de cada transferencia.
380    match drivers::disco::irq() {
381        Some(irq) => {
382            consola.escribir(&format!("disco :: virtio-blk en IRQ {irq} -- E/S asincrona\n"))
383        }
384        None => consola.escribir("disco :: IRQ no enrutada -- E/S por sondeo\n"),
385    }
386    consola.presentar();
387}
388
389// =============================================================================
390//  PUNTO DE ENTRADA DEL KERNEL
391// =============================================================================
392
393fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
394    // --- 1. Recuperar el framebuffer GOP que el firmware nos confio. ---
395    let framebuffer = match boot_info.framebuffer.as_mut() {
396        Some(fb) => fb,
397        None => detener(),
398    };
399    let info: FrameBufferInfo = framebuffer.info();
400    let formato: PixelFormat = info.pixel_format;
401    let pantalla = Pantalla::adoptar(framebuffer, info);
402
403    // Datos para la sonda de disco (Fase 6.1b): el offset al que el cargador
404    // mapeo la memoria fisica y la mayor region de RAM libre para el DMA.
405    let offset_fisico = boot_info.physical_memory_offset.into_option();
406    let region_dma = mayor_region_usable(&boot_info.memory_regions);
407
408    // --- 2. Encender la baliza: la red de seguridad visual va primero. ---
409    BALIZA_PANICO.encender(
410        &pantalla,
411        codificar(formato, Color::ALERTA),
412        codificar(formato, Color::OOM),
413    );
414
415    // --- 3. Cimientos de fallos e interrupciones (Fases 2.0 y 2.1). ---
416    gdt::init();
417    interrupts::init();
418    pic::init();
419
420    // --- 4. FASE 3 :: fundar el heap. A partir de aqui, `alloc` esta vivo. ---
421    memory::init();
422
423    // --- 5. Con el heap activo, fundar lo que depende de el: el canal de
424    //        scancodes, el reloj de fotogramas y la tipografia vectorial. ---
425    async_system::teclado::init();
426    async_system::reloj::init();
427    texto::init();
428
429    // --- 6. Construir el lienzo y la consola; pintar el rotulo inicial,
430    //        ya rasterizado por fontdue, y publicar la consola globalmente. ---
431    let memoria = match reclamar_memoria_lienzo() {
432        Some(m) => m,
433        None => detener(),
434    };
435    let ancho_lienzo = info.width.min(ANCHO_MAX);
436    let alto_lienzo = info.height.min(ALTO_MAX);
437    let mut lienzo = Lienzo::nuevo(memoria, ancho_lienzo, alto_lienzo, formato);
438    lienzo.limpiar(Color::LIENZO_EN_REPOSO);
439
440    let mut consola = Consola::nueva(lienzo, pantalla);
441    consola.escribir("renaser :: fase 6.2 -- E/S de disco asincrona por interrupcion\n");
442    consola.presentar();
443    CONSOLA.call_once(|| Mutex::new(consola));
444
445    // --- 6.5. FASE 6.1c :: fundar el subsistema de disco y, sobre el, el grafo
446    //          de objetos: enumerar el bus PCI, montar el transporte virtio-blk,
447    //          y leer o forjar el superbloque del almacen direccionado por
448    //          contenido. El kernel adquiere, por fin, una memoria que perdura. ---
449    match (offset_fisico, region_dma) {
450        (Some(offset), Some((inicio, fin))) => {
451            drivers::disco::init(offset, inicio, fin);
452            informar_almacen();
453        }
454        _ => {
455            if let Some(consola) = CONSOLA.get() {
456                let mut consola = consola.lock();
457                consola.escribir("virtio-blk :: omitido -- memoria fisica sin mapear\n");
458                consola.presentar();
459            }
460        }
461    }
462
463    // --- 6.6. FASE 13 :: despertar el raton PS/2. El 8042 enciende su
464    //          dispositivo auxiliar, el raton empieza a reportar, y el PIC
465    //          desenmascara su IRQ12. Desde aqui hay un puntero en pantalla,
466    //          y los clics pueden alcanzar al compositor.
467    drivers::raton::init(ancho_lienzo, alto_lienzo);
468
469    // --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL
470    //        GRAFO. El kernel ya no empotra los modulos WASM: lee el
471    //        Manifiesto de Genesis que `boot` sembro en la imagen de disco e
472    //        instancia cada `EntradaApp` recuperando su bytecode del grafo de
473    //        objetos. Las cinco apps de genesis (dos instancias de hello, la
474    //        discola, la glotona y la cronista) nacen del disco, no del
475    //        binario del kernel.
476    //
477    //        Las interrupciones se habilitan AHORA: el temporizador marcara el
478    //        compas de los fotogramas y la IRQ del teclado difundira cada
479    //        scancode a los canales que las apps consultan. ---
480    let mut ejecutor = Executor::nuevo();
481    cargar_userspace(&mut ejecutor, ancho_lienzo, alto_lienzo);
482    // FASE 6.2 :: una tarea mas del reactor — no una app WASM— que sondea el
483    // disco de forma ASINCRONA: la demostracion de que la IRQ del disco
484    // conduce la E/S sin detener a las aplicaciones visuales.
485    ejecutor.spawn(tarea_sonda_disco());
486    x86_64::instructions::interrupts::enable();
487    ejecutor.run();
488}