diff --git a/Cargo.toml b/Cargo.toml index f4ed048..5209af0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -302,6 +302,13 @@ members = [ "crates/apps/charka", ] +# renaser — el SO bare-metal SASOS. Vive en el mismo repo pero es su +# PROPIO workspace de Cargo: usa toolchain nightly, target +# `x86_64-unknown-none` y `panic = "abort"`, incompatibles con los +# perfiles globales de este workspace. Cargo lo trata como ajeno; los +# crates compartidos se referencian por `path` cruzando la frontera. +exclude = ["renaser"] + [workspace.package] version = "0.1.0" edition = "2021" diff --git a/renaser/.cargo/config.toml b/renaser/.cargo/config.toml new file mode 100644 index 0000000..e7ab80c --- /dev/null +++ b/renaser/.cargo/config.toml @@ -0,0 +1,19 @@ +# ============================================================================= +# renaser :: configuracion de cargo +# ----------------------------------------------------------------------------- +# El kernel se compila contra el target NATIVO precompilado `x86_64-unknown-none` +# (bare-metal, soft-float, sin SSE). No hay JSON propio ni `build-std`: cero +# burocracia del compilador, aislamiento de arquitectura sin friccion. +# ============================================================================= + +[unstable] +# Dependencias de artefacto (RFC 3028): permiten que el miembro `boot` ordene a +# cargo compilar el kernel para `x86_64-unknown-none` de forma automatica y +# reciba la ruta de su ELF. Sigue siendo una funcion inestable (nightly). +bindeps = true + +[alias] +# Compila unicamente el kernel, en aislamiento de arquitectura. +kernel = "build -p kernel --target x86_64-unknown-none" +# Construye la imagen UEFI y abre QEMU (equivale a `cargo run -p boot`). +qemu = "run -p boot" diff --git a/renaser/.gitignore b/renaser/.gitignore new file mode 100644 index 0000000..39e8e2a --- /dev/null +++ b/renaser/.gitignore @@ -0,0 +1,24 @@ +# ============================================================================= +# renaser :: archivos que NO se versionan +# ============================================================================= + +# Artefactos de compilacion de Rust — kernel, boot y apps WASM. +/target +/kernel/target +/apps/*/target + +# Bloqueo de dependencias del kernel: se genera al compilarlo en aislamiento. +# La verdad de versiones la fija el Cargo.lock raiz — el kernel es dependencia +# de artefacto de `boot` y se resuelve con el. +/kernel/Cargo.lock + +# Ajustes locales del entorno de desarrollo (personales, no compartidos). +/.claude/settings.local.json + +# Capturas temporales de QEMU. +*.ppm +*.png.tmp + +# Borrador de trabajo local — puede contener notas y credenciales; nunca se +# versiona. (Fue subido por error y purgado del historial; ver el README.) +/renaser.txt diff --git a/renaser/ARCHITECTURE.md b/renaser/ARCHITECTURE.md new file mode 100644 index 0000000..acf60fa --- /dev/null +++ b/renaser/ARCHITECTURE.md @@ -0,0 +1,173 @@ +# renaser — Arquitectura + +Este documento describe la arquitectura del kernel `renaser` subsistema a +subsistema. Para el estado por fases, ver `ROADMAP.md`. + +## 1. Filosofía + +renaser es un **SASOS** — *Single Address Space Operating System*. No hay un +espacio de direcciones por proceso ni cambios de contexto de hardware: todo el +sistema —kernel y aplicaciones— comparte una única RAM plana. + +El aislamiento NO lo da la MMU ni los anillos de privilegio de la CPU. Lo da el +**software**: las aplicaciones se distribuyen como bytecode WebAssembly y se +ejecutan dentro de un intérprete que acota matemáticamente cada acceso a +memoria. Una aplicación solo puede hacer aquello para lo que el kernel le haya +inyectado una *capacidad* (una función de host). Lo que no está importado no +tiene camino físico que recorrer. + +La interfaz es **visual desde el primer microsegundo**. No hay TTY; el texto se +rasteriza como gráfico vectorial. "El texto es un caso particular del dibujo." + +## 2. La cadena de arranque + +``` +Firmware UEFI → crate `bootloader` 0.11 → kernel::_start → kernel_main +``` + +El miembro `boot/` (que corre en el **anfitrión**) toma el ELF del kernel, +lo fusiona con el cargador `bootloader` 0.11 en una imagen de disco UEFI GPT y +la lanza en QEMU con el firmware OVMF. El cargador deja la CPU en modo largo de +64 bits, mapea el kernel y le entrega una `BootInfo` con el framebuffer GOP. + +## 3. Estructura del espacio de trabajo + +El workspace tiene **un solo miembro**, `boot`. El `kernel` y las `apps` están +**excluidos** a propósito: + +- El kernel es `#![no_std] #![no_main]` y define su propio `_start`. Si fuese + miembro del workspace, `cargo build` intentaría compilarlo para el anfitrión + y fallaría por un símbolo `_start` duplicado con el `crt0` del sistema. +- Por eso el kernel se construye **solo** como *dependencia de artefacto* + (RFC 3028) de `boot`, que le fija el target `x86_64-unknown-none`. +- Las apps WASM tienen su propio target (`wasm32-unknown-unknown`) y se + compilan aparte. + +El kernel usa el target **nativo precompilado** `x86_64-unknown-none`: sin +target JSON propio, sin `build-std`. Soft-float y sin SSE, para que las +interrupciones no corrompan registros de punto flotante. + +## 4. Subsistemas del kernel + +### `grafico` — el sustrato visual +`Color` (RGB de 24 bits, independiente del hardware), `Pantalla` (el +framebuffer GOP físico, envuelto en seguridad) y `Lienzo` (el búfer intermedio +en RAM). Toda composición ocurre en el `Lienzo`; `Pantalla::presentar` lo +vuelca de un solo gesto con escrituras volátiles — **doble búfer**, sin +desgarros. `codificar` traduce un `Color` al formato nativo del framebuffer. + +### `consola` — la superficie de texto e imagen +Une `Lienzo` + `Pantalla` + una pluma de escritura. Rasteriza glifos con +`fontdue` y los funde sobre el lienzo mezclando por cobertura (anti-aliasing). +También vuelca fotogramas crudos del userspace WASM. Es global, tras un `Mutex`. + +### `baliza` — la red de seguridad visual +Publica de forma atómica y sin cerrojos los datos mínimos del framebuffer. Los +manejadores `#[panic_handler]` y `#[alloc_error_handler]` los usan para pintar +una franja de advertencia —**roja** si el sistema colapsa, **naranja** si el +heap se agota— escribiendo directo sobre el silicio, sin confiar en el heap ni +en estructura dinámica alguna. + +### `gdt` — cimientos del manejo de fallos +GDT propia con segmentos de código y datos del kernel, y un TSS cuyo único +cometido es alojar un **stack de emergencia** (IST) para el doble fallo: ni un +desbordamiento de la pila del kernel impide su diagnóstico. + +### `interrupts` — la tabla de reflejos +IDT con manejadores de excepción de CPU (breakpoint recuperable; el resto, +fatales → `panic!`) e interrupciones de hardware. El doble fallo se atiende +sobre el stack de emergencia del TSS. + +### `pic` — el latido del hardware +Remapea el par 8259 (PIC) fuera del rango de las excepciones de CPU y programa +el temporizador de intervalos (PIT). El teclado (IRQ1) deposita scancodes en +una cola lock-free. `desenmascarar` abre una línea de IRQ concreta —la del +disco, descubierta en tiempo de ejecución (Fase 6.2)—. + +### `drivers` — el hardware que el kernel conquista (Fases 6.1, 6.2) +A diferencia del framebuffer o el temporizador —que el firmware sirve en +bandeja—, el disco hay que DESCUBRIRLO y reclamarlo. +- `pci` — `CamPuertos`: acceso al espacio de configuración del bus PCI por el + mecanismo #1 (puertos `0xCF8`/`0xCFC`); `linea_irq` lee la IRQ del dispositivo. +- `disco` — el disco virtio-blk. Un asignador de marcos por **mapa de bits** + —con liberación real— reparte páginas físicas para el DMA; `KernelHal` + implementa el `trait Hal` de `virtio-drivers` (traducción de direcciones, + memoria rebote para el DMA); el `VirtIOBlk` se monta una vez y persiste tras + un `Mutex`. La E/S de bloques es **asíncrona** (Fase 6.2): `EsperaDisco` es un + `Future` que envía la petición por la API no bloqueante de `virtio-drivers`, + cede la CPU y se reanuda con la IRQ del disco (`atender_irq`). `bloquear_en` + conduce ese `Future` desde los contextos síncronos durmiendo la CPU con + `hlt`; sobre él, `leer_sectores`/`escribir_sectores` sirven al grafo de + objetos. La IRQ del disco se enruta por el PIC 8259, no por el IOAPIC. + +### `almacen` — el grafo de objetos direccionado por contenido (Fase 6.1c) +renaser no tiene un sistema de archivos plano POSIX: tiene un DAG de objetos. +Un `Objeto` es una carga útil de bytes y una lista de aristas (hashes de +hijos). La identidad de un objeto es el hash BLAKE3 de su forma serializada +(`postcard`) — de ahí integridad (un objeto se verifica al leerlo) y +deduplicación (contenido idéntico, un solo registro). El disco es un **log**: +el sector 0 es el superbloque (magia, versión, cursor, raíz); tras él se anexan +los registros de objetos. Un índice hash→sector se reconstruye al arrancar +recorriendo el log. Las capacidades `sys_object_*` exponen el grafo al +userspace. + +### `memory` — el heap dinámico +`linked_list_allocator` como `#[global_allocator]` sobre una región estática de +64 MiB en `.bss`. Desbloquea `alloc::*` — `Box`, `Vec`, `BTreeMap`, `Arc`. + +### `async_system` — el reactor cooperativo +- `task` — `Task`: un `Future` anclado (`Pin>`) con `TaskId`. +- `executor` — el `Executor`: censo de tareas, cola de listas, y un `hlt` + controlado cuando no hay trabajo. +- `waker` — un `Waker` que reinyecta el `TaskId` en la cola al despertar. +- `teclado` — los canales de scancodes. Cada app abre el suyo; la IRQ1 **difunde** + cada scancode a todos, de modo que varias apps reciben la entrada en paralelo. +- `reloj` — convierte la IRQ0 (PIT, 100 Hz) en el `Future` `EsperaFrame`, que se + resuelve en el siguiente pulso. Es el **compás de los fotogramas**: una tarea + WASM hace su trabajo de un fotograma y `.await`-ea el siguiente. + +Las interrupciones de hardware no conmutan el contexto de la CPU: **despiertan +tareas**. El kernel avanza cooperativamente. + +### `texto` — tipografía vectorial +Empotra un `.ttf` en el binario (`include_bytes!`) y lo rasteriza con `fontdue` +glifo a glifo, bajo demanda. + +### `wasm` — el escudo de aislamiento +- `mod` — el runtime y la `AplicacionWasm`: una instancia **persistente** entre + fotogramas (`Store` + `TypedFunc<(), ()>` + región). El ABI del userspace es + `init()` (una vez) y `tick()` (un fotograma, y retorna). Dos guardarraíles + acotan a cada app: el **temporal** —un presupuesto de **combustible** (`fuel`) + por `tick`; agotarlo la desaloja (baliza púrpura)— y el **espacial** —un techo + de memoria lineal vía `Store::limiter`; rebasarlo la desaloja (baliza + amarilla)—. En ambos casos `wasmi` lanza una trampa, el kernel recupera el + mando y el sistema no sufre. El `Drop` de `AplicacionWasm` reconcilia el ciclo + de vida: da de baja su canal de teclado. +- `env` — la **matriz de capacidades**: las funciones de host que el módulo + WASM puede invocar. Hoy son siete: + - `sys_render_frame(ptr, len)` — compone un fotograma dentro de la **región** + de pantalla asignada a la app. El kernel valida **matemáticamente** que + `[ptr, ptr+len)` cae dentro de la memoria lineal del módulo, y que el + tamaño es el de la región, antes de leer un byte; si no, aborta la app. + - `sys_get_scancode()` — entrega, sin bloquear, el siguiente scancode del + canal de teclado **propio** de la app. + - `sys_object_put` / `sys_object_datos` / `sys_object_hijo` / + `sys_object_raiz` / `sys_object_fijar_raiz` — el acceso al grafo de objetos + persistente (ver `almacen`): grabar un objeto, leer su carga útil, recorrer + sus aristas, y leer o fijar la raíz del DAG. Cada puntero que la app + entrega se valida contra su memoria lineal, igual que en `sys_render_frame`. + +Todo puntero inválido aborta la app —es su culpa: el `Error` se traduce en una +trampa de WASM, la app se desaloja y el resto del sistema sigue—. Un fallo del +almacenamiento, en cambio, no es culpa de la app: se le devuelve un código de +error que ella decide cómo afrontar. + +## 5. Restricciones de ingeniería + +- **Escrituras volátiles** obligatorias para framebuffer y MMIO. +- **`unsafe`** confinado en células mínimas, cada una con un contrato + `SEGURIDAD:` y envuelta en una abstracción segura. `unsafe_op_in_unsafe_fn` + está en `deny`. +- **Alineación**: 16 bytes para estructuras genéricas; 4096 (página) para + buffers de asignador y el lienzo de respaldo. +- **`no_std` estricto**: `core` + `alloc`; nada de `std`. diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md new file mode 100644 index 0000000..db1b321 --- /dev/null +++ b/renaser/CHANGELOG.md @@ -0,0 +1,457 @@ +# Registro de cambios — renaser + +Registro técnico detallado, fase a fase, en orden cronológico. Formato +inspirado en *Keep a Changelog*. Para la crónica en lenguaje llano, ver +`DIARIO.md`; para el estado y los planes, `ROADMAP.md`. + +--- + +## Fase 1 — El primer microsegundo — 2026-05-21 + +### Infraestructura +- Espacio de trabajo Cargo, `kernel/Cargo.toml`, `.cargo/config.toml`, + `rust-toolchain.toml`. +- Target inicial: especificación JSON propia `x86_64-renaser.json` — PIC, + soft-float, sin SSE, enlazador `lld`. + +### Añadido +- `kernel/src/main.rs`: punto de entrada con el macro `entry_point!` de + `bootloader_api` 0.11. +- Adopción y verificación con seguridad de tipos del framebuffer GOP (ancho, + alto, formato de píxel, *stride*). +- Lienzo intermedio estático de 8 MiB en `.bss`, alineado a página: la técnica + de doble búfer para evitar parpadeos. +- `#[panic_handler]` que dibuja una franja roja directamente sobre el + framebuffer mediante escrituras volátiles. +- Dependencias: `bootloader_api` 0.11, `x86_64` 0.15, `embedded-graphics` 0.8. + +### Notas +- Corrección clave: `bootloader` (constructor de imagen, lado anfitrión) no es + `bootloader_api` (la API `no_std` que consume el kernel). + +--- + +## Fase 1.5 — Empaquetado y arranque — 2026-05-21 + +### Cambiado +- Migración del target del kernel: de la JSON propia `x86_64-renaser.json` al + target nativo precompilado `x86_64-unknown-none`. Elimina `build-std` y la + burocracia de compilador asociada. + +### Añadido +- Miembro `boot/`: orquestador de anfitrión. Dependencia de artefacto + (RFC 3028) sobre el kernel; construcción de la imagen de disco UEFI con + `bootloader::UefiBoot`; lanzador de QEMU. + +### Toolchain +- Instalación de la toolchain nightly (la máquina solo tenía la estable). + +### Corregido +- Los nightly recientes exigen `-Zjson-target-spec` para usar specs JSON + (resuelto al migrar al target nativo). +- Sintaxis del acelerador de QEMU: `-machine q35,accel=kvm:tcg`. + +### Verificado +- Arranque en QEMU: superficie índigo limpia a 1280×800. +- Baliza de pánico (franja roja) confirmada inyectando un pánico de prueba. + +--- + +## Fase 2.0 — Cimientos del manejo de fallos — 2026-05-21 + +### Añadido +- `kernel/src/gdt.rs`: GDT propia, TSS y un stack de emergencia (IST) + reservado para el manejador de doble fallo. +- `kernel/src/interrupts.rs`: IDT con manejadores de excepción de CPU. El + breakpoint (#BP) es recuperable; #UD, #DE, #GP, #PF y #DF son fatales y + encienden la baliza. +- `#![feature(abi_x86_interrupt)]`. + +### Corregido +- La GDT debe recargar los registros SS/DS/ES con un segmento de datos del + kernel: el cargador deja `SS = 0x10`, valor que en la GDT nueva pasa a ser el + descriptor del TSS; el primer `iretq` de una rutina de excepción provocaba un + #GP. Diagnosticado con la traza `-d int` de QEMU. + +### Verificado +- Excepción `int3` atrapada y superada; línea-latido teal dibujada como prueba. + +--- + +## Fase 2.1 — Interrupciones de hardware — 2026-05-21 + +### Añadido +- `kernel/src/pic.rs`: remapeo del par 8259 (PIC) fuera del rango de las + excepciones (vectores 0x20+); programación del temporizador PIT a 100 Hz; + IRQ1 de teclado. +- Bucle de render en `kernel_main`, despertado por el temporizador, en + sustitución del `hlt` estático. + +### Verificado +- La línea-latido se anima (temporizador en marcha); las pulsaciones de teclado + inyectadas cambian el cuadro-eco (teclado en marcha). + +--- + +## Fase 3 — Memoria dinámica y reactor asíncrono — 2026-05-21 + +### Añadido +- `kernel/src/memory/`: `linked_list_allocator` como `#[global_allocator]` + sobre una región estática en `.bss`. +- `#[alloc_error_handler]`: franja naranja de agotamiento de memoria. + `#![feature(alloc_error_handler)]`. +- `kernel/src/async_system/`: reactor cooperativo — `Executor`, `Task`/`TaskId`, + `Waker` (vía `alloc::task::Wake`) y `ScancodeStream`. +- `kernel/src/texto.rs`: rasterización de tipografía vectorial con `fontdue`; + TTF Adwaita Mono empotrada con `include_bytes!`. +- Dependencias: `linked_list_allocator` 0.10, `spin` 0.9, `crossbeam-queue` + 0.3, `futures-util` 0.3, `fontdue` 0.9. + +### Cambiado +- Heap ampliado de 16 MiB a 64 MiB: `fontdue`, al analizar una tipografía real, + agotaba los 16 MiB iniciales. El `#[alloc_error_handler]` lo delató pintando + la franja naranja — el guardarraíl funcionó en silicio real. + +### Corregido +- El modo `no_std` de `fontdue` está condicionado a su feature `hashbrown`; sin + ella recae en `std`. Se fija `default-features = false, features = ["hashbrown"]`. + +### Verificado +- Rótulo de bienvenida rasterizado al arranque; el texto tecleado aparece en vivo. + +--- + +## Fase 4 — El escudo de aislamiento WASM — 2026-05-21 + +### Añadido +- `kernel/src/wasm/`: intérprete `wasmi` 1.0.9 en modo `no_std`. `env.rs` + define la matriz de capacidades — exactamente dos funciones de host: + `sys_render_frame(ptr, len)`, con validación infranqueable de los límites de + la memoria lineal, y `sys_get_scancode()`. +- `apps/hello_wasm/`: primera aplicación del userspace — módulo + `wasm32-unknown-unknown` (cdylib), un cuadrado móvil dirigido por teclado. Se + empotra en el kernel con `include_bytes!`. +- Dependencia: `wasmi` 1.0. + +### Cambiado +- El kernel se EXCLUYE del espacio de trabajo (`exclude = ["kernel", "apps"]`): + al añadir `wasmi`, `cargo build` intentaba compilar el kernel para el + anfitrión y fallaba por un símbolo `_start` duplicado. El kernel pasa a + construirse únicamente como dependencia de artefacto de `boot`. + +### Corregido +- `wasmi` `no_std` necesita `default-features = false, features = ["hash-collections"]`. +- `wasmi` 1.0.9 usa `Linker::instantiate_and_start` (no existe `instantiate`). + +### Verificado +- La aplicación WASM pinta su propia superficie y responde a las pulsaciones de + teclado inyectadas, sin más vía hacia el kernel que las dos capacidades. + +--- + +## Mantenimiento — Estructura, documentación e integración — 2026-05-21 + +### Cambiado +- Refactorización: `main.rs` dividido de 692 a ~155 líneas. Se extraen los + módulos `sync.rs`, `grafico.rs`, `consola.rs` y `baliza.rs`. Sin cambios de + comportamiento (verificado en QEMU). + +### Añadido +- Documentación: `CLAUDE.md`, `README.md`, `ARCHITECTURE.md`, `ROADMAP.md`, + `CHANGELOG.md` y `DIARIO.md`. +- Integración con git: repositorio inicializado, remoto `origin` en Gitea, + `.gitignore`. + +### Seguridad +- `renaser.txt` —un borrador de trabajo que contenía una credencial— se subió + por error en el commit inicial. Se purgó de todo el historial con + `git filter-branch` y se reescribió el remoto con `push --force`. La + credencial expuesta debe rotarse. + +--- + +## Fase 5 — Multitarea cooperativa, guardarrail de fuel y reloj — 2026-05-22 + +Unificación de la Fase 3 (reactor) y la Fase 4 (WASM): el userspace deja de ser +una sola app que monopoliza la CPU y pasa a ser un conjunto de aplicaciones +cooperativas, aisladas también en el TIEMPO. + +### Añadido +- `kernel/src/async_system/reloj.rs`: convierte la IRQ0 (PIT, 100 Hz) en una + primitiva asíncrona. `CONTADOR_PULSOS` (`AtomicU64`) y `EsperaFrame`, un + `Future` que se resuelve en el siguiente pulso — la unidad de cesión + cooperativa del userspace. Censo de wakers tras `Mutex`, drenado por la IRQ0. +- `kernel/src/wasm`: ABI de fotograma. `AplicacionWasm` —instancia PERSISTENTE + entre fotogramas (`Store` + `TypedFunc<(), ()>` + región)— sustituye al + `wasm::ejecutar` de fuego-y-olvido. El módulo del userspace exporta ahora + `init()` (una vez) y `tick()` (un fotograma, y retorna). +- Escudo de combustible: `Engine` con `Config::consume_fuel(true)` y + `CompilationMode::Eager` (el `fuel` mide solo ejecución). Presupuesto recargado + antes de cada `tick` — `FUEL_ARRANQUE` (20 M) y `FUEL_FOTOGRAMA` (2 M). +- `kernel/src/wasm/env.rs`: capacidad `sys_render_frame` con **regiones de + dibujo** — cada fotograma se compone desplazado por el `(offset_x, offset_y)` + de la app; un tamaño ajeno a la región se rechaza. Canal de teclado **por + aplicación**: la IRQ1 difunde cada scancode a TODOS los canales, de modo que + varias apps reciben la entrada en paralelo sin robársela. +- `kernel/src/grafico.rs`: `RegionPantalla` y `Color::DESALOJO` (púrpura). +- `apps/discola/`: aplicación WASM construida para portarse mal — su `tick` es + un bucle cerrado. Demuestra el guardarrail de fuel en vivo. + +### Cambiado +- `apps/hello_wasm`: migrada al ABI `init`/`tick`; su lienzo se dimensiona a la + región (480×560). El estado del cuadrado persiste en su memoria lineal. +- `interrupts.rs`: la IRQ0 avanza el `reloj`; la IRQ1 difunde a los canales. +- `consola.rs`: `volcar_marco` compone en una sub-región; `pintar_desalojo` + tatúa la baliza púrpura. +- Contención de fallos: el subsistema WASM ya no usa `.expect()`. Toda falla + —carga, instanciación, desbordamiento, agotamiento de fuel— se devuelve como + `Result`; la tarea desaloja la app (baliza púrpura) y el kernel sigue vivo. + +### Verificado +- QEMU: tres apps concurrentes. Dos instancias de `hello_wasm` (mismo bytecode, + regiones izquierda y derecha) renderizan y responden a W/A/S/D **en paralelo**, + con movimiento idéntico. La app díscola es desalojada en su primer fotograma + por agotamiento de combustible: su región queda púrpura y el sistema —kernel y + apps vecinas— no sufre un solo sobresalto. + +--- + +## Fase 6.0 — Cuotas de memoria y ciclo de vida del userspace — 2026-05-22 + +El aislamiento de las aplicaciones, que la Fase 5 hizo temporal (combustible), +se completa ahora en la dimensión espacial (memoria) y se cierra la fuga de +recursos del ciclo de vida. + +### Añadido +- Techo de memoria lineal por aplicación: `4 MiB`. `wasm/mod.rs` construye un + `StoreLimits` (`StoreLimitsBuilder::memory_size` + `trap_on_grow_failure`) y lo + liga al `Store` con `Store::limiter`. Un `memory.grow` que rebase la cuota se + convierte en trampa; el kernel la captura y desaloja la app. +- `FallaApp::SinMemoria` y `Color::DESALOJO_MEMORIA` (amarillo pálido). El + desalojo distingue la causa por color: púrpura (tiempo/aborto), amarillo + (memoria). La clasificación es robusta — `Error::as_trap_code()` da un código + público y unívoco: `TrapCode::GrowthOperationLimited`. +- `Drop` para `AplicacionWasm`: al morir una app desalojada, su canal de teclado + se da de baja del censo de difusión de la IRQ1 — cierra la fuga señalada en la + Fase 5. `async_system::teclado` se reorganiza en `crear_canal` / + `registrar_canal` / `cerrar_canal`; el canal se inscribe al FINAL de la carga, + de modo que una carga fallida no deja canales huérfanos. +- `apps/glotona/`: aplicación WASM construida para devorar memoria — su `tick` + invoca `memory.grow` sin freno. Demuestra el guardarrail espacial en vivo. + +### Verificado +- QEMU: cuatro apps concurrentes. Las dos `hello_wasm` renderizan y responden al + teclado en paralelo; la app díscola es desalojada por combustible (franja + púrpura) y la app glotona por cuota de memoria (franja amarilla), ambas en su + primer fotograma, sin que el kernel ni las apps honradas se inmuten. + +--- + +## Fase 6.1a — Sonda PCI y disco de pruebas — 2026-05-22 + +Primer paso de la estrategia incremental hacia el almacenamiento: derribar el +muro del descubrimiento de hardware antes de diseñar nada encima. + +### Añadido +- `kernel/src/drivers/`: nuevo subsistema de drivers. `pci.rs` enumera el bus + PCI por fuerza bruta mediante el mecanismo de configuración #1 —puertos de E/S + `0xCF8` (dirección) y `0xCFC` (datos)—; recorre buses y dispositivos leyendo + el Vendor/Device ID, y localiza el disco virtio-blk (vendor `0x1AF4`, device + `0x1001`/`0x1042`), devolviendo su ubicación y sus seis BARs. +- `kernel_main`: tras fundar la consola, sondea el bus PCI y deja constancia + visual del hallazgo. + +### Cambiado +- `boot/src/main.rs`: forja un disco de pruebas `target/disk.img` (fichero + disperso de 32 MiB; se respeta si ya existe) y lo adjunta a QEMU como + `virtio-blk-pci`. **Corrección de plataforma:** la máquina `q35` es x86_64 y + exige la transmisión PCI; `virtio-blk-device` (su gemelo MMIO) es de ARM. + +### Verificado +- QEMU: el kernel enumera el bus y reporta en pantalla + `virtio-blk en bus 0 dev 3 :: BAR0 E/S 0x6000` — un dispositivo virtio-blk + transicional (device `0x1001`), con su BAR0 en espacio de E/S. El muro del + descubrimiento PCI queda derribado; las cuatro apps del userspace siguen + operando sin alteración. + +--- + +## Fase 6.1b — HAL, DMA y lectura del sector 0 — 2026-05-22 + +Segundo paso del sustrato de almacenamiento: el diálogo real con el disco. El +kernel monta el dispositivo virtio-blk y lee su primer sector por DMA. + +### Añadido +- Dependencia `virtio-drivers` 1.13 (`no_std`, `default-features = false`, + feature `alloc`). +- `kernel/src/drivers/disco.rs`: + - **Asignador de marcos** «bump» — reparte páginas físicas de 4 KiB para el + DMA, tomadas de la mayor región de RAM libre que el cargador reporta. No + libera: suficiente para una sonda (la gestión fina llegará con el grafo). + - **`KernelHal`** — implementa el `trait Hal` de `virtio-drivers`. `dma_alloc` + entrega marcos físicos a cero; `mmio_phys_to_virt` traduce los BARs (el + cargador mapea ≥ 4 GiB de memoria física, que cubre todo MMIO de PCI); + `share`/`unshare` usan un buffer rebote para que cualquier región del kernel + pueda viajar al dispositivo. + - **`montar_y_leer_sector0`** — enumera el bus, habilita E/S + memoria + + bus-master, monta el `PciTransport` y el `VirtIOBlk`, y lee el sector 0 por + **sondeo** del *used ring* (sin depender aún de interrupciones). +- `kernel/src/drivers/pci.rs`: reescrito como `CamPuertos`, la implementación de + `ConfigurationAccess` de `virtio-drivers` sobre los puertos `0xCF8`/`0xCFC`. +- `kernel_main` captura `physical_memory_offset` y la región de RAM de + `BootInfo`, funda el subsistema de disco y reporta el resultado de la sonda. + +### Cambiado +- `boot/src/main.rs`: al forjar el disco de pruebas graba una firma + (`renaser-6.1b`) en su sector 0 — el testigo del viaje de ida y vuelta. + +### Verificado +- QEMU: el kernel reporta `virtio-blk :: bus 0 dev 3 :: 65536 sectores :: + s0=renaser-6.1b`. La firma grabada por el anfitrión se lee de vuelta intacta: + descubrimiento PCI, transporte, DMA y transferencia funcionan de punta a + punta. Las cuatro apps del userspace siguen operando sin alteración. + +--- + +## Fase 6.1c — El grafo de objetos direccionado por contenido — 2026-05-22 + +Tercer y último paso del sustrato de almacenamiento. El disco deja de ser un +dispositivo que se sondea y pasa a ser una MEMORIA QUE PERDURA: un grafo +dirigido acíclico de objetos direccionados por contenido — no un sistema de +archivos plano POSIX. + +### Añadido +- `kernel/src/almacen.rs` — el grafo de objetos: + - **Objeto** — una carga útil de bytes (`datos`) y una lista de aristas + (`hijos`: hashes de otros objetos). Las aristas hacen del almacén un DAG. + - **Direccionamiento por contenido** — la identidad de un objeto es el hash + BLAKE3 de su forma serializada. De ello se siguen dos propiedades que un + sistema de archivos jamás regala: INTEGRIDAD (el contenido leído se + rehashea y se verifica contra el hash pedido) y DEDUPLICACIÓN (contenido + idéntico produce el mismo hash; se almacena una sola vez). + - **Disco como log** — el sector 0 es el superbloque (magia `RENASGRF`, + versión, cursor del log y hash de la raíz); tras él se anexan los registros + de objetos, `[longitud u32 LE][payload postcard][relleno a cero]`. Un + índice en memoria (hash -> sector) se reconstruye al arrancar recorriendo + el log de cabo a rabo. + - API: `init` —monta el disco, lee o forja el superbloque, reconstruye el + índice—, `almacenar`, `recuperar`, `raiz` y `fijar_raiz`. +- Cinco capacidades nuevas del host en `wasm/env.rs` — `sys_object_put`, + `sys_object_datos`, `sys_object_hijo`, `sys_object_raiz` y + `sys_object_fijar_raiz` —, con la misma validación infranqueable de límites + de la memoria lineal que `sys_render_frame`. Distinguen dos clases de fallo: + un puntero inválido ABORTA la app —es su culpa, se traduce en trampa—; un + fallo del almacenamiento le devuelve un código de error negativo —no lo es—. +- `apps/cronista/` — la primera aplicación del userspace que escribe en el + almacenamiento PERSISTENTE. En cada arranque consulta la raíz del grafo, + graba un objeto nuevo —`datos`: el número de arranque; `hijos`: la raíz + anterior, el eslabón del DAG— y lo corona como raíz. Pinta una celda por + arranque registrado y un testigo de integridad que recorre la cadena entera. +- Dependencias: `serde` 1 y `postcard` 1 (serialización binaria compacta, el + formato que viaja al disco) y `blake3` 1 (la función hash). Las tres `no_std`. + +### Cambiado +- `kernel/src/drivers/disco.rs` reescrito para un sustrato PERMANENTE: + - El asignador de marcos «bump» de la Fase 6.1b cede el paso a uno de MAPA DE + BITS con liberación real. `dma_dealloc` y `unshare` devuelven los marcos a + la arena: un almacén vivo, con su trasiego incesante de DMA, ya no la agota. + - El `VirtIOBlk` deja de montarse y destruirse en cada llamada: se monta UNA + vez y queda tras un `Mutex` global. `leer_sectores` / `escribir_sectores` + exponen la E/S de bloques — el disco deja de ser de solo lectura. Se retira + `montar_y_leer_sector0`, la sonda de un solo uso de la Fase 6.1b. +- `boot/src/main.rs`: el disco de pruebas pasa a ser el disco de objetos. Ya + no se le graba una firma — se forja virgen, a cero, y el kernel lo formatea + la primera vez que no halle el superbloque. +- `main.rs`: `informar_disco` (la sonda de la Fase 6.1b) se sustituye por + `informar_almacen`, que funda el grafo. El userspace pasa de cuatro a cinco + apps; las regiones de discola y glotona se reajustan para alojar a cronista. + +### Notas +- **blake3 forzado a escalar.** El target del kernel corre sin SSE; un camino + SIMD de blake3 activado por detección en tiempo de ejecución ejecutaría + instrucciones que la CPU, sin `CR4.OSFXSR`, rechazaría con un #UD. Se fija + blake3 con `pure` + los cuatro `no_*` (`sse2`, `sse41`, `avx2`, `avx512`): + implementación puramente escalar, sin SIMD ni ensamblador. + +### Verificado +- QEMU, TRES arranques consecutivos sobre el mismo disco persistente: la app + cronista pinta 1, luego 2, luego 3 celdas — la cuenta de arranques sobrevive + a los reinicios porque vive en el grafo, en el disco, no en la RAM. El + testigo de integridad queda VERDE en los tres: el DAG entero se recorre, de + la raíz al primer eslabón, y su profundidad cuadra con la cuenta. El + superbloque en disco confirma la magia `RENASGRF`, versión 1, cursor 4 y la + raíz del tercer arranque; el registro del primer arranque guarda `datos = 1` + y cero hijos. Las otras cuatro apps siguen su curso sin alteración — las dos + `hello_wasm` renderizando, discola desalojada en púrpura, glotona en amarillo. + +--- + +## Fase 6.2 — E/S de disco asíncrona por interrupción — 2026-05-22 + +La Fase 6.1 hizo hablar al disco, pero por SONDEO: el procesador se quedaba en +espera activa vigilando el *used ring* de virtio, incapaz de atender nada más. +La 6.2 libera el planificador cooperativo — la E/S de bloques pasa a ser +REACTIVA, guiada por la interrupción física del dispositivo. + +### Añadido +- `EsperaDisco` (`drivers/disco.rs`): una transferencia de bloques expresada + como `Future` nativo. Posee sus buferes DMA —`BlkReq`, `BlkResp` y los datos, + en el heap para una dirección estable—; su `poll` envía la petición por la + API NO BLOQUEANTE de `virtio-drivers` (`read_blocks_nb`/`write_blocks_nb`), + consulta el *used ring* (`peek_used`) y, si la transferencia sigue en vuelo, + inscribe el waker y cede. `leer_bloques`/`escribir_bloques` lo construyen. +- La IRQ del disco: `montar` descubre la línea de IRQ legada del dispositivo + (registro «Interrupt Line», offset 0x3C del espacio de configuración PCI), + registra un manejador en la IDT y abre la línea en el PIC. + `interrupts::irq_disco` → `disco::atender_irq` reconoce la interrupción en el + dispositivo —leer su registro ISR baja la línea INTx— y despierta, vía un + waker de ranura única, a la tarea que aguardaba el bloque. +- `bloquear_en` — el puente para los contextos SÍNCRONOS (el arranque, las + capacidades WASM, que no pueden `.await`): lleva un `Future` de disco hasta + su final durmiendo la CPU con `hlt` —la despiertan la IRQ del disco o el + temporizador, como red de seguridad—; jamás en espera activa con el sistema + ya en marcha. +- `pic::desenmascarar` / `pic::vector_irq`: abrir una línea concreta del par + 8259 (con su cascada, si vive en el esclavo) y mapear línea → vector de IDT. +- `pci::linea_irq`: leer el registro «Interrupt Line» de un dispositivo. +- `tarea_sonda_disco` (`main.rs`): una tarea del reactor que lee el sector 0 de + forma asíncrona — la prueba viva de que la IRQ conduce la E/S sin detener a + las aplicaciones. + +### Cambiado +- `leer_sectores`/`escribir_sectores` se reescriben sobre la maquinaria + asíncrona (`bloquear_en` + `EsperaDisco`). El `almacen` y las capacidades + `sys_object_*` NO cambian una línea: heredan la E/S por interrupción de + forma transparente — la espera de disco deja de quemar ciclos en sondeo. +- El `VirtIOBlk` pide al dispositivo que emita interrupciones al completar cada + petición (`enable_interrupts`). + +### Decisiones de ingeniería +- **PIC, no IOAPIC.** El kernel corre íntegramente sobre el par 8259 (`pic.rs`) + y la crate `x86_64` no ofrece abstracción de APIC. Las IRQ legadas de PCI de + la máquina `q35` se enrutan por el 8259: basta leer la línea que el firmware + UEFI ya asignó y abrirla. Migrar al IOAPIC habría exigido levantar LAPIC + + IOAPIC + parseo de tablas ACPI — un subsistema entero, desproporcionado para + el objetivo. El resultado funcional es idéntico: E/S conducida por la + interrupción física. +- **Las capacidades WASM no `.await`-ean.** El intérprete `wasmi` ejecuta las + funciones de host de forma SÍNCRONA: no hay manera de suspender un módulo a + mitad de una llamada de host. Por eso `sys_object_*` no se vuelven + asíncronas; usan `bloquear_en`, que duerme la CPU con `hlt` en lugar de + sondear. El verdadero solapamiento E/S ↔ render lo aprovechan las TAREAS del + reactor (`tarea_sonda_disco` hoy; la carga dinámica de módulos, mañana). +- Una IRQ legada de PCI es de NIVEL: el manejador reconoce primero al + dispositivo —lo que baja su línea— y sólo después cierra el EOI del PIC; el + orden inverso reavivaría la interrupción en bucle. +- Todo acceso al disco toma su `Mutex` con las interrupciones desactivadas + (`without_interrupts`): la IRQ del disco jamás encuentra el cerrojo ocupado, + lo que hace imposible el interbloqueo por interrupción. + +### Verificado +- QEMU: el disco virtio-blk se enruta a la IRQ 11. La `tarea_sonda_disco` + reporta «sonda asíncrona OK -- 2 IRQ de disco atendidas»: la interrupción del + disco dispara de verdad. La app cronista, sobre el disco persistente heredado + de la Fase 6.1c, continúa la cuenta de arranques (3 → 4 → 5 celdas) con el + testigo de integridad del DAG en verde — la nueva E/S asíncrona lee y escribe + el grafo sin un solo fallo. Las cuatro apps WASM siguen su curso, sin un + sobresalto ni un micro-congelamiento. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md new file mode 100644 index 0000000..2400deb --- /dev/null +++ b/renaser/CLAUDE.md @@ -0,0 +1,88 @@ +# renaser — guía operativa + +**renaser** es un kernel asíncrono de *Espacio de Direccionamiento Único* +(SASOS) en Rust `#![no_std]`, bare-metal x86_64. Rompe con POSIX: interfaz +visual nativa desde el arranque, aislamiento **por software** (no por MMU), +el texto como caso particular del dibujo. + +Documentos hermanos: `ARCHITECTURE.md` (arquitectura), `ROADMAP.md` (fases y +plan), `CHANGELOG.md` (registro técnico de cambios), `DIARIO.md` (crónica en +lenguaje llano), `README.md` (presentación). + +## Construir y ejecutar + +Desde la raíz del repositorio: + +| Comando | Efecto | +|---|---| +| `cargo run` | compila el kernel, forja la imagen UEFI y abre QEMU | +| `cargo build` | compila el kernel + la imagen, sin lanzar QEMU | +| `cargo kernel` | alias: compila solo el kernel para `x86_64-unknown-none` | + +Verificación headless (sin ventana): añadir +`-- -display none -monitor unix:/tmp/q.sock,server,nowait` y capturar con +`screendump` por el socket del monitor. + +Reconstruir la app WASM del userspace tras tocar `apps/hello_wasm`: + +```sh +cd apps/hello_wasm +cargo build --target wasm32-unknown-unknown --release +cp target/wasm32-unknown-unknown/release/hello_wasm.wasm ../../kernel/assets/app.wasm +``` + +## Estructura del espacio de trabajo + +- `boot/` — orquestador de **anfitrión**: construye la imagen de disco UEFI con + la crate `bootloader` 0.11 y lanza QEMU. Es el **único miembro** del workspace. +- `kernel/` — el kernel **bare-metal**. Está **excluido** del workspace y se + compila solo como dependencia de artefacto de `boot` (target + `x86_64-unknown-none`). NUNCA debe ser miembro del workspace ni compilarse + para el anfitrión: lo haría fallar por un `_start` duplicado. +- `apps/` — aplicaciones del userspace, módulos `.wasm` + (target `wasm32-unknown-unknown`). Workspaces propios, excluidos. + +Módulos del kernel (`kernel/src/`): `main`, `grafico`, `consola`, `baliza`, +`sync`, `gdt`, `interrupts`, `pic`, `drivers/`, `almacen`, `memory/`, +`async_system/`, `texto`, `wasm/`. El detalle de cada uno está en +`ARCHITECTURE.md`. + +## Toolchain + +Nightly, fijado en `rust-toolchain.toml`. Targets `x86_64-unknown-none` y +`wasm32-unknown-unknown`. Componentes `rust-src` y `llvm-tools` (los exige el +build script de la crate `bootloader`). Entorno verificado: Artix Linux, +QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG). + +## Convenciones + +- **Idioma:** comentarios E identificadores en español. +- **`unsafe`:** confinado en células mínimas, envuelto de inmediato en una + abstracción segura. `#![deny(unsafe_op_in_unsafe_fn)]`. Cada bloque `unsafe` + lleva un comentario `SEGURIDAD:` que justifica su corrección. +- **Framebuffer / MMIO:** siempre `core::ptr::write_volatile`; jamás asignación + indexada directa (el optimizador la elidiría). +- **`no_std` estricto:** solo `core::*` y `alloc::*` (heap vivo desde la Fase 3). + Prohibido cualquier ruta de `std::*`. +- **Alineación:** 16 bytes para estructuras genéricas; 4096 bytes (página) para + buffers de memoria y regiones de asignador. +- Código densamente comentado; se cuida la coherencia de estilo entre módulos. + +## Estado + +Fases 1 a 5, la 6.0, la 6.1 completa (sustrato de almacenamiento: sonda PCI, +HAL/DMA y el grafo de objetos) y la 6.2 (E/S de disco asíncrona por +interrupción) completadas y verificadas en QEMU. Ver `ROADMAP.md`. + +## Flujo de trabajo + +En **cada iteración** de trabajo, sin excepción: + +1. Actualizar `CHANGELOG.md` — la entrada técnica detallada del cambio. +2. Actualizar `DIARIO.md` — la misma jornada contada en lenguaje llano y + elegante, sin carga técnica (es una crónica humana, no un registro técnico). +3. Verificar en QEMU si el cambio es observable (con captura de pantalla). +4. `git commit` (mensaje en español, descriptivo) y `git push origin main`. + +El remoto `origin` es `gitea.gioser.net/sergio/renaser`. Mensajes de commit en +español. Verifica una fase en QEMU antes de darla por cerrada. diff --git a/renaser/Cargo.lock b/renaser/Cargo.lock new file mode 100644 index 0000000..e1e1cd1 --- /dev/null +++ b/renaser/Cargo.lock @@ -0,0 +1,1178 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "boot" +version = "0.1.0" +dependencies = [ + "bootloader", + "kernel", +] + +[[package]] +name = "bootloader" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5fb69232f250c0d5b74eeb285702b4b0479f574980f45dc9bef97b2c65e2021" +dependencies = [ + "anyhow", + "bootloader-boot-config", + "fatfs", + "gpt", + "llvm-tools", + "mbrman", + "serde_json", + "tempfile", +] + +[[package]] +name = "bootloader-boot-config" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a5e475c13d3d988e105b76b92aeea94614c12590c33495165c02b16e895375" +dependencies = [ + "serde", +] + +[[package]] +name = "bootloader_api" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c074a3d1075b26ed77e502d6af8ab1263abbdc3d026c166e46480f973287293" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "const_fn" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413d67b29ef1021b4d60f4aa1e925ca031751e213832b4b1d588fae623c05c60" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "embedded-graphics" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core", + "float-cmp", + "micromath", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95743bef3ff70fcba3930246c4e6872882bbea0dcc6da2ca860112e0cd4bd09f" +dependencies = [ + "az", + "byteorder", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fatfs" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05669f8e7e2d7badc545c513710f0eba09c2fbef683eb859fd79c46c355048e0" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "log", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fontdue" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e57e16b3fe8ff4364c0661fdaac543fb38b29ea9bc9c2f45612d90adf931d2b" +dependencies = [ + "hashbrown 0.15.5", + "ttf-parser", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gpt" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" +dependencies = [ + "bitflags 2.11.1", + "crc", + "log", + "uuid", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "blake3", + "bootloader_api", + "crossbeam-queue", + "embedded-graphics", + "fontdue", + "futures-util", + "linked_list_allocator", + "mirada-layout", + "postcard", + "serde", + "spin", + "virtio-drivers", + "wasmi", + "x86_64", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mbrman" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fc3bff63c208d4a14301c6cb807af2d1a0760052584ce3f9a737b55fb85498" +dependencies = [ + "bincode", + "bitvec", + "serde", + "serde-big-array", + "thiserror 1.0.69", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "micromath" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" + +[[package]] +name = "mirada-layout" +version = "0.1.0" +dependencies = [ + "libm", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "safe-mmio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4813ee49326057f105d6d8ca3d8f9265095f26aa7b42094e487028403a594f4c" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + +[[package]] +name = "string-interner" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "virtio-drivers" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdc1c628cdd8ce7c3b9e65a8ed550d0338e9ef9f911e729666f1cce097de2f7" +dependencies = [ + "bitflags 2.11.1", + "enumn", + "log", + "safe-mmio", + "thiserror 2.0.18", + "zerocopy", +] + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasmi" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22bf475363d09d960b48275c4ea9403051add498a9d80c64dbc91edabab9d1d0" +dependencies = [ + "spin", + "wasmi_collections", + "wasmi_core", + "wasmi_ir", + "wasmparser 0.228.0", +] + +[[package]] +name = "wasmi_collections" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85851acbdffd675a9b699b3590406a1d37fc1e1fd073743c7c9cf47c59caacba" +dependencies = [ + "hashbrown 0.15.5", + "string-interner", +] + +[[package]] +name = "wasmi_core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef64cf60195d1f937dbaed592a5afce3e6d86868fb8070c5255bc41539d68f9d" +dependencies = [ + "libm", +] + +[[package]] +name = "wasmi_ir" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb572ce4400e06b5475819f3d6b9048513efbca785f0b9ef3a41747f944fd8" +dependencies = [ + "wasmi_core", +] + +[[package]] +name = "wasmparser" +version = "0.228.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x86_64" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7841fa0098ceb15c567d93d3fae292c49e10a7662b4936d5f6a9728594555ba" +dependencies = [ + "bit_field", + "bitflags 2.11.1", + "const_fn", + "rustversion", + "volatile", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/renaser/Cargo.toml b/renaser/Cargo.toml new file mode 100644 index 0000000..f014b24 --- /dev/null +++ b/renaser/Cargo.toml @@ -0,0 +1,89 @@ +# ============================================================================= +# renaser :: manifiesto raíz del espacio de trabajo +# ----------------------------------------------------------------------------- +# Un único espacio de direccionamiento, una única verdad de versiones. +# Las dependencias se declaran aqui una sola vez y persisten, coherentes, +# a traves de todos los miembros del sistema. +# ============================================================================= + +[workspace] +resolver = "2" +# Solo `boot` es miembro del espacio de trabajo: corre en el anfitrion. El +# kernel queda EXCLUIDO a proposito —es bare-metal puro y jamas debe compilarse +# para el anfitrion— y se construye unica y exclusivamente como dependencia de +# artefacto de `boot`, que le fija el target `x86_64-unknown-none`. +members = ["boot"] +# El kernel (bare-metal) y las apps WASM (target wasm32) se compilan aparte, +# cada cual con su propio target; quedan fuera del espacio de trabajo. +exclude = ["kernel", "apps"] + +# ----------------------------------------------------------------------------- +# Metadatos compartidos: cada miembro hereda esta identidad con `*.workspace`. +# ----------------------------------------------------------------------------- +[workspace.package] +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" +authors = ["JL Soltech "] +description = "renaser :: kernel asincrono SASOS basado en aislamiento por software" + +# ----------------------------------------------------------------------------- +# Dependencias del ecosistema. Reutilizamos infraestructura madura en lugar +# de reinventar boilerplate. `default-features = false` se aplica con bisturi, +# unicamente alli donde las features por defecto arrastrarian `std`. +# ----------------------------------------------------------------------------- +[workspace.dependencies] +# Constructor de la imagen de disco UEFI/BIOS. Es una herramienta de ANFITRION +# (usa std), la consume el miembro `boot` y NO debe enlazarse jamas en el kernel. +bootloader = "0.11" + +# API `#![no_std]` que el kernel SI consume: expone `entry_point!`, `BootInfo` +# y la descripcion del framebuffer GOP. Su version debe acompasar a `bootloader`. +bootloader_api = "0.11" + +# Abstraccion de la CPU x86_64: registros de control, GDT, IDT e instrucciones +# privilegiadas sin ensamblador inline. Es `#![no_std]` de nacimiento; su +# feature `instructions` (activa por defecto) nos da `hlt` y el control de IRQ. +x86_64 = "0.15" + +# Rasterizacion de primitivas vectoriales sobre el framebuffer. `#![no_std]`, +# sin heap: en la Fase 1 solo aporta el rasgo `DrawTarget` del lienzo. +embedded-graphics = "0.8" + +# Rasterizador de tipografias. REQUIERE `alloc`, de modo que solo se teje en el +# kernel una vez exista el asignador (Fase 2). `default-features = false` +# silencia su feature `std`. Se declara ya para congelar la version. +# `hashbrown` es lo que ACTIVA el modo `no_std` de fontdue; sin el, la crate +# recae en `std::collections` y no compila para bare-metal. `simd` queda fuera: +# nuestro target no tiene SSE. +fontdue = { version = "0.9", default-features = false, features = ["hashbrown"] } + +# --- Fase 3 :: heap dinamico y reactor asincrono --- +# Asignador del heap del kernel — algoritmo probado, configurado `no_std`. +linked_list_allocator = "0.10" +# Exclusion mutua sin sistema operativo: `Mutex` e inicializacion unica `Once`. +spin = "0.9" +# Cola lock-free, segura frente a interrupciones, para el canal de scancodes. +crossbeam-queue = { version = "0.3", default-features = false, features = ["alloc"] } +# Combinadores `Future`/`Stream` y `AtomicWaker`. Sin ejecutor ni I/O: solo +# las piezas que renaser teje en su propio reactor. +futures-util = { version = "0.3", default-features = false, features = ["alloc"] } + +# --- Fase 4 :: interprete WebAssembly para el userspace aislado --- +# `wasmi` en modo bare-metal: sin `std`, sin `wat`, sin `simd` (el target no +# tiene SSE). `hash-collections` le da mapas basados en hashbrown, `no_std`. +wasmi = { version = "1.0", default-features = false, features = ["hash-collections"] } + +# ----------------------------------------------------------------------------- +# Perfiles de compilacion. Cargo exige que los perfiles vivan en la RAIZ del +# espacio de trabajo. `panic = "abort"` es obligatorio: sin sistema operativo +# subyacente no hay desenrollado de pila (`unwinding`) posible. +# ----------------------------------------------------------------------------- +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" # codigo compacto: cada byte cuenta en bare-metal +lto = true # el optimizador cruza fronteras de crate +codegen-units = 1 # una sola unidad => maxima oportunidad de optimizacion diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md new file mode 100644 index 0000000..64bb2cc --- /dev/null +++ b/renaser/DIARIO.md @@ -0,0 +1,226 @@ +# Diario de renaser + +*Una crónica, en lenguaje llano, de cómo va naciendo un sistema operativo. +Cada jornada de trabajo añade aquí su página.* + +--- + +## El primer día — la luz + +Todo empezó con un gesto pequeño y luminoso: lograr que la máquina, al +despertar, no mostrara la fría consola de texto de siempre, sino una superficie +limpia y serena, de un azul profundo, casi nocturno. Puede parecer poca cosa, +pero esa superficie es el cimiento de cuanto vendrá: el lienzo en blanco de un +sistema entero. + +Junto a esa primera luz nació también una promesa de honestidad. Si alguna vez +algo saliera terriblemente mal, renaser no se quedaría callado: pintaría una +franja roja, ancha y visible, para confesarlo sin rodeos. Una alarma sincera. + +## El empaquetado — un cuerpo para viajar + +Un sistema no sirve de nada si no puede encenderse en una máquina de verdad. +Así que el segundo paso fue darle a renaser un cuerpo: envolverlo en un disco +capaz de arrancar por sí solo, y enseñar al ordenador a lanzarlo. Desde +entonces, una sola orden basta para verlo cobrar vida ante nuestros ojos. + +## Los reflejos — aprender a reaccionar + +Hasta aquí, renaser sabía mostrarse, pero no sabía protegerse. La tercera etapa +consistió en darle reflejos: la capacidad de reaccionar ante lo inesperado en +lugar de derrumbarse en silencio. Se le tejió una red de seguridad interna para +que, ante un tropiezo, supiera recogerse con dignidad y avisar de lo ocurrido. + +## El latido — sentir el tiempo y escuchar + +Un sistema vivo necesita pulso. Se le dio a renaser un latido regular, como un +metrónomo interno, y oídos para escuchar el teclado. Por primera vez el sistema +dejó de estar simplemente quieto: empezó a respirar, a redibujarse con cada +latido y a notar cuándo una mano pulsaba una tecla. + +## La memoria y la fluidez — crecer y atender muchas cosas + +Para crecer, renaser necesitaba memoria de la que disponer con libertad. Y para +no atascarse, necesitaba aprender a atender varias cosas a la vez sin que unas +estorbaran a otras. En esta etapa recibió ambas. Además aprendió a escribir de +verdad: con una tipografía elegante, dibujando cada letra con esmero. El texto +dejó de ser un molde rígido para volverse, también él, un dibujo. + +## La habitación segura — invitar a otros programas + +La etapa más ambiciosa hasta ahora: abrir la puerta a que otros programas +vivieran dentro de renaser. Pero no de cualquier modo. Cada programa invitado +entra en una habitación sellada de la que no puede salir; solo puede hacer +aquello para lo que renaser, expresamente, le tiende un puente. Todo lo demás, +sencillamente, no existe para él. La primera visita fue modesta y entrañable: +un cuadrado de color que se desliza por la pantalla obedeciendo al teclado. + +## Una jornada de orden — ordenar la casa y guardar la historia + +No todo es construir; a veces toca ordenar. Se revisó la estructura del +proyecto y se repartió mejor el trabajo entre sus piezas, para que cada una +tuviera un cometido claro y nítido. Se escribió la documentación que cuenta qué +es renaser y hacia dónde camina. Y se guardó todo el trabajo en un lugar +seguro, donde queda registro fiel de cada cambio, de modo que ninguna jornada +se pierda. Hubo, además, un pequeño susto: una contraseña se coló por error +donde no debía; se retiró de inmediato y se dejó constancia para corregirla. + +## El reparto del tiempo — muchos huéspedes, ningún tirano + +Hasta esta jornada, el programa invitado de renaser, una vez dentro, se quedaba +con la casa entera: hablaba sin pausa y no dejaba sitio a nadie más. Era un solo +huésped, y se comportaba como dueño. + +Esta etapa enseñó a renaser a repartir el tiempo. Se acordó un compás —el latido +del sistema marca, una y otra vez, el turno de cada cual— y se acordó también un +gesto de cortesía: cada programa hace su pequeña tarea, pinta su fotograma y, al +terminar, cede el paso. Así, lo que antes era un único inquilino pasaron a ser +varios, conviviendo en paz, cada uno en su propia ventana de la pantalla. + +Pero la cortesía, por sí sola, es frágil: basta un huésped maleducado —uno que +no quiera ceder nunca el turno— para paralizarlo todo. Por eso se añadió una +salvaguarda serena pero firme: a cada programa se le entrega, en cada turno, una +ración medida de tiempo. Si la agota sin terminar —porque se ha enredado en un +bucle sin fin—, renaser le retira el turno con delicadeza, tiñe su ventana de un +púrpura inconfundible y prosigue. El sistema no se cuelga; simplemente despide al +inquilino díscola y sigue atendiendo a los demás, que ni se enteran. + +Para comprobarlo se invitó, a propósito, a un huésped díscola: un programa hecho +para enredarse en un bucle eterno. Al arrancar, las dos ventanas honradas +cobraron vida y obedecieron al teclado a la vez, en perfecta armonía; la tercera, +la del díscola, se apagó al instante en un púrpura tranquilo. La promesa quedó +demostrada: renaser es veloz porque confía, pero nunca ingenuo. + +## La otra mitad del muro — el espacio, no solo el tiempo + +La jornada anterior enseñó a renaser a repartir el tiempo y a contener al que no +quería cederlo. Pero un huésped puede portarse mal de dos maneras: acaparando el +tiempo, sí, o acaparando el espacio. Quedaba media casa por proteger. + +En esta etapa se le puso a cada programa un techo a la memoria que puede +reclamar. Mientras se conforme con su habitación, vive tranquilo; si pretende +derribar las paredes para anexarse la casa entera, renaser se lo impide con +serenidad y, como con el díscola, lo despide —esta vez tiñendo su ventana de un +amarillo pálido, para que de un vistazo se distinga al que abusó del espacio del +que abusó del tiempo. + +Se cerró además una pequeña negligencia de la jornada anterior: cuando un +huésped era despedido, renaser olvidaba recoger del todo sus cosas. Ahora, al +marcharse cualquier programa, el sistema reclama hasta el último de sus enseres; +no queda rastro. Y se invitó a un nuevo huésped de prueba, una aplicación +glotona hecha para devorar memoria: al arrancar, fue frenada y despedida al +instante, su ventana amarilla junto a la púrpura del díscola, mientras las dos +aplicaciones honradas seguían su baile, ajenas a todo. + +Con esto, las dos dimensiones físicas —el tiempo y el espacio— quedan bajo el +gobierno firme y sereno de renaser. El siguiente horizonte es más hondo: dar al +sistema una memoria que perdure, un lugar donde guardar las cosas más allá del +apagado. Pero esa es ya otra página. + +## El primer golpe a la roca — encontrar el disco + +Dar a renaser una memoria que perdure exige, antes que nada, hablar con un +disco. Y un disco, a diferencia de la pantalla o del teclado, no se le anuncia +al sistema al nacer: hay que salir a buscarlo. Es una empresa grande, así que se +decidió acometerla con prudencia, a pasos cortos y firmes. + +El primer paso fue, sencillamente, encontrar el disco. renaser aprendió a +recorrer el bus por el que se enganchan los periféricos de la máquina, +preguntando puerta por puerta «¿quién hay aquí?», hasta dar con el disco virtual +que se le había preparado. Y al hallarlo, lo dijo en voz alta sobre su propia +pantalla: lo había localizado, y supo decir en qué dirección vivía. + +Puede parecer un gesto pequeño —una línea de texto—, pero es el primer golpe de +pico contra una roca dura. La conversación con el disco apenas empieza; pero la +puerta, al menos, ya está encontrada. + +## La primera palabra del disco — leer lo que se escribió + +Encontrada la puerta, faltaba lo más difícil: cruzarla. Hablar de verdad con un +disco no es pedirle las cosas y esperar: es tender con él una memoria +compartida, un terreno común donde el sistema deja una petición y el disco +deposita su respuesta. Montar ese terreno —y aprender el protocolo para usarlo— +fue el trabajo de esta jornada, el más cercano al metal de cuantos renaser ha +acometido. + +Para no equivocar el paso se ideó una prueba sencilla y honesta. Al preparar el +disco, el sistema anfitrión grabó en su primer renglón una breve firma, una +palabra clave. Y entonces se le pidió a renaser que, ya por sus propios medios, +abriera el disco y leyera ese mismo renglón. Si la palabra volvía intacta, no +habría duda: el camino completo —encontrar el disco, tender la memoria +compartida, pedir, esperar y recibir— funcionaba de cabo a rabo. + +Al arrancar, renaser lo dijo en su pantalla, sereno: había leído el sector +cero, y la palabra que mostró era, letra por letra, la que se había grabado. El +disco había hablado, y renaser lo había entendido. La roca, por fin, cedió. + +Queda por delante lo más hermoso: sobre esta conversación recién abierta, +levantar una verdadera memoria duradera —no un archivo de los de antes, sino un +tejido de objetos—. Pero el cimiento ya está puesto, y es firme. + +## Un tejido de objetos — la memoria que perdura + +La conversación con el disco estaba abierta; faltaba decidir qué contarle. El +mundo que renaser hereda de la informática de siempre habría respondido sin +pensarlo: archivos, carpetas, rutas con barras. renaser eligió otra cosa. + +En lugar de un archivero de cajones con etiquetas, se tejió un grafo de +objetos. Cada objeto es un puñado de datos y unos cuantos hilos que lo enlazan +con otros. Y —esta es la idea hermosa— ningún objeto lleva un nombre que +alguien le haya puesto: su nombre es su contenido. De cada uno se calcula una +huella única, irrepetible, y esa huella es su identidad. Dos cosas idénticas +tienen la misma huella, de modo que nada se guarda dos veces; y si una se +corrompe, su huella deja de cuadrar y la mentira se delata sola. + +Para que esa memoria mereciera el nombre de duradera hubo que pulir el trato +con el disco: enseñarle a renaser no solo a leer, sino a escribir. Y poner +orden en la despensa de la que el sistema toma prestada memoria para hablar con +el hardware — que ahora, al terminar cada gestión, devuelve lo que tomó en vez +de acumularlo sin fin. + +Y entonces llegó la prueba más bonita. Se escribió una pequeña aplicación, una +cronista, con un único oficio: llevar la cuenta de las veces que el sistema +despierta. En cada arranque consulta el grafo, encuentra el último apunte, +añade el suyo —enlazado al anterior, como el eslabón nuevo de una cadena— y +pinta una casilla por cada despertar registrado. + +Se apagó la máquina y se volvió a encender. Y otra vez. La cronista dibujó +primero una casilla, luego dos, luego tres. La cuenta no se perdía con el +apagón: vivía en el disco, en el tejido de objetos, y cada reinicio la +encontraba intacta y la hacía crecer. Por primera vez, renaser recordaba algo +de una vida a la siguiente. + +La memoria duradera ya no es una promesa. Es un tejido, y tiene sus primeros +hilos. + +## El disco que avisa — la espera que ya no congela + +Hablar con el disco, hasta esta jornada, tenía un precio oculto. Cada vez que +renaser le pedía un bloque, el sistema entero contenía el aliento: el procesador +se quedaba mirando fijamente al disco, preguntando una y otra vez «¿ya?, ¿ya?», +sin hacer nada más, hasta que la respuesta llegaba. Para una lectura suelta +apenas se notaba. Pero el futuro de renaser —cargar aplicaciones enteras desde +el disco— habría convertido esos instantes en tirones visibles, en pequeños +congelamientos de la imagen. + +Así que se le enseñó al disco a AVISAR. En lugar de que el sistema vigile, ahora +es el disco quien, al terminar, da un golpecito en el hombro del kernel —una +interrupción— para decir «listo». Y mientras tanto, renaser no aguarda de brazos +cruzados: si hay trabajo, lo hace; si no, se adormece un instante, y el propio +golpecito del disco lo despierta. Ni un ciclo malgastado. + +Para no equivocar el paso se escribió una pequeña sonda: una tarea que pide al +disco su primer bloque y, en vez de quedarse esperando, cede el turno. Las +aplicaciones de la pantalla siguieron pintándose, fluidas, mientras el disco +trabajaba por su cuenta; y cuando el bloque estuvo listo, su aviso reanudó la +sonda. En la pantalla quedó escrito: la lectura se había atendido por aviso, no +por vigilancia. + +Es un cambio que casi no se ve —la cronista sigue contando arranques, una +casilla más cada vez— pero que se sentirá en todo lo que venga después. El disco +y renaser ya no se miran fijamente: se hablan, y entre frase y frase, cada cual +atiende lo suyo. + +--- + +*El diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/README.md b/renaser/README.md new file mode 100644 index 0000000..12a6c3b --- /dev/null +++ b/renaser/README.md @@ -0,0 +1,48 @@ +# renaser + +**renaser** es un kernel asíncrono de *Espacio de Direccionamiento Único* +(SASOS), escrito en Rust `#![no_std]` para x86_64 bare-metal. + +Es un sistema operativo disruptivo que rompe por completo con el paradigma +POSIX de los años 70: no emula Linux, no usa archivos planos, no usa TTYs ni +capas GNU. El aislamiento entre aplicaciones no descansa en la MMU ni en los +anillos de privilegio de la CPU, sino en **límites matemáticos sobre el +bytecode** — aislamiento por software (SFI). La interfaz es visual desde el +primer microsegundo: el texto es, simplemente, un caso particular del dibujo. + +## Qué hace, hoy + +- Arranca por **UEFI** y adopta el framebuffer GOP con doble búfer sin parpadeo. +- Se **autoempaqueta** en una imagen de disco UEFI y se lanza en QEMU. +- Tiene **reflejos de fallo**: GDT/TSS, IDT y manejadores de excepción; si + colapsa, lo dibuja (franja roja de pánico, naranja de memoria agotada). +- **Late con el hardware**: PIC remapeado, temporizador (PIT) y teclado. +- Gestiona **memoria dinámica** (heap de 64 MiB, asignador global). +- Ejecuta un **reactor asíncrono cooperativo** sobre los `Future` nativos de + Rust: las interrupciones no conmutan contexto, despiertan tareas. +- Rasteriza **texto vectorial** al vuelo con `fontdue`. +- Ejecuta un **userspace WebAssembly** aislado por capacidades (`wasmi`): las + aplicaciones solo tocan el mundo a través de funciones de host concedidas. + +## Construir y ejecutar + +Requisitos: `rustup` con toolchain nightly, QEMU y firmware OVMF. + +```sh +cargo run +``` + +Compila el kernel para `x86_64-unknown-none`, forja la imagen de disco UEFI y +abre QEMU. Ver `CLAUDE.md` para el resto de comandos y el flujo de la app WASM. + +## Documentación + +| Documento | Contenido | +|---|---| +| `ARCHITECTURE.md` | la arquitectura del sistema, subsistema a subsistema | +| `ROADMAP.md` | fases completadas y plan de las siguientes | +| `CLAUDE.md` | guía operativa: comandos, estructura y convenciones | + +## Licencia + +MPL-2.0 diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md new file mode 100644 index 0000000..c00988c --- /dev/null +++ b/renaser/ROADMAP.md @@ -0,0 +1,108 @@ +# renaser — Hoja de ruta + +Estado de las fases del proyecto. Para la arquitectura, ver `ARCHITECTURE.md`. + +## Fases completadas + +Todas verificadas en QEMU (captura de pantalla incluida en su momento). + +### Fase 1 — el primer microsegundo +Arranque UEFI, adopción del framebuffer GOP, lienzo de doble búfer y la baliza +de pánico (franja roja al colapsar). Punto de entrada con `bootloader_api`. + +### Fase 1.5 — empaquetado y arranque +Miembro `boot/`: constructor de la imagen de disco UEFI (crate `bootloader` +0.11, vía dependencia de artefacto) y lanzador de QEMU. `cargo run` pasa a +compilar, forjar la imagen y arrancar, todo de un gesto. + +### Fase 2.0 — cimientos del manejo de fallos +GDT propia + TSS con stack de emergencia (IST) para el doble fallo. IDT con +manejadores de excepción de CPU. El breakpoint es recuperable; el resto de +excepciones encienden la baliza. + +### Fase 2.1 — interrupciones de hardware +Remapeo del PIC 8259 fuera del rango de las excepciones. Temporizador (PIT) a +100 Hz e IRQ1 de teclado. El kernel pasa de "pintar una vez" a un bucle de +render despertado por el hardware. + +### Fase 3 — memoria dinámica y reactor asíncrono +Heap de 64 MiB con `linked_list_allocator` como asignador global; manejador +propio de OOM (franja naranja). Reactor cooperativo: `Executor`, `Task`, +`Waker`. Texto vectorial con `fontdue`: el texto deja de ser mapa de bits. + +### Fase 4 — el escudo de aislamiento WASM +Runtime `wasmi` `no_std`. Matriz de capacidades con dos funciones de host +(`sys_render_frame`, `sys_get_scancode`) y validación infranqueable de límites +de la memoria lineal. Primera app del userspace: `apps/hello_wasm`, un módulo +`wasm32` aislado que pinta y responde al teclado. + +### Fase 5 — multitarea cooperativa, fuel y reloj +Unificación del reactor (Fase 3) y el runtime WASM (Fase 4). El ABI del +userspace pasa a `init`/`tick`: cada `tick` es un punto de cesión cooperativa. +`async_system::reloj` convierte la IRQ0 en el `Future` `EsperaFrame`, que marca +el compás de los fotogramas. Cada app es una `AplicacionWasm` persistente y una +tarea del reactor. Escudo de combustible (`fuel`): cada `tick` corre con un +presupuesto estricto; agotarlo desaloja la app sin tocar al kernel. Capacidades +ampliadas — regiones de dibujo por app y canal de teclado por app. Verificado +en QEMU con tres apps concurrentes, una de ellas díscola y desalojada en vivo. + +### Fase 6.0 — cuotas de memoria y ciclo de vida +Completa el aislamiento espacial del userspace. Cada `AplicacionWasm` instancia +su `Store` con un `StoreLimits` (techo de memoria lineal de 4 MiB, vía +`Store::limiter`); rebasarlo es una trampa que desaloja la app con baliza +amarilla — gemela de la púrpura del desalojo por combustible. `Drop` para +`AplicacionWasm` reconcilia el ciclo de vida: da de baja el canal de teclado de +la difusión de la IRQ1. Verificado en QEMU con cuatro apps — una díscola +(desalojo temporal) y una glotona (desalojo espacial), ambas en vivo. + +## Fase 6.1 — sustrato de almacenamiento (completada) + +Estrategia incremental para el almacenamiento, frente al riesgo del hardware: + +- **6.1a — Sonda PCI** *(hecha)* — `drivers/pci.rs` enumera el bus PCI por + `0xCF8`/`0xCFC` y localiza el disco virtio-blk; `boot` forja el disco de + pruebas y lo adjunta como `virtio-blk-pci`. El muro del descubrimiento de + hardware queda derribado. +- **6.1b — HAL y lectura de sector** *(hecha)* — `drivers/disco.rs`: asignador + de marcos «bump», `KernelHal` (el `trait Hal` de `virtio-drivers`: DMA y + traducción de direcciones) y `montar_y_leer_sector0`, que lee el sector 0 por + sondeo. Verificado por una firma que viaja del anfitrión al disco y vuelve. +- **6.1c — Grafo de objetos** *(hecha)* — `almacen.rs`: el almacenamiento como + DAG direccionado por contenido —la identidad de un objeto es el hash BLAKE3 + de su forma serializada (`postcard`); el disco se organiza como un log con + superbloque e índice—, y las cinco capacidades `sys_object_*` que lo exponen + al userspace. `drivers/disco.rs` gana un asignador de marcos con liberación + real (mapa de bits), escritura de sectores y un `VirtIOBlk` persistente. La + app `cronista` lleva la cuenta de los arranques en el grafo: la cuenta + perdura entre reinicios. + +## Fase 6.2 — E/S de disco asíncrona por interrupción (completada) + +La Fase 6.1 hizo hablar al disco, pero por **sondeo**: el procesador se quedaba +en espera activa vigilando el *used ring* de virtio. La 6.2 libera el +planificador cooperativo — la E/S de bloques pasa a ser **reactiva**: + +- `EsperaDisco` — una transferencia de bloques como `Future` nativo, sobre la + API no bloqueante de `virtio-drivers` (`read_blocks_nb` / `peek_used` / + `complete_*`). Cede la CPU mientras el disco trabaja. +- La **IRQ del disco** — `montar` descubre la línea de IRQ legada del + dispositivo (registro «Interrupt Line» del espacio de configuración PCI), la + enruta por el 8259 y registra su manejador; `atender_irq` reconoce la + interrupción y despierta a la tarea que aguardaba el bloque. +- `bloquear_en` — el puente para los contextos síncronos (el arranque, las + capacidades WASM): duerme la CPU con `hlt` en vez de sondear. + +Decisión de ingeniería: las IRQ se enrutan por el **PIC 8259** que el kernel ya +gobierna, no por el IOAPIC — basta leer la línea que el firmware ya asignó. +Verificado en QEMU: el disco se enruta a la IRQ 11; una tarea-sonda del reactor +lee un bloque de forma asíncrona mientras las apps siguen pintando. + +Líneas abiertas posteriores: carga/descarga dinámica de apps desde el grafo de +objetos; más capacidades del host (temporización, audio). + +## Principios que persisten entre fases + +- Reutilizar infraestructura madura de la comunidad antes que reinventar. +- `unsafe` mínimo, confinado y justificado. +- Verificar cada fase en QEMU antes de cerrarla. +- `git commit` + `git push` tras cada iteración. diff --git a/renaser/apps/cronista/Cargo.lock b/renaser/apps/cronista/Cargo.lock new file mode 100644 index 0000000..d19e5f3 --- /dev/null +++ b/renaser/apps/cronista/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cronista" +version = "0.1.0" diff --git a/renaser/apps/cronista/Cargo.toml b/renaser/apps/cronista/Cargo.toml new file mode 100644 index 0000000..46804b4 --- /dev/null +++ b/renaser/apps/cronista/Cargo.toml @@ -0,0 +1,31 @@ +# ============================================================================= +# renaser :: apps/cronista — Fase 6.1c :: el primer escriba del userspace +# ----------------------------------------------------------------------------- +# Un modulo WebAssembly que, a diferencia de sus vecinas, deja HUELLA. En cada +# arranque graba un objeto en el grafo persistente del kernel —enlazado al del +# arranque anterior, formando una cadena— y lo corona como raiz. Asi lleva la +# cronica de cuantas veces ha despertado el sistema: una cuenta que sobrevive +# a los reinicios porque vive en el disco, no en la RAM. +# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel. +# ============================================================================= + +[package] +name = "cronista" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM cronista — la cronica persistente de los arranques" + +[workspace] + +# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que +# wasmi instancia. +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/cronista/src/lib.rs b/renaser/apps/cronista/src/lib.rs new file mode 100644 index 0000000..53d8ae7 --- /dev/null +++ b/renaser/apps/cronista/src/lib.rs @@ -0,0 +1,234 @@ +// ============================================================================= +// renaser :: apps/cronista — Fase 6.1c :: el primer escriba del userspace +// ----------------------------------------------------------------------------- +// Las apps de fases anteriores eran efimeras: su mundo se borraba al apagar la +// maquina. `cronista` es la primera que deja HUELLA. En cada arranque: +// +// 1. pregunta al kernel por la RAIZ del grafo de objetos —la cabeza de la +// cadena de arranques anteriores—; +// 2. lee de ella el numero del ultimo arranque; +// 3. graba un objeto nuevo —sus datos: el numero de arranque; su hijo: la +// raiz anterior— y lo corona como raiz; +// 4. recorre la cadena entera para verificar que el DAG persiste integro; +// 5. pinta una celda por cada arranque registrado. +// +// La cuenta NO vive en la RAM: vive en el disco, en el grafo direccionado por +// contenido. Sobrevive a los reinicios. Cada vez que renaser despierta, la +// cronista añade un eslabon a la cadena y una celda a su rejilla. +// +// Sus unicas vias hacia el mundo son las capacidades `sys_object_*` que el +// kernel le inyecta. No conoce el disco, ni el bus PCI, ni el formato en +// sectores: solo objetos, hashes y aristas. +// ============================================================================= + +#![no_std] + +// --- Las capacidades que el kernel `renaser` inyecta a esta aplicacion. --- +#[link(wasm_import_module = "renaser")] +extern "C" { + /// Compone un bufer de pixeles (de ESTA memoria lineal) en la region que el + /// kernel asigno a esta aplicacion. + fn sys_render_frame(ptr: u32, len: u32); + + /// Graba un objeto en el grafo: `datos` es su carga util; `hijos` apunta a + /// un arreglo de `hijos_cnt` hashes de 32 bytes —las aristas—. El hash + /// resultante se escribe en `salida`. Devuelve 0 si todo fue bien. + fn sys_object_put(datos: u32, datos_len: u32, hijos: u32, hijos_cnt: u32, salida: u32) -> i32; + + /// Copia la carga util del objeto `hash` en `salida`. Devuelve el numero de + /// bytes copiados, o un valor negativo si fallo. + fn sys_object_datos(hash: u32, salida: u32, capacidad: u32) -> i32; + + /// Devuelve el numero de hijos del objeto `hash` y, si `indice` es valido, + /// escribe el hash de ese hijo en `salida`. Negativo si el objeto no existe. + fn sys_object_hijo(hash: u32, indice: u32, salida: u32) -> i32; + + /// Escribe en `salida` el hash de la raiz del grafo. Devuelve 1 si hay + /// raiz, 0 si el grafo aun esta vacio. + fn sys_object_raiz(salida: u32) -> i32; + + /// Corona el objeto `hash` como raiz del grafo. Devuelve 0 si lo logro. + fn sys_object_fijar_raiz(hash: u32) -> i32; +} + +/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco. +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +// --- Geometria de la escena. El ancho y el alto DEBEN coincidir con la region +// que el kernel asigna a esta app. --- +const ANCHO: usize = 360; +const ALTO: usize = 80; + +/// Lado de paso de la rejilla de celdas, en pixeles. +const PASO: usize = 20; +/// Lado de una celda, en pixeles. +const LADO: usize = 16; +/// Celdas que caben en una fila. +const POR_FILA: usize = ANCHO / PASO; +/// Celdas que caben en la rejilla entera — el techo de lo que se pinta. +const MAX_CELDAS: usize = POR_FILA * (ALTO / PASO); + +/// Indigo casi negro: el fondo del lienzo de la cronista. +const FONDO: u32 = 0x0E_14_22; +/// Ambar calido: una celda por arranque registrado. +const CELDA: u32 = 0xF2_B2_33; +/// Verde: el DAG se recorrio integro de la raiz al primer eslabon. +const VERDE: u32 = 0x35_C4_6A; +/// Rojo: la cadena se rompio — un objeto no resolvio. +const ROJO: u32 = 0xD4_1E_2C; + +/// El lienzo de la aplicacion, en SU propia memoria lineal. +static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO]; + +// --- Buferes de intercambio con las capacidades `sys_object_*`. El kernel lee +// y escribe hashes y datos AQUI, siempre dentro de esta memoria lineal. --- +/// El hash de la raiz anterior — la cabeza de la cadena al arrancar. +static mut HASH_RAIZ: [u8; 32] = [0; 32]; +/// El hash del objeto que esta cronista graba en este arranque. +static mut HASH_NUEVO: [u8; 32] = [0; 32]; +/// Hash de trabajo para recorrer la cadena del DAG. +static mut HASH_AUX: [u8; 32] = [0; 32]; +/// Los ocho bytes del numero de arranque, de ida y de vuelta del grafo. +static mut DATOS_IO: [u8; 8] = [0; 8]; + +/// Preparacion: el kernel la invoca UNA sola vez. Aqui ocurre toda la cronica — +/// leer el grafo, grabar el eslabon nuevo, verificar el DAG y pintar. +#[no_mangle] +pub extern "C" fn init() { + // 1. ¿Hay ya una raiz? Es la cabeza de la cadena de arranques anteriores. + let tiene_raiz = unsafe { sys_object_raiz(core::ptr::addr_of_mut!(HASH_RAIZ) as u32) } == 1; + + // 2. El numero del ultimo arranque vive en los `datos` de esa raiz. + let mut previo: u64 = 0; + if tiene_raiz { + let leidos = unsafe { + sys_object_datos( + core::ptr::addr_of!(HASH_RAIZ) as u32, + core::ptr::addr_of_mut!(DATOS_IO) as u32, + 8, + ) + }; + if leidos == 8 { + // SEGURIDAD: lectura de un escalar `Copy` de un estatico propio. + previo = u64::from_le_bytes(unsafe { *core::ptr::addr_of!(DATOS_IO) }); + } + } + let cuenta = previo + 1; + + // 3. Grabar el objeto de ESTE arranque. Sus `datos` son el numero de + // arranque; su unico hijo, la raiz anterior — el eslabon nuevo del DAG. + // SEGURIDAD: escritura de un escalar `Copy` a un estatico propio. + unsafe { + *core::ptr::addr_of_mut!(DATOS_IO) = cuenta.to_le_bytes(); + } + let (hijos_ptr, hijos_cnt) = if tiene_raiz { + (core::ptr::addr_of!(HASH_RAIZ) as u32, 1u32) + } else { + (0u32, 0u32) + }; + let grabado = unsafe { + sys_object_put( + core::ptr::addr_of!(DATOS_IO) as u32, + 8, + hijos_ptr, + hijos_cnt, + core::ptr::addr_of_mut!(HASH_NUEVO) as u32, + ) + }; + + // 4. Coronar el objeto nuevo como raiz y verificar la integridad del DAG. + let mut integro = false; + if grabado == 0 + && unsafe { sys_object_fijar_raiz(core::ptr::addr_of!(HASH_NUEVO) as u32) } == 0 + { + integro = verificar_cadena(cuenta); + } + + // 5. Pintar la cronica: una celda por arranque, un testigo de integridad. + pintar(cuenta, integro); +} + +/// Un fotograma de trabajo. El numero de arranque no cambia durante una sesion: +/// la cronica que `init` pinto persiste en el lienzo del kernel. `tick` solo +/// cede el control, fiel al ABI cooperativo — no toda app necesita redibujar. +#[no_mangle] +pub extern "C" fn tick() {} + +/// Recorre la cadena del DAG desde el objeto recien grabado, descendiendo por +/// el hijo 0, y comprueba que su profundidad coincide con el numero de +/// arranque. Si coincide, el grafo entero se leyo de vuelta del disco integro. +fn verificar_cadena(cuenta: u64) -> bool { + // Partir del objeto recien grabado. + // SEGURIDAD: copia de un arreglo `Copy` entre dos estaticos propios. + unsafe { + *core::ptr::addr_of_mut!(HASH_AUX) = *core::ptr::addr_of!(HASH_NUEVO); + } + let mut profundidad: u64 = 0; + loop { + profundidad += 1; + // `sys_object_hijo` lee el hash de HASH_AUX y, si hay hijo, escribe el + // del hijo 0 en el MISMO bufer: la cadena desciende un eslabon. + let hijos = unsafe { + sys_object_hijo( + core::ptr::addr_of!(HASH_AUX) as u32, + 0, + core::ptr::addr_of_mut!(HASH_AUX) as u32, + ) + }; + // Sin hijos: fin de la cadena — el primer arranque de todos. Un valor + // negativo seria un objeto que no resolvio: la cadena estaria rota. + if hijos <= 0 || profundidad >= 4096 { + break; + } + } + profundidad == cuenta +} + +/// Pinta la cronica: el fondo, una celda ambar por arranque y, en la esquina, +/// el testigo de integridad del grafo. +fn pintar(cuenta: u64, integro: bool) { + // SEGURIDAD: durante `init` esta es la unica via de acceso a LIENZO, y el + // kernel jamas reentra el modulo mientras `init` corre. + let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) }; + for pixel in lienzo.iter_mut() { + *pixel = FONDO; + } + + // Una celda ambar por cada arranque registrado, dispuestas en rejilla. + let celdas = (cuenta as usize).min(MAX_CELDAS); + for i in 0..celdas { + let x = (i % POR_FILA) * PASO + 2; + let y = (i / POR_FILA) * PASO + 2; + rellenar(lienzo, x, y, LADO, LADO, CELDA); + } + + // El testigo de integridad: verde si la cadena se recorrio entera de la + // raiz al primer eslabon, rojo si algo se rompio. + let testigo = if integro { VERDE } else { ROJO }; + rellenar(lienzo, ANCHO - 14, 4, 10, 10, testigo); + + // SEGURIDAD: `sys_render_frame` es una capacidad del host; el (ptr, len) + // describe nuestra propia memoria lineal y el host lo verifica sin piedad. + unsafe { + sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32); + } +} + +/// Rellena un rectangulo, recortado con firmeza a los limites del lienzo. +fn rellenar(lienzo: &mut [u32], x: usize, y: usize, ancho: usize, alto: usize, color: u32) { + let x1 = (x + ancho).min(ANCHO); + let y1 = (y + alto).min(ALTO); + let mut fila = y; + while fila < y1 { + let base = fila * ANCHO; + let mut col = x; + while col < x1 { + lienzo[base + col] = color; + col += 1; + } + fila += 1; + } +} diff --git a/renaser/apps/discola/Cargo.lock b/renaser/apps/discola/Cargo.lock new file mode 100644 index 0000000..e06a61b --- /dev/null +++ b/renaser/apps/discola/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "discola" +version = "0.1.0" diff --git a/renaser/apps/discola/Cargo.toml b/renaser/apps/discola/Cargo.toml new file mode 100644 index 0000000..c09c8b2 --- /dev/null +++ b/renaser/apps/discola/Cargo.toml @@ -0,0 +1,30 @@ +# ============================================================================= +# renaser :: apps/discola — el inquilino discolo del userspace +# ----------------------------------------------------------------------------- +# Un modulo WebAssembly construido a proposito para portarse mal: su `tick` +# cae en un bucle cerrado y jamas retorna. Existe para una sola cosa — +# demostrar que el escudo de combustible (fuel) del kernel `renaser` lo +# desaloja sin colgar el sistema ni perturbar a sus apps vecinas. +# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel. +# ============================================================================= + +[package] +name = "discola" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM discola — un bucle infinito que el fuel fulmina" + +[workspace] + +# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que +# wasmi instancia. +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/discola/src/lib.rs b/renaser/apps/discola/src/lib.rs new file mode 100644 index 0000000..f59e1cd --- /dev/null +++ b/renaser/apps/discola/src/lib.rs @@ -0,0 +1,46 @@ +// ============================================================================= +// renaser :: apps/discola — Fase 5 :: el inquilino discolo del userspace +// ----------------------------------------------------------------------------- +// Esta aplicacion esta MAL a proposito. Su `tick` no hace un fotograma de +// trabajo y retorna —como manda el ABI cooperativo—: cae en un bucle cerrado +// y jamas devuelve el control. En un sistema cooperativo ingenuo, eso colgaria +// la maquina entera. +// +// Pero renaser ejecuta cada `tick` con un presupuesto estricto de COMBUSTIBLE. +// Cuando este bucle lo agota, el runtime `wasmi` lanza una trampa, el kernel +// recupera el mando y desaloja a este modulo — tatuando su region de purpura. +// El resto del userspace ni se entera. Eso es lo que esta app demuestra. +// ============================================================================= + +#![no_std] + +/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco. +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +/// Sumidero de las escrituras del bucle: obliga al compilador a CONSERVAR cada +/// iteracion —y, con ella, su consumo de combustible— en lugar de elidirla. +static mut SUMIDERO: u64 = 0; + +/// Preparacion. No hay nada honrado que preparar: el kernel la invoca, retorna +/// sin incidentes, y la app pasa por buena... hasta su primer `tick`. +#[no_mangle] +pub extern "C" fn init() {} + +/// El fotograma que nunca termina. Un bucle cerrado, deliberado: jamas retorna. +/// El kernel `renaser` lo cortara por agotamiento de combustible y desalojara +/// esta aplicacion sin que el sistema sufra un solo sobresalto. +#[no_mangle] +pub extern "C" fn tick() { + let mut contador: u64 = 0; + loop { + contador = contador.wrapping_add(1); + // SEGURIDAD: escritura volatil a un escalar estatico; su unico fin es + // que el optimizador no pueda vaciar el bucle. No se crea referencia. + unsafe { + core::ptr::write_volatile(core::ptr::addr_of_mut!(SUMIDERO), contador); + } + } +} diff --git a/renaser/apps/glotona/Cargo.lock b/renaser/apps/glotona/Cargo.lock new file mode 100644 index 0000000..de7981b --- /dev/null +++ b/renaser/apps/glotona/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "glotona" +version = "0.1.0" diff --git a/renaser/apps/glotona/Cargo.toml b/renaser/apps/glotona/Cargo.toml new file mode 100644 index 0000000..d2cf8ae --- /dev/null +++ b/renaser/apps/glotona/Cargo.toml @@ -0,0 +1,30 @@ +# ============================================================================= +# renaser :: apps/glotona — el inquilino voraz del userspace +# ----------------------------------------------------------------------------- +# Un modulo WebAssembly construido a proposito para devorar memoria: su `tick` +# reclama paginas de memoria lineal sin freno. Existe para demostrar que el +# techo ESPACIAL del kernel `renaser` lo desaloja —baliza amarilla— sin agotar +# el heap del sistema ni perturbar a sus apps vecinas. +# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel. +# ============================================================================= + +[package] +name = "glotona" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM glotona — devora memoria; el techo espacial la frena" + +[workspace] + +# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que +# wasmi instancia. +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/glotona/src/lib.rs b/renaser/apps/glotona/src/lib.rs new file mode 100644 index 0000000..d8b90e3 --- /dev/null +++ b/renaser/apps/glotona/src/lib.rs @@ -0,0 +1,39 @@ +// ============================================================================= +// renaser :: apps/glotona — Fase 6.0 :: el inquilino voraz del userspace +// ----------------------------------------------------------------------------- +// Esta aplicacion esta MAL a proposito, como su hermana `discola` — pero en la +// otra dimension. Donde `discola` devora TIEMPO (un bucle sin fin), `glotona` +// devora ESPACIO: su `tick` invoca `memory.grow` reclamando memoria lineal sin +// freno alguno. +// +// renaser le impone a cada modulo un techo de memoria. Cuando esta peticion lo +// rebasa, el runtime `wasmi` —configurado para ello— lanza una trampa en vez +// de devolver un discreto -1; el kernel la captura, desaloja a este modulo y +// tiñe su region de amarillo palido. El heap del sistema jamas corre peligro. +// ============================================================================= + +#![no_std] + +/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco. +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +/// Preparacion. Nada honrado que preparar: la app pasa por buena hasta su +/// primer `tick`, igual que su hermana `discola`. +#[no_mangle] +pub extern "C" fn init() {} + +/// El fotograma voraz. Reclama 4096 paginas de memoria lineal de un golpe — +/// 256 MiB, muy por encima de cualquier techo razonable. El kernel `renaser` +/// denegara la expansion con una trampa y desalojara esta aplicacion. +#[no_mangle] +pub extern "C" fn tick() { + // `memory_grow` sobre la memoria 0. Con el techo espacial activo y la + // denegacion configurada como trampa, esta instruccion NO retorna: aborta. + let _ = core::arch::wasm32::memory_grow(0, 4096); + // Red de seguridad: si la denegacion no fuese una trampa, este bucle + // cerrado garantiza el desalojo —por combustible— de todos modos. + loop {} +} diff --git a/renaser/apps/hello_wasm/Cargo.lock b/renaser/apps/hello_wasm/Cargo.lock new file mode 100644 index 0000000..a55539b --- /dev/null +++ b/renaser/apps/hello_wasm/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "hello_wasm" +version = "0.1.0" diff --git a/renaser/apps/hello_wasm/Cargo.toml b/renaser/apps/hello_wasm/Cargo.toml new file mode 100644 index 0000000..716ef15 --- /dev/null +++ b/renaser/apps/hello_wasm/Cargo.toml @@ -0,0 +1,28 @@ +# ============================================================================= +# renaser :: apps/hello_wasm — primera aplicacion del userspace aislado +# ----------------------------------------------------------------------------- +# No es un ELF: es un modulo WebAssembly puro. Se compila para wasm32 y el +# kernel lo ejecuta dentro de wasmi, encerrado en su propia memoria lineal. +# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel. +# ============================================================================= + +[package] +name = "hello_wasm" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM de prueba — un cuadrado movil dirigido por teclado" + +[workspace] + +# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que +# wasmi instancia. La aplicacion solo habla con el kernel por funciones del host. +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/hello_wasm/src/lib.rs b/renaser/apps/hello_wasm/src/lib.rs new file mode 100644 index 0000000..90d661b --- /dev/null +++ b/renaser/apps/hello_wasm/src/lib.rs @@ -0,0 +1,140 @@ +// ============================================================================= +// renaser :: apps/hello_wasm — Fase 4/5 :: el primer ciudadano del userspace +// ----------------------------------------------------------------------------- +// Esta aplicacion vive DENTRO de su propia memoria lineal de WebAssembly. No +// conoce la MMU, no conoce los anillos de privilegio de la CPU: su unica via +// hacia el mundo son las dos capacidades que el kernel le inyecta. Lo que no +// este importado, sencillamente, no tiene camino fisico que recorrer. +// +// FASE 5 :: el ABI deja de ser un `run()` que se queda dentro para siempre. +// Ahora la app exporta `init()` —preparacion, una sola vez— y `tick()` —un +// fotograma de trabajo, y RETORNA—. Ese retorno es el punto de cesion +// cooperativa: el kernel recupera el control y atiende a las demas apps. +// ============================================================================= + +#![no_std] + +// --- Las dos UNICAS capacidades que el kernel `renaser` expone al modulo. --- +#[link(wasm_import_module = "renaser")] +extern "C" { + /// Compone un bufer de pixeles (de ESTA memoria lineal) en la region que el + /// kernel asigno a esta aplicacion. + fn sys_render_frame(ptr: u32, len: u32); + /// Devuelve el ultimo scancode crudo del teclado, o 0 si no hay ninguno. + fn sys_get_scancode() -> u32; +} + +/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco. +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +// --- Geometria de la escena. El ancho y el alto DEBEN coincidir con la region +// que el kernel asigna a esta app: el host rechaza cualquier fotograma de +// un tamaño que no sea, exactamente, el de su ventana. --- +const ANCHO: usize = 480; +const ALTO: usize = 560; +const LADO: usize = 96; +const PASO: i32 = 24; + +/// Azul nocturno: el fondo del lienzo de la aplicacion. +const FONDO: u32 = 0x0A_18_30; +/// Ambar: el cuadrado que el usuario gobierna. +const CUADRO: u32 = 0xFF_B0_00; + +/// El lienzo de la aplicacion, en SU propia memoria lineal. El kernel jamas lo +/// ve directamente: solo recibe el (ptr, len) que cada fotograma le entrega. +static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO]; + +/// Posicion del cuadrado. Vive entre fotogramas en la memoria lineal del modulo +/// — el estado persiste porque la instancia, en la Fase 5, ya no es efimera. +static mut POS_X: i32 = 0; +static mut POS_Y: i32 = 0; + +/// Preparacion: el kernel la invoca UNA sola vez, al cargar el modulo. Pinta el +/// fondo, centra el cuadrado y vuelca el primer fotograma. +#[no_mangle] +pub extern "C" fn init() { + // SEGURIDAD: durante `init` y `tick` esta es la unica via de acceso a + // LIENZO, y el kernel jamas reentra el modulo mientras una de ellas corre. + let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) }; + for pixel in lienzo.iter_mut() { + *pixel = FONDO; + } + + let x = (ANCHO / 2 - LADO / 2) as i32; + let y = (ALTO / 2 - LADO / 2) as i32; + rellenar(lienzo, x, y, CUADRO); + // SEGURIDAD: escritura de escalares `Copy`; no se crea referencia alguna. + unsafe { + POS_X = x; + POS_Y = y; + } + + volcar(lienzo); +} + +/// Un fotograma de trabajo: escucha el teclado, mueve el cuadrado, vuelca la +/// imagen y RETORNA. El retorno cede la CPU al kernel y a las apps vecinas. +#[no_mangle] +pub extern "C" fn tick() { + let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) }; + let (mut x, mut y) = unsafe { (POS_X, POS_Y) }; + + // 1. Escuchar al teclado a traves de la capacidad del host. + let (dx, dy) = match unsafe { sys_get_scancode() } { + 0x11 => (0, -PASO), // tecla W -> arriba + 0x1F => (0, PASO), // tecla S -> abajo + 0x1E => (-PASO, 0), // tecla A -> izquierda + 0x20 => (PASO, 0), // tecla D -> derecha + _ => (0, 0), + }; + + // 2. Borrar el cuadrado anterior repintando su hueco con el fondo. + rellenar(lienzo, x, y, FONDO); + + // 3. Moverlo, manteniendolo siempre dentro del lienzo. + x = (x + dx).clamp(0, (ANCHO - LADO) as i32); + y = (y + dy).clamp(0, (ALTO - LADO) as i32); + + // 4. Dibujar el cuadrado en su nueva posicion y guardar el estado. + rellenar(lienzo, x, y, CUADRO); + unsafe { + POS_X = x; + POS_Y = y; + } + + // 5. Volcar el fotograma: el host lo compondra dentro de nuestra region. + volcar(lienzo); +} + +/// Entrega el lienzo completo al kernel. El (ptr, len) apunta SIEMPRE dentro de +/// nuestra memoria lineal, y su tamaño es, exactamente, el de la region. +fn volcar(lienzo: &[u32]) { + // SEGURIDAD: `sys_render_frame` es una capacidad del host; el (ptr, len) + // describe nuestra propia memoria lineal y el host lo verifica sin piedad. + unsafe { + sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32); + } +} + +/// Rellena un cuadrado de lado `LADO`, con su esquina en (x, y), recortado con +/// firmeza a los limites del lienzo. +fn rellenar(lienzo: &mut [u32], x: i32, y: i32, color: u32) { + let x0 = x.max(0) as usize; + let y0 = y.max(0) as usize; + let x1 = (x0 + LADO).min(ANCHO); + let y1 = (y0 + LADO).min(ALTO); + + let mut fila = y0; + while fila < y1 { + let base = fila * ANCHO; + let mut col = x0; + while col < x1 { + lienzo[base + col] = color; + col += 1; + } + fila += 1; + } +} diff --git a/renaser/boot/Cargo.toml b/renaser/boot/Cargo.toml new file mode 100644 index 0000000..b8f6485 --- /dev/null +++ b/renaser/boot/Cargo.toml @@ -0,0 +1,27 @@ +# ============================================================================= +# renaser :: boot (Fase 1.5) — orquestador host-side de empaquetado y arranque +# ----------------------------------------------------------------------------- +# Este paquete se ejecuta en el ANFITRION (Artix Linux), nunca en bare-metal. +# Toma el ELF nativo del kernel, lo fusiona con el cargador UEFI y lanza QEMU. +# ============================================================================= + +[package] +name = "boot" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +description = "renaser :: constructor de imagen de disco UEFI y lanzador de QEMU" + +[dependencies] +# Constructor de la imagen de disco UEFI. Corre en el anfitrion, usa `std`. +bootloader.workspace = true + +# Dependencia de ARTEFACTO (RFC 3028). Cargo compila el kernel para +# `x86_64-unknown-none` —en aislamiento total de arquitectura— y nos inyecta la +# ruta de su ELF en la variable de entorno `CARGO_BIN_FILE_KERNEL_kernel`, +# accesible desde `main.rs` mediante el macro `env!`. +[dependencies.kernel] +path = "../kernel" +artifact = "bin" +target = "x86_64-unknown-none" diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs new file mode 100644 index 0000000..100a7c6 --- /dev/null +++ b/renaser/boot/src/main.rs @@ -0,0 +1,172 @@ +// ============================================================================= +// renaser :: boot/src/main.rs — Fase 1.5 :: el puente hacia el silicio +// ----------------------------------------------------------------------------- +// Un kernel bare-metal no nace solo: alguien debe fusionarlo con un cargador, +// sellarlo en una imagen de disco arrancable y entregarlo al hardware. Esa es +// la unica mision de este orquestador de ANFITRION. +// +// El flujo es deliberadamente lineal y sin ambiguedad: +// +// 1. Localizar el ELF nativo del kernel (lo inyecta la dep. de artefacto). +// 2. Fusionarlo con el cargador UEFI en una imagen de disco GPT. +// 3. Lanzar QEMU con esa imagen y el firmware OVMF. +// +// Cada paso que pueda fallar lo hace en voz alta, con un mensaje accionable: +// preferimos un error claro a un arranque silencioso hacia la nada. +// ============================================================================= + +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Ruta del ELF del kernel, ya compilado para `x86_64-unknown-none`. +/// +/// La dependencia de artefacto define esta variable de entorno en tiempo de +/// compilacion: cuando este binario de anfitrion existe, el kernel ya existe. +const KERNEL_ELF: &str = env!("CARGO_BIN_FILE_KERNEL_kernel"); + +/// Firmware UEFI OVMF tal como lo empaqueta Artix Linux (paquete `edk2-ovmf`). +/// Es la imagen combinada codigo+variables, apta para `-bios`. +const OVMF_POR_DEFECTO: &str = "/usr/share/edk2/x64/OVMF.4m.fd"; + +/// Nombre de la imagen de disco UEFI que renaser genera. +const NOMBRE_IMAGEN: &str = "renaser-uefi.img"; + +/// Ruta del disco de objetos del grafo persistente (Fase 6.1c). Relativa al +/// directorio de trabajo —la raiz del repo—, comun a `boot` y a QEMU. +const NOMBRE_DISCO: &str = "target/disk.img"; + +/// Tamaño del disco de objetos: 32 MiB. Se crea como fichero disperso. +const TAM_DISCO: u64 = 32 * 1024 * 1024; + +fn main() { + if let Err(fallo) = orquestar() { + // Un error de orquestacion se anuncia en rojo y aborta con codigo 1: + // ninguna falla del anfitrion debe disfrazarse de exito. + eprintln!("\x1b[1;31m[renaser/boot] fallo:\x1b[0m {fallo}"); + std::process::exit(1); + } +} + +/// Ejecuta, en orden, las tres operaciones de la Fase 1.5. +fn orquestar() -> Result<(), String> { + // --- 1. Localizar el artefacto del kernel. --- + let kernel = Path::new(KERNEL_ELF); + if !kernel.is_file() { + return Err(format!( + "no se encontro el ELF del kernel en {}\n \ + (¿se interrumpio la compilacion de la dependencia de artefacto?)", + kernel.display() + )); + } + println!("[renaser/boot] kernel localizado :: {}", kernel.display()); + + // --- 2. Fusionar kernel + cargador UEFI en una imagen de disco. --- + let imagen = ruta_imagen(kernel); + println!("[renaser/boot] forjando imagen UEFI :: {}", imagen.display()); + bootloader::UefiBoot::new(kernel) + .create_disk_image(&imagen) + .map_err(|e| format!("la crate `bootloader` no pudo crear la imagen UEFI: {e:?}"))?; + + // --- 3. Garantizar el disco de objetos del grafo persistente. --- + preparar_disco_objetos()?; + + // --- 4. Lanzar QEMU sobre esa imagen. --- + let ovmf = localizar_ovmf()?; + lanzar_qemu(&imagen, &ovmf) +} + +/// Garantiza la existencia del disco de objetos del grafo persistente. Si no +/// existe, lo forja como un fichero disperso de 32 MiB, ENTERAMENTE A CERO: el +/// kernel, al no hallar la firma de su superbloque, lo formateara como un grafo +/// virgen. Si ya existe, lo respeta — el grafo perdura entre arranques. +fn preparar_disco_objetos() -> Result<(), String> { + let disco = Path::new(NOMBRE_DISCO); + if disco.is_file() { + println!("[renaser/boot] disco de objetos presente :: {}", disco.display()); + return Ok(()); + } + if let Some(directorio) = disco.parent() { + std::fs::create_dir_all(directorio) + .map_err(|e| format!("no se pudo crear el directorio del disco de objetos: {e}"))?; + } + // Forjar el disco: un fichero disperso, a cero, de 32 MiB. El kernel + // escribira su superbloque la primera vez que lo monte. + let fichero = std::fs::File::create(disco) + .map_err(|e| format!("no se pudo crear el disco de objetos «{}»: {e}", disco.display()))?; + fichero + .set_len(TAM_DISCO) + .map_err(|e| format!("no se pudo dimensionar el disco de objetos: {e}"))?; + println!( + "[renaser/boot] disco de objetos forjado :: {} ({} MiB, virgen)", + disco.display(), + TAM_DISCO / (1024 * 1024) + ); + Ok(()) +} + +/// Calcula la ruta de la imagen: junto al propio ELF del kernel, es decir, +/// dentro de `target/`. Una ubicacion predecible y siempre escribible. +fn ruta_imagen(kernel: &Path) -> PathBuf { + kernel + .parent() + .unwrap_or_else(|| Path::new(".")) + .join(NOMBRE_IMAGEN) +} + +/// Resuelve la ruta del firmware OVMF. Permite sobreescribirla con la variable +/// de entorno `RENASER_OVMF` para entornos cuyo `edk2-ovmf` viva en otra ruta. +fn localizar_ovmf() -> Result { + let ruta = std::env::var("RENASER_OVMF").unwrap_or_else(|_| OVMF_POR_DEFECTO.to_string()); + if Path::new(&ruta).is_file() { + Ok(ruta) + } else { + Err(format!( + "firmware UEFI OVMF no encontrado en «{ruta}»\n \ + instala el paquete `edk2-ovmf`, o exporta RENASER_OVMF=" + )) + } +} + +/// Invoca QEMU como subproceso. Los argumentos se ciñen a las primitivas +/// minimas necesarias para que el Framebuffer GOP cobre vida: +/// +/// * `-bios` firmware UEFI OVMF. +/// * `-drive raw` la imagen de disco UEFI, sin capa de traduccion. +/// * `-vga std` VGA estandar => framebuffer lineal que el GOP expone. +/// * `-serial stdio` telemetria serial del procesador hacia esta consola. +/// * `--no-reboot` un fallo triple detiene la maquina en vez de reiniciar +/// en bucle: asi la baliza de panico permanece visible. +/// * `virtio-blk-pci` el disco de objetos, sobre el bus PCI (q35 es x86_64; +/// `virtio-blk-device`, su gemelo MMIO, es cosa de ARM). +fn lanzar_qemu(imagen: &Path, ovmf: &str) -> Result<(), String> { + println!("[renaser/boot] arrancando QEMU :: la superficie indigo nace ahora\n"); + + let mut qemu = Command::new("qemu-system-x86_64"); + // `accel=kvm:tcg` intenta KVM y, si no esta disponible, recae en TCG puro. + qemu.arg("-machine").arg("q35,accel=kvm:tcg") + .arg("-m").arg("256M") + .arg("-bios").arg(ovmf) + .arg("-drive").arg(format!("format=raw,file={}", imagen.display())) + .arg("-vga").arg("std") + .arg("-serial").arg("stdio") + .arg("--no-reboot") + // El disco de objetos, como dispositivo virtio-blk sobre el bus PCI. + .arg("-drive").arg(format!("format=raw,file={NOMBRE_DISCO},if=none,id=drv0")) + .arg("-device").arg("virtio-blk-pci,drive=drv0"); + + // Cualquier argumento extra tras `--` se reenvia a QEMU intacto. + // Ejemplo: `cargo run -p boot -- -display none -d int`. + qemu.args(std::env::args().skip(1)); + + match qemu.status() { + Ok(estado) if estado.success() => { + println!("\n[renaser/boot] QEMU finalizo limpiamente."); + Ok(()) + } + Ok(estado) => Err(format!("QEMU termino con estado anomalo: {estado}")), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err( + "`qemu-system-x86_64` no esta en el PATH; instala el paquete `qemu-full`".to_string(), + ), + Err(e) => Err(format!("no se pudo ejecutar QEMU: {e}")), + } +} diff --git a/renaser/kernel/Cargo.toml b/renaser/kernel/Cargo.toml new file mode 100644 index 0000000..3ea7bda --- /dev/null +++ b/renaser/kernel/Cargo.toml @@ -0,0 +1,68 @@ +# ============================================================================= +# renaser :: kernel — el corazon bare-metal que late en el espacio unico +# ----------------------------------------------------------------------------- +# Este paquete esta EXCLUIDO del espacio de trabajo (ver el Cargo.toml raiz): +# es codigo puramente bare-metal y solo se compila como dependencia de +# artefacto de `boot`, que le impone el target `x86_64-unknown-none`. Por eso +# fija sus versiones de forma explicita, sin herencia del workspace. +# ============================================================================= + +[package] +name = "kernel" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" +authors = ["JL Soltech "] +description = "renaser :: kernel asincrono SASOS — entrada, framebuffer y reactor" + +# El kernel es un binario freestanding: sin arnes de pruebas ni de benchmarks. +[[bin]] +name = "kernel" +path = "src/main.rs" +test = false +bench = false +doctest = false + +[dependencies] +# --- Fase 1-2 :: arranque, framebuffer e interrupciones --- +bootloader_api = "0.11" +x86_64 = "0.15" +embedded-graphics = "0.8" + +# --- Fase 3 :: heap dinamico, reactor asincrono y texto vectorial --- +linked_list_allocator = "0.10" +spin = "0.9" +crossbeam-queue = { version = "0.3", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3", default-features = false, features = ["alloc"] } +# `hashbrown` ACTIVA el modo `no_std` de fontdue; sin el recae en `std`. +fontdue = { version = "0.9", default-features = false, features = ["hashbrown"] } + +# --- Fase 4 :: interprete WebAssembly del userspace aislado --- +# `wasmi` bare-metal: sin `std`, sin `wat`, sin `simd` (el target no tiene SSE). +wasmi = { version = "1.0", default-features = false, features = ["hash-collections"] } + +# --- Fase 6 :: drivers de hardware — el disco virtio-blk sobre el bus PCI --- +# `virtio-drivers` bare-metal: el kernel implementa su `trait Hal` para el DMA. +virtio-drivers = { version = "0.13", default-features = false, features = ["alloc"] } + +# --- Fase 6.1c :: el grafo de objetos direccionado por contenido --- +# `serde` da el rasgo de (de)serializacion; `postcard` lo materializa en un +# formato binario compacto, pensado para sistemas empotrados — el que viaja al +# disco. Ambos `no_std`, apoyados en `alloc`. +serde = { version = "1", default-features = false, features = ["alloc", "derive"] } +postcard = { version = "1", default-features = false, features = ["alloc"] } +# `blake3`: la funcion hash que da identidad a cada objeto. Se fuerza la +# implementacion ESCALAR pura (`pure` + los cuatro `no_*`): el target del kernel +# corre sin SSE, y un camino SIMD activado por deteccion en tiempo de ejecucion +# ejecutaria instrucciones que la CPU, sin `CR4.OSFXSR`, rechazaria con un #UD. +blake3 = { version = "1", default-features = false, features = [ + "pure", "no_sse2", "no_sse41", "no_avx2", "no_avx512", +] } + +# --- Fase 8 (preparación) :: el compositor --- +# `mirada-layout` es el motor de teselado del compositor de brahman — +# geometría pura (rectángulos, foco, Z-order), `no_std`, sin smithay ni +# Wayland. Vive en el monorepo brahman, en su PROPIO workspace; renaser +# lo enlaza por `path` cruzando la frontera. La feature `serde` queda +# APAGADA: el kernel computa el layout en memoria, no lo serializa. +mirada-layout = { path = "../../crates/modules/mirada/mirada-layout" } diff --git a/renaser/kernel/assets/app.wasm b/renaser/kernel/assets/app.wasm new file mode 100755 index 0000000..3660612 Binary files /dev/null and b/renaser/kernel/assets/app.wasm differ diff --git a/renaser/kernel/assets/cronista.wasm b/renaser/kernel/assets/cronista.wasm new file mode 100755 index 0000000..3b06a01 Binary files /dev/null and b/renaser/kernel/assets/cronista.wasm differ diff --git a/renaser/kernel/assets/discola.wasm b/renaser/kernel/assets/discola.wasm new file mode 100755 index 0000000..f416e59 Binary files /dev/null and b/renaser/kernel/assets/discola.wasm differ diff --git a/renaser/kernel/assets/font.ttf b/renaser/kernel/assets/font.ttf new file mode 100644 index 0000000..7f95a67 Binary files /dev/null and b/renaser/kernel/assets/font.ttf differ diff --git a/renaser/kernel/assets/glotona.wasm b/renaser/kernel/assets/glotona.wasm new file mode 100755 index 0000000..aade2fc Binary files /dev/null and b/renaser/kernel/assets/glotona.wasm differ diff --git a/renaser/kernel/src/almacen.rs b/renaser/kernel/src/almacen.rs new file mode 100644 index 0000000..025ff00 --- /dev/null +++ b/renaser/kernel/src/almacen.rs @@ -0,0 +1,292 @@ +// ============================================================================= +// renaser :: kernel/src/almacen.rs — Fase 6.1c :: el grafo de objetos +// ----------------------------------------------------------------------------- +// renaser rompe con POSIX tambien en el almacenamiento: aqui no hay un sistema +// de archivos plano —rutas, directorios, inodos—. Hay un GRAFO DIRIGIDO ACICLICO +// de objetos DIRECCIONADOS POR CONTENIDO. +// +// Un objeto es una carga util de bytes y una lista de aristas hacia otros +// objetos. Su IDENTIDAD no es un nombre ni un numero: es el hash BLAKE3 de su +// forma serializada. De ello se siguen dos propiedades que un FS jamas regala: +// +// * INTEGRIDAD — el hash verifica el contenido; un objeto corrupto se delata. +// * DEDUPLICACION — contenido identico produce hash identico; se almacena +// una sola vez, aunque mil aristas apunten a el. +// +// El disco se organiza como un LOG: el sector 0 es el superbloque —el ancla +// del grafo—, y tras el se anexan los registros de objetos, uno tras otro. Un +// indice en memoria (hash -> sector) se reconstruye al arrancar recorriendo el +// log. La serializacion la hace `postcard`: binaria, compacta, determinista. +// ============================================================================= + +use alloc::collections::BTreeMap; +use alloc::vec; +use alloc::vec::Vec; + +use serde::{Deserialize, Serialize}; +use spin::{Mutex, Once}; + +use crate::drivers::disco::{self, TAM_SECTOR}; + +/// Firma magica del superbloque — «RENASer GRaFo». Distingue un disco de +/// renaser de uno virgen o ajeno. +const MAGIA: [u8; 8] = *b"RENASGRF"; + +/// Version del formato en disco. Un disco con otra version se reformatea. +const VERSION: u32 = 1; + +/// Techo del tamaño de un objeto serializado: 1 MiB. Acota los buferes de E/S +/// y permite descartar un registro corrupto sin intentar leer un disparate. +const MAX_OBJETO: usize = 1024 * 1024; + +/// El identificador de un objeto: el hash BLAKE3 de su forma serializada. En un +/// almacen direccionado por contenido, la identidad ES el contenido. +pub type Hash = [u8; 32]; + +/// Un objeto del grafo: una carga util opaca y las aristas que lo enlazan con +/// otros objetos. Los `hijos` hacen del almacen un DAG —no un arbol, no una +/// lista—: un objeto puede ser hijo de muchos, y el direccionamiento por +/// contenido garantiza que cada contenido distinto se guarda una sola vez. +#[derive(Serialize, Deserialize, Clone)] +pub struct Objeto { + /// La carga util del objeto: bytes crudos, que el kernel no interpreta. + pub datos: Vec, + /// Los hashes de los objetos hijos: las aristas salientes del DAG. + pub hijos: Vec, +} + +/// El superbloque: el sector 0 del disco. Ancla el grafo entero — dice por +/// donde continua el log y cual es el objeto raiz. +#[derive(Serialize, Deserialize)] +struct SuperBloque { + /// Firma magica: debe ser [`MAGIA`]. + magia: [u8; 8], + /// Version del formato: debe ser [`VERSION`]. + version: u32, + /// Proximo sector libre del log — donde se anexara el siguiente objeto. + cursor: u64, + /// El objeto raiz del DAG: el punto de entrada que el userspace fija y lee. + raiz: Option, +} + +/// El estado vivo del almacen: el cursor del log, la raiz y el indice en +/// memoria que traduce cada hash al sector donde habita su registro. +struct Almacen { + /// Proximo sector libre del log. + cursor: u64, + /// El objeto raiz del DAG. + raiz: Option, + /// Indice hash -> sector del registro. Se reconstruye al arrancar. + indice: BTreeMap, + /// Capacidad del disco, en sectores. + capacidad: u64, +} + +/// El almacen global de renaser. Se funde una sola vez, en `init`. +static ALMACEN: Once> = Once::new(); + +/// El fruto de fundar el almacen — para que el arranque deje constancia visual. +pub struct Resumen { + /// Capacidad del disco, en sectores. + pub capacidad: u64, + /// Numero de objetos hallados en el grafo. + pub objetos: usize, + /// ¿Tiene el grafo un objeto raiz? + pub raiz: bool, + /// ¿Se reformateo el disco (estaba virgen o era ajeno)? + pub formateado: bool, +} + +/// Numero de sectores que ocupa un registro cuyo payload mide `longitud` bytes. +/// Cada registro en disco es: `[longitud: u32 LE][payload postcard][relleno 0]`. +fn sectores_registro(longitud: usize) -> u64 { + (4 + longitud).div_ceil(TAM_SECTOR) as u64 +} + +/// Funda el almacen de objetos: monta el disco, lee el superbloque y, si el +/// disco ya es de renaser, reconstruye el indice recorriendo el log; si es +/// virgen o ajeno, lo formatea. Toda falla se devuelve como `Err`. +pub fn init() -> Result { + let capacidad = disco::montar()?; + if capacidad < 2 { + return Err("el disco es demasiado pequeño para un grafo"); + } + + // Leer el sector 0 e intentar interpretarlo como superbloque de renaser. + let mut sector0 = [0u8; TAM_SECTOR]; + disco::leer_sectores(0, &mut sector0)?; + + let (cursor, raiz, indice, formateado) = + match postcard::take_from_bytes::(§or0) { + // Disco de renaser, con la version corriente: adoptar su grafo. + Ok((sb, _)) if sb.magia == MAGIA && sb.version == VERSION => { + let indice = reconstruir_indice(sb.cursor)?; + (sb.cursor, sb.raiz, indice, false) + } + // Disco virgen, ajeno o de otra version: empezar de cero. El log + // arranca en el sector 1, justo despues del superbloque. + _ => (1, None, BTreeMap::new(), true), + }; + + let objetos = indice.len(); + let tiene_raiz = raiz.is_some(); + let almacen = Almacen { + cursor, + raiz, + indice, + capacidad, + }; + + // Un disco recien formateado necesita su superbloque grabado de inmediato. + if formateado { + persistir(&almacen)?; + } + ALMACEN.call_once(|| Mutex::new(almacen)); + + Ok(Resumen { + capacidad, + objetos, + raiz: tiene_raiz, + formateado, + }) +} + +/// Recorre el log —del sector 1 al `cursor`— y reconstruye el indice +/// hash -> sector. Cada registro se rehashea: el indice se reconstruye, no se +/// confia. Un registro corrupto detiene el escaneo sin incendiar nada. +fn reconstruir_indice(cursor: u64) -> Result, &'static str> { + let mut indice = BTreeMap::new(); + let mut sector: u64 = 1; + while sector < cursor { + let payload = leer_registro(sector)?; + match payload { + // Un payload valido: hashearlo e indexarlo. + Some(payload) => { + let n = sectores_registro(payload.len()); + let hash = *blake3::hash(&payload).as_bytes(); + indice.insert(hash, sector); + sector += n; + } + // Cabecera a cero o longitud imposible: fin (o corrupcion) del log. + None => break, + } + } + Ok(indice) +} + +/// Lee el registro que arranca en `sector` y devuelve su payload postcard +/// (sin la cabecera de longitud ni el relleno). `None` si la cabecera dice +/// longitud cero —fin del log— o una longitud imposible —corrupcion—. +fn leer_registro(sector: u64) -> Result>, &'static str> { + let mut cabecera = [0u8; TAM_SECTOR]; + disco::leer_sectores(sector, &mut cabecera)?; + let longitud = + u32::from_le_bytes([cabecera[0], cabecera[1], cabecera[2], cabecera[3]]) as usize; + if longitud == 0 || longitud > MAX_OBJETO { + return Ok(None); + } + let n = sectores_registro(longitud) as usize; + // Si el registro cabe en el sector ya leido, evitar una segunda lectura. + let payload = if n == 1 { + cabecera[4..4 + longitud].to_vec() + } else { + let mut buf = vec![0u8; n * TAM_SECTOR]; + disco::leer_sectores(sector, &mut buf)?; + buf[4..4 + longitud].to_vec() + }; + Ok(Some(payload)) +} + +/// Graba el superbloque —el ancla del grafo— en el sector 0. +fn persistir(almacen: &Almacen) -> Result<(), &'static str> { + let sb = SuperBloque { + magia: MAGIA, + version: VERSION, + cursor: almacen.cursor, + raiz: almacen.raiz, + }; + let bytes = postcard::to_allocvec(&sb).map_err(|_| "no se pudo serializar el superbloque")?; + if bytes.len() > TAM_SECTOR { + return Err("el superbloque no cabe en un sector"); + } + let mut sector0 = [0u8; TAM_SECTOR]; + sector0[..bytes.len()].copy_from_slice(&bytes); + disco::escribir_sectores(0, §or0) +} + +/// Almacena un objeto y devuelve su hash. Direccionamiento por contenido en +/// estado puro: si un objeto de contenido identico ya existe, NO se reescribe — +/// se devuelve el hash que ya tenia. El grafo nunca guarda dos veces lo mismo. +pub fn almacenar(datos: Vec, hijos: Vec) -> Result { + let objeto = Objeto { datos, hijos }; + let bytes = + postcard::to_allocvec(&objeto).map_err(|_| "no se pudo serializar el objeto")?; + if bytes.is_empty() || bytes.len() > MAX_OBJETO { + return Err("el objeto tiene un tamaño invalido"); + } + // La identidad del objeto: el hash de su forma serializada. + let hash = *blake3::hash(&bytes).as_bytes(); + + let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; + let mut almacen = mutex.lock(); + + // ¿Ya esta en el grafo? Entonces no hay nada que grabar. + if almacen.indice.contains_key(&hash) { + return Ok(hash); + } + + // Reservar los sectores del registro al final del log. + let n = sectores_registro(bytes.len()); + if almacen.cursor + n > almacen.capacidad { + return Err("el grafo de objetos esta lleno"); + } + let sector = almacen.cursor; + + // Componer el registro: [longitud][payload][relleno a cero] y grabarlo. + let mut registro = vec![0u8; n as usize * TAM_SECTOR]; + registro[0..4].copy_from_slice(&(bytes.len() as u32).to_le_bytes()); + registro[4..4 + bytes.len()].copy_from_slice(&bytes); + disco::escribir_sectores(sector, ®istro)?; + + // El objeto ya esta en disco: avanzar el cursor, indexarlo y RE-anclar el + // superbloque. El orden importa — el superbloque se graba el ultimo, de + // modo que jamas apunte a un registro a medio escribir. + almacen.cursor += n; + almacen.indice.insert(hash, sector); + persistir(&almacen)?; + + Ok(hash) +} + +/// Recupera un objeto por su hash. `Ok(None)` si el hash no esta en el grafo. +pub fn recuperar(hash: &Hash) -> Result, &'static str> { + let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; + // Soltar el cerrojo del almacen ANTES de la E/S de disco —lenta, por + // sondeo—: el indice ya entrego el sector, y nada mas reclama el cerrojo. + let sector = match mutex.lock().indice.get(hash) { + Some(&s) => s, + None => return Ok(None), + }; + let payload = leer_registro(sector)?.ok_or("registro de objeto corrupto")?; + // Verificacion de integridad: el contenido leido DEBE rehashear al hash + // pedido. Si no, el disco ha mentido — y se delata. + if *blake3::hash(&payload).as_bytes() != *hash { + return Err("el objeto no supero la verificacion de integridad"); + } + let (objeto, _) = postcard::take_from_bytes::(&payload) + .map_err(|_| "no se pudo deserializar el objeto")?; + Ok(Some(objeto)) +} + +/// El hash del objeto raiz del grafo, si lo hay. +pub fn raiz() -> Option { + ALMACEN.get().and_then(|mutex| mutex.lock().raiz) +} + +/// Corona un objeto como raiz del grafo y ancla el cambio en el superbloque. +pub fn fijar_raiz(hash: Hash) -> Result<(), &'static str> { + let mutex = ALMACEN.get().ok_or("almacen no inicializado")?; + let mut almacen = mutex.lock(); + almacen.raiz = Some(hash); + persistir(&almacen) +} diff --git a/renaser/kernel/src/async_system/executor.rs b/renaser/kernel/src/async_system/executor.rs new file mode 100644 index 0000000..f6c3dbb --- /dev/null +++ b/renaser/kernel/src/async_system/executor.rs @@ -0,0 +1,103 @@ +// ============================================================================= +// renaser :: async_system/executor.rs — Fase 3 :: el reactor cooperativo +// ----------------------------------------------------------------------------- +// El ejecutor es el corazon reactivo de renaser. Mantiene el censo de tareas +// vivas y una cola de las que estan listas para avanzar. Cuando no queda nada +// por hacer, no malgasta la CPU en un bucle ocupado: la duerme con `hlt` +// hasta que el proximo impulso de hardware la despierte. +// ============================================================================= + +use alloc::collections::{BTreeMap, VecDeque}; +use alloc::sync::Arc; +use core::future::Future; +use core::task::{Context, Poll, Waker}; + +use spin::Mutex; +use x86_64::instructions::interrupts; + +use super::task::{Task, TaskId}; +use super::waker::{self, ColaListas}; + +/// El ejecutor cooperativo de renaser. +pub struct Executor { + /// Censo de todas las tareas vivas, indexadas por su identidad. + tareas: BTreeMap, + /// Cola de tareas listas para su proximo avance. + cola_listas: ColaListas, + /// Cache de wakers: uno por tarea, reutilizado entre avances. + cache_wakers: BTreeMap, +} + +impl Executor { + /// Crea un ejecutor vacio. Requiere que el heap ya este fundado. + pub fn nuevo() -> Executor { + Executor { + tareas: BTreeMap::new(), + cola_listas: Arc::new(Mutex::new(VecDeque::new())), + cache_wakers: BTreeMap::new(), + } + } + + /// Da de alta una tarea nueva y la marca como lista para su primer avance. + pub fn spawn(&mut self, futuro: impl Future + Send + 'static) { + let tarea = Task::nueva(futuro); + let id = tarea.id; + if self.tareas.insert(id, tarea).is_some() { + panic!("renaser :: TaskId duplicado — lo imposible ha ocurrido"); + } + interrupts::without_interrupts(|| self.cola_listas.lock().push_back(id)); + } + + /// Avanza, hasta agotarla, la cola de tareas listas. + fn avanzar_listas(&mut self) { + loop { + // Sacar el siguiente id con las interrupciones acalladas: la cola + // es compartida con los wakers que se disparan desde las IRQ. + let siguiente = interrupts::without_interrupts(|| self.cola_listas.lock().pop_front()); + let id = match siguiente { + Some(id) => id, + None => return, + }; + let tarea = match self.tareas.get_mut(&id) { + Some(t) => t, + None => continue, // la tarea concluyo en una vuelta previa + }; + // Un waker por tarea, reutilizado: reinyecta esta tarea en la cola. + let cola = self.cola_listas.clone(); + let despertador = self + .cache_wakers + .entry(id) + .or_insert_with(|| waker::crear(id, cola)); + let mut contexto = Context::from_waker(despertador); + if let Poll::Ready(()) = tarea.poll(&mut contexto) { + // La tarea termino: se retira del censo y se libera su waker. + self.tareas.remove(&id); + self.cache_wakers.remove(&id); + } + } + } + + /// Si no queda trabajo, duerme la CPU hasta la proxima interrupcion. El + /// chequeo y el `hlt` se hacen con las interrupciones acalladas para que + /// ningun despertar se pierda en la rendija entre uno y otro. + fn dormir_si_inactivo(&self) { + interrupts::disable(); + let hay_trabajo = !self.cola_listas.lock().is_empty(); + if hay_trabajo { + // Llego trabajo justo ahora: reactivar y seguir sin dormir. + interrupts::enable(); + } else { + // `sti; hlt` atomico: habilita y duerme sin condicion de carrera. + interrupts::enable_and_hlt(); + } + } + + /// Cede el hilo principal al reactor. No retorna jamas: desde aqui, renaser + /// vive del latir de sus interrupciones. + pub fn run(&mut self) -> ! { + loop { + self.avanzar_listas(); + self.dormir_si_inactivo(); + } + } +} diff --git a/renaser/kernel/src/async_system/mod.rs b/renaser/kernel/src/async_system/mod.rs new file mode 100644 index 0000000..fcf0569 --- /dev/null +++ b/renaser/kernel/src/async_system/mod.rs @@ -0,0 +1,16 @@ +// ============================================================================= +// renaser :: kernel/src/async_system — el reactor cooperativo (Fase 3 / 5) +// ----------------------------------------------------------------------------- +// Aqui renaser rompe con el modelo de hilos pesados de Linux. No hay cambio +// de contexto en la CPU: las interrupciones de hardware no conmutan pilas, +// DESPIERTAN tareas. El kernel avanza cooperativamente, una tarea cede y la +// siguiente toma el relevo. Sobre estos cimientos corre, desde la Fase 5, el +// bytecode WASM aislado por software de la Fase 4: cada aplicacion es una +// tarea mas, y el `reloj` le marca el compas de sus fotogramas. +// ============================================================================= + +pub mod executor; +pub mod reloj; +pub mod task; +pub mod teclado; +pub mod waker; diff --git a/renaser/kernel/src/async_system/reloj.rs b/renaser/kernel/src/async_system/reloj.rs new file mode 100644 index 0000000..00320ab --- /dev/null +++ b/renaser/kernel/src/async_system/reloj.rs @@ -0,0 +1,101 @@ +// ============================================================================= +// renaser :: async_system/reloj.rs — Fase 5 :: el compas de los fotogramas +// ----------------------------------------------------------------------------- +// El temporizador (PIT, IRQ0) ya no solo despierta al ejecutor de su `hlt`: +// ahora marca el COMPAS del userspace. Cada pulso a 100 Hz es un fotograma — +// una oportunidad de cesion cooperativa. `EsperaFrame` convierte ese pulso de +// hardware en un `Future`: una tarea WASM hace su trabajo de un fotograma y +// `.await`-ea el siguiente, cediendo la CPU a sus vecinas mientras tanto. +// ============================================================================= + +use alloc::vec::Vec; +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{AtomicU64, Ordering}; +use core::task::{Context, Poll, Waker}; + +use spin::{Mutex, Once}; +use x86_64::instructions::interrupts; + +/// Pulsos del temporizador acumulados desde el arranque. Lo incrementa la IRQ0; +/// lo consulta `EsperaFrame`. Atomico: la IRQ y el hilo principal lo comparten. +static CONTADOR_PULSOS: AtomicU64 = AtomicU64::new(0); + +/// Censo de wakers en espera del proximo fotograma. Vive en el heap —de ahi el +/// `Once`—; tras su `Mutex`, lo disputan el lado tarea y el manejador de IRQ0. +static EN_ESPERA: Once>> = Once::new(); + +/// Funda el censo de wakers del reloj. Requiere el heap ya activo; debe +/// invocarse una sola vez, antes de habilitar las interrupciones. +pub fn init() { + EN_ESPERA.call_once(|| Mutex::new(Vec::new())); +} + +/// Punto de entrada DESDE el manejador de IRQ0. Avanza el contador de pulsos y +/// despierta a cuantas tareas aguardaban el fotograma. Deliberadamente breve y +/// libre de panicos: corre en contexto de interrupcion. +pub fn pulso() { + // `Release`: el avance del contador se publica ANTES de que los wakers + // reinyecten sus tareas; el `poll` que sigue lo leera con `Acquire` y vera, + // garantizado, el valor nuevo. + CONTADOR_PULSOS.fetch_add(1, Ordering::Release); + if let Some(censo) = EN_ESPERA.get() { + // En contexto de IRQ las interrupciones ya estan acalladas; tomar aqui + // el cerrojo no puede interbloquear, pues el lado tarea siempre lo toma + // con las interrupciones desactivadas (ver `inscribir`). + for waker in censo.lock().drain(..) { + waker.wake(); + } + } +} + +/// Numero de pulsos del temporizador desde el arranque. +fn pulsos() -> u64 { + CONTADOR_PULSOS.load(Ordering::Acquire) +} + +/// Inscribe un waker en el censo de espera del proximo fotograma. +fn inscribir(waker: Waker) { + if let Some(censo) = EN_ESPERA.get() { + // El cerrojo lo disputa el manejador de IRQ0: tomarlo con las + // interrupciones acalladas hace IMPOSIBLE el interbloqueo. + interrupts::without_interrupts(|| censo.lock().push(waker)); + } +} + +/// Un `Future` que se resuelve en el proximo pulso del temporizador. Es la +/// unidad de cesion cooperativa del userspace: una tarea WASM hace su trabajo +/// de un fotograma y `.await`-ea un `EsperaFrame` para ceder hasta el siguiente. +pub struct EsperaFrame { + /// Pulso a partir del cual la espera se da por cumplida. + objetivo: u64, +} + +impl EsperaFrame { + /// Crea una espera que se resolvera en el siguiente pulso del temporizador. + pub fn nueva() -> EsperaFrame { + EsperaFrame { + objetivo: pulsos() + 1, + } + } +} + +impl Future for EsperaFrame { + type Output = (); + + fn poll(self: Pin<&mut Self>, contexto: &mut Context<'_>) -> Poll<()> { + // Lectura optimista: si el fotograma ya llego, no hay nada que esperar. + if pulsos() >= self.objetivo { + return Poll::Ready(()); + } + // Aun no: inscribir el waker y RE-VERIFICAR. Si un pulso se colo entre + // la lectura de arriba y la inscripcion, este segundo chequeo lo atrapa; + // sin el, el despertar se perderia hasta el pulso siguiente. + inscribir(contexto.waker().clone()); + if pulsos() >= self.objetivo { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} diff --git a/renaser/kernel/src/async_system/task.rs b/renaser/kernel/src/async_system/task.rs new file mode 100644 index 0000000..5a4b008 --- /dev/null +++ b/renaser/kernel/src/async_system/task.rs @@ -0,0 +1,45 @@ +// ============================================================================= +// renaser :: async_system/task.rs — Fase 3 :: la unidad de trabajo asincrono +// ============================================================================= + +use alloc::boxed::Box; +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{AtomicU64, Ordering}; +use core::task::{Context, Poll}; + +/// Identificador unico de una tarea. Autoincremental y atomico: dos tareas +/// jamas comparten identidad. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TaskId(u64); + +impl TaskId { + /// Acuña un identificador nuevo, distinto de todos los anteriores. + fn nuevo() -> TaskId { + static SIGUIENTE: AtomicU64 = AtomicU64::new(0); + TaskId(SIGUIENTE.fetch_add(1, Ordering::Relaxed)) + } +} + +/// Una tarea asincrona: un `Future` encapsulado, anclado (`Pin`) en el heap +/// para que su direccion nunca cambie mientras avanza. +pub struct Task { + /// La identidad de la tarea, su nombre ante el ejecutor. + pub id: TaskId, + futuro: Pin + Send + 'static>>, +} + +impl Task { + /// Envuelve un `Future` en una tarea con identidad propia. + pub fn nueva(futuro: impl Future + Send + 'static) -> Task { + Task { + id: TaskId::nuevo(), + futuro: Box::pin(futuro), + } + } + + /// Hace avanzar la tarea un paso. `Poll::Ready` significa que concluyo. + pub fn poll(&mut self, contexto: &mut Context) -> Poll<()> { + self.futuro.as_mut().poll(contexto) + } +} diff --git a/renaser/kernel/src/async_system/teclado.rs b/renaser/kernel/src/async_system/teclado.rs new file mode 100644 index 0000000..707d441 --- /dev/null +++ b/renaser/kernel/src/async_system/teclado.rs @@ -0,0 +1,76 @@ +// ============================================================================= +// renaser :: async_system/teclado.rs — el canal de scancodes del teclado +// ----------------------------------------------------------------------------- +// El manejador de IRQ1 es un mero PRODUCTOR: deposita cada scancode en colas +// lock-free, seguras frente a interrupciones. Los consumidores —las apps WASM, +// via la capacidad `sys_get_scancode`— las drenan sin bloquear. +// +// FASE 5 :: con varias apps concurrentes, una sola cola compartida no sirve: +// la primera en sondear le robaria la pulsacion a las demas. Por eso cada +// aplicacion abre su PROPIO canal y la IRQ1 DIFUNDE cada scancode a todos — +// cada app recibe su copia integra del flujo de entrada. +// ============================================================================= + +use alloc::sync::Arc; +use alloc::vec::Vec; + +use crossbeam_queue::ArrayQueue; +use spin::{Mutex, Once}; +use x86_64::instructions::interrupts; + +/// Capacidad de la cola de scancodes de cada app. Holgada: nadie teclea tanto. +const CAPACIDAD_COLA: usize = 256; + +/// Un canal de teclado: la cola lock-free de scancodes de UNA aplicacion. +pub type CanalTeclado = Arc>; + +/// Censo de canales — uno por aplicacion del userspace. El manejador de IRQ1 +/// difunde cada scancode a TODOS: asi cada app recibe su propia copia del +/// evento, sin que una le arrebate la pulsacion a otra. +static CANALES: Once>> = Once::new(); + +/// Funda el censo de canales del teclado. Requiere el heap ya activo; debe +/// invocarse una sola vez, antes de habilitar las interrupciones. +pub fn init() { + CANALES.call_once(|| Mutex::new(Vec::new())); +} + +/// Crea un canal de teclado nuevo, AUN sin inscribir en la difusion. Cada +/// aplicacion reclama el suyo al empezar a cargarse. +pub fn crear_canal() -> CanalTeclado { + Arc::new(ArrayQueue::new(CAPACIDAD_COLA)) +} + +/// Inscribe un canal en el censo de difusion. Desde este instante, la IRQ1 +/// empuja cada scancode tambien a este canal. Se invoca al final de la carga +/// de una app: una carga fallida no debe dejar canales huerfanos. +pub fn registrar_canal(canal: &CanalTeclado) { + if let Some(censo) = CANALES.get() { + // El cerrojo lo disputa el manejador de IRQ1: tomarlo con las + // interrupciones acalladas hace imposible el interbloqueo. + interrupts::without_interrupts(|| censo.lock().push(canal.clone())); + } +} + +/// Da de baja un canal del censo de difusion. Lo invoca el `Drop` de una +/// aplicacion desalojada: la IRQ1 deja, de inmediato, de empujarle scancodes. +pub fn cerrar_canal(canal: &CanalTeclado) { + if let Some(censo) = CANALES.get() { + interrupts::without_interrupts(|| { + censo.lock().retain(|inscrito| !Arc::ptr_eq(inscrito, canal)); + }); + } +} + +/// Punto de entrada DESDE el manejador de IRQ1. DIFUNDE el scancode a cuantos +/// canales haya abiertos. Deliberadamente breve y libre de panicos: corre en +/// contexto de interrupcion. +pub fn recibir_scancode(scancode: u8) { + if let Some(censo) = CANALES.get() { + for canal in censo.lock().iter() { + // Si un canal desborda, se descarta el scancode en silencio: mas + // vale perder una tecla que colapsar dentro de una interrupcion. + let _ = canal.push(scancode); + } + } +} diff --git a/renaser/kernel/src/async_system/waker.rs b/renaser/kernel/src/async_system/waker.rs new file mode 100644 index 0000000..aab5449 --- /dev/null +++ b/renaser/kernel/src/async_system/waker.rs @@ -0,0 +1,55 @@ +// ============================================================================= +// renaser :: async_system/waker.rs — Fase 3 :: el despertador de tareas +// ----------------------------------------------------------------------------- +// Un `Waker` es la promesa de que una tarea dormida volvera a ejecutarse. El +// nuestro es minimo: al invocarse, reinyecta el `TaskId` de su tarea en la +// cola de listas del ejecutor. Quien lo invoque —incluido un manejador de +// IRQ— vuelve a poner esa tarea en circulacion. +// ============================================================================= + +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use alloc::task::Wake; +use core::task::Waker; + +use spin::Mutex; + +use super::task::TaskId; + +/// La cola de tareas listas para avanzar, compartida entre el ejecutor y todos +/// los wakers que ha repartido. +pub type ColaListas = Arc>>; + +/// El despertador de UNA tarea concreta. +struct WakerTarea { + id: TaskId, + cola: ColaListas, +} + +impl WakerTarea { + /// Reinyecta la tarea en la cola de ejecucion. + fn reinyectar(&self) { + // El spinlock de la cola lo tocan tanto el hilo principal como los + // manejadores de IRQ. Tomarlo con las interrupciones acalladas hace + // IMPOSIBLE el interbloqueo: ninguna IRQ puede interrumpir al hilo + // principal justo mientras este sostiene el cerrojo. + x86_64::instructions::interrupts::without_interrupts(|| { + self.cola.lock().push_back(self.id); + }); + } +} + +impl Wake for WakerTarea { + fn wake(self: Arc) { + self.reinyectar(); + } + + fn wake_by_ref(self: &Arc) { + self.reinyectar(); + } +} + +/// Crea un `Waker` estandar que, al invocarse, reinyecta `id` en `cola`. +pub fn crear(id: TaskId, cola: ColaListas) -> Waker { + Waker::from(Arc::new(WakerTarea { id, cola })) +} diff --git a/renaser/kernel/src/baliza.rs b/renaser/kernel/src/baliza.rs new file mode 100644 index 0000000..01b841e --- /dev/null +++ b/renaser/kernel/src/baliza.rs @@ -0,0 +1,128 @@ +// ============================================================================= +// renaser :: kernel/src/baliza.rs — la red de seguridad visual del sistema +// ----------------------------------------------------------------------------- +// Sin consola de texto que valga cuando el sistema cae: si renaser colapsa, lo +// DIBUJA. La baliza publica —de forma atomica y sin cerrojos— los datos +// minimos del framebuffer, de modo que los manejadores de fallo puedan pintar +// una franja de advertencia incluso cuando el resto del kernel ya no es fiable. +// ============================================================================= + +use core::panic::PanicInfo; +use core::ptr; +use core::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering}; + +use crate::grafico::{escribir_pixel_volatil, Pantalla}; + +/// Datos del framebuffer expuestos a los manejadores de fallo. Todo son +/// atomicos: la baliza es intrinsecamente `Sync`, sin cerrojos. +pub(crate) struct BalizaPanico { + base: AtomicPtr, + paso_bytes: AtomicUsize, + ancho: AtomicUsize, + alto: AtomicUsize, + bytes_por_pixel: AtomicUsize, + /// Rojo de alerta de colapso, ya codificado al formato de la pantalla. + pixel_alerta: AtomicU32, + /// Naranja de agotamiento de memoria, ya codificado. + pixel_oom: AtomicU32, +} + +impl BalizaPanico { + /// Baliza apagada: sin pantalla publicada todavia. + const fn apagada() -> BalizaPanico { + BalizaPanico { + base: AtomicPtr::new(ptr::null_mut()), + paso_bytes: AtomicUsize::new(0), + ancho: AtomicUsize::new(0), + alto: AtomicUsize::new(0), + bytes_por_pixel: AtomicUsize::new(0), + pixel_alerta: AtomicU32::new(0), + pixel_oom: AtomicU32::new(0), + } + } + + /// Enciende la baliza con los datos de una pantalla viva. + pub(crate) fn encender(&self, pantalla: &Pantalla, pixel_alerta: u32, pixel_oom: u32) { + self.paso_bytes.store(pantalla.paso_bytes, Ordering::Relaxed); + self.ancho.store(pantalla.ancho, Ordering::Relaxed); + self.alto.store(pantalla.alto, Ordering::Relaxed); + self.bytes_por_pixel + .store(pantalla.bytes_por_pixel, Ordering::Relaxed); + self.pixel_alerta.store(pixel_alerta, Ordering::Relaxed); + self.pixel_oom.store(pixel_oom, Ordering::Relaxed); + // La base se publica de ultima, con semantica `Release`. + self.base.store(pantalla.base, Ordering::Release); + } + + /// Pinta, directa y volatilmente, una banda horizontal sobre el framebuffer + /// fisico. Es la herramienta de los manejadores de fallo: no confia ni en + /// el lienzo, ni en el heap, ni en estructura dinamica alguna. + fn pintar_banda(&self, y0: usize, altura: usize, pixel: u32) { + let base = self.base.load(Ordering::Acquire); + if base.is_null() { + return; + } + let ancho = self.ancho.load(Ordering::Relaxed); + let alto = self.alto.load(Ordering::Relaxed); + let paso = self.paso_bytes.load(Ordering::Relaxed); + let bpp = self.bytes_por_pixel.load(Ordering::Relaxed); + + let y_fin = (y0 + altura).min(alto); + let mut y = y0.min(alto); + while y < y_fin { + let fila = y * paso; + let mut x = 0; + while x < ancho { + // SEGURIDAD: (x, y) esta acotado por las dimensiones que la + // baliza publico desde una pantalla real. + unsafe { + escribir_pixel_volatil(base.add(fila + x * bpp), pixel, bpp); + } + x += 1; + } + y += 1; + } + } + + /// Altura de la franja de advertencia: ~8 % de la pantalla. + fn franja(&self) -> usize { + let alto = self.alto.load(Ordering::Relaxed); + if alto == 0 { + 0 + } else { + (alto / 12).max(1) + } + } +} + +/// Instancia global de la baliza. Comienza apagada y se enciende en el arranque. +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. +#[panic_handler] +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), + ); + crate::detener() +} + +/// Si el heap se agota, tatuamos una franja NARANJA: un fallo distinto al +/// colapso, y distinguible de un vistazo. +#[alloc_error_handler] +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), + ); + crate::detener() +} diff --git a/renaser/kernel/src/consola.rs b/renaser/kernel/src/consola.rs new file mode 100644 index 0000000..68796a6 --- /dev/null +++ b/renaser/kernel/src/consola.rs @@ -0,0 +1,186 @@ +// ============================================================================= +// renaser :: kernel/src/consola.rs — la superficie de texto e imagen +// ----------------------------------------------------------------------------- +// La consola une el lienzo intermedio, la pantalla fisica y una pluma de +// escritura. Rasteriza cada glifo con `fontdue` al vuelo —el texto convertido, +// por fin, en dibujo— y tambien sabe volcar fotogramas crudos del userspace +// WASM. Es global y serializada tras un `Mutex`: las tareas escriben en ella. +// ============================================================================= + +use spin::{Mutex, Once}; + +use crate::grafico::{codificar, Color, Lienzo, Pantalla, RegionPantalla}; +use crate::texto; + +/// Margen del texto respecto al borde del lienzo, en pixeles. +const MARGEN: usize = 40; +/// Tamaño de la tipografia, en pixeles. +const TAM_FUENTE: f32 = 30.0; +/// Altura de avance de una linea de texto, en pixeles. +const ALTO_LINEA: usize = 40; +/// Posicion vertical de la primera linea base. +const BASE_INICIAL: usize = MARGEN + 30; + +/// Interpola dos colores segun una cobertura: `0` => `fondo`, `255` => `tinta`. +fn mezclar(fondo: Color, tinta: Color, cobertura: u8) -> Color { + let canal = |a: u8, b: u8| -> u8 { + let c = cobertura as u16; + ((a as u16 * (255 - c) + b as u16 * c) / 255) as u8 + }; + Color { + r: canal(fondo.r, tinta.r), + g: canal(fondo.g, tinta.g), + b: canal(fondo.b, tinta.b), + } +} + +/// La consola grafica de renaser: doble bufer, pantalla fisica y pluma. +pub(crate) struct Consola { + lienzo: Lienzo, + pantalla: Pantalla, + /// Posicion horizontal de la pluma de escritura. + pluma_x: usize, + /// Linea base vertical de la pluma de escritura. + base_y: usize, +} + +// SEGURIDAD: `Consola` encierra, via `Pantalla`, un puntero crudo al +// framebuffer. Ese puntero es valido durante toda la vida del kernel y todo +// acceso a la consola se serializa tras un `Mutex`. En un sistema de un solo +// nucleo, esto la hace segura de compartir entre el hilo principal y las tareas. +unsafe impl Send for Consola {} + +impl Consola { + /// Crea una consola con la pluma en la esquina superior izquierda. + pub(crate) fn nueva(lienzo: Lienzo, pantalla: Pantalla) -> Consola { + Consola { + lienzo, + pantalla, + pluma_x: MARGEN, + base_y: BASE_INICIAL, + } + } + + /// Lleva la pluma al inicio de la siguiente linea. Al llegar al fondo, + /// limpia el lienzo: una pizarra nueva. + fn nueva_linea(&mut self) { + self.pluma_x = MARGEN; + self.base_y += ALTO_LINEA; + if self.base_y + ALTO_LINEA >= self.lienzo.alto { + self.lienzo.limpiar(Color::LIENZO_EN_REPOSO); + self.base_y = BASE_INICIAL; + } + } + + /// Escribe un caracter: rasteriza su glifo y avanza la pluma. + fn escribir_char(&mut self, caracter: char) { + if caracter == '\n' { + self.nueva_linea(); + return; + } + let (metricas, cobertura) = texto::rasterizar(caracter, TAM_FUENTE); + // Salto de linea automatico al alcanzar el margen derecho. + if self.pluma_x + metricas.advance_width as usize + MARGEN > self.lienzo.ancho { + self.nueva_linea(); + } + self.dibujar_glifo(&metricas, &cobertura); + self.pluma_x += metricas.advance_width as usize; + } + + /// Escribe una cadena completa, caracter a caracter. + pub(crate) fn escribir(&mut self, texto: &str) { + for caracter in texto.chars() { + self.escribir_char(caracter); + } + } + + /// Funde un mapa de cobertura de `fontdue` sobre el lienzo, en la pluma. + fn dibujar_glifo(&mut self, metricas: &fontdue::Metrics, cobertura: &[u8]) { + // Origen del glifo: la pluma desplazada por las metricas. El mapa de + // `fontdue` se recorre de arriba a abajo desde la cima del glifo. + let inicio_x = self.pluma_x as isize + metricas.xmin as isize; + let inicio_y = self.base_y as isize - metricas.ymin as isize - metricas.height as isize; + + for fila in 0..metricas.height { + for col in 0..metricas.width { + let opacidad = cobertura[fila * metricas.width + col]; + if opacidad == 0 { + continue; // pixel transparente: no toca el fondo + } + let x = inicio_x + col as isize; + let y = inicio_y + fila as isize; + if x < 0 || y < 0 { + continue; + } + let color = mezclar(Color::LIENZO_EN_REPOSO, Color::TEXTO, opacidad); + self.lienzo.pintar_pixel(x as usize, y as usize, color); + } + } + } + + /// Compone un fotograma crudo del userspace WASM —pixeles `0x00RRGGBB`, con + /// sus limites ya verificados por el host— sobre la SUB-REGION asignada a su + /// aplicacion. Cada pixel se recodifica al formato nativo del framebuffer y + /// se deposita desplazado por `(region.x, region.y)`: una app jamas escribe + /// fuera de su ventana, y varias cohabitan el lienzo sin pisarse. + fn volcar_marco(&mut self, region: RegionPantalla, datos: &[u8]) { + for (indice, trozo) in datos.chunks_exact(4).enumerate() { + let columna = indice % region.ancho; + let fila = indice / region.ancho; + if fila >= region.alto { + break; // el fotograma excede el alto de la region: se ignora el resto + } + let x = region.x + columna; + let y = region.y + fila; + if x >= self.lienzo.ancho || y >= self.lienzo.alto { + continue; // recorte firme: nada se pinta fuera del lienzo + } + let p = u32::from_le_bytes([trozo[0], trozo[1], trozo[2], trozo[3]]); + let color = Color { + r: (p >> 16) as u8, + g: (p >> 8) as u8, + b: p as u8, + }; + self.lienzo.pixeles[y * self.lienzo.ancho + x] = + codificar(self.lienzo.formato, color); + } + self.presentar(); + } + + /// Inunda una region entera con un color plano y la presenta. Es la baliza + /// de desalojo: cuando una aplicacion falla, su ventana se tatua de purpura. + fn pintar_region(&mut self, region: RegionPantalla, color: Color) { + self.lienzo + .rellenar_rect(region.x, region.y, region.ancho, region.alto, color); + self.presentar(); + } + + /// Vuelca el lienzo sobre la pantalla fisica. + pub(crate) fn presentar(&mut self) { + self.pantalla.presentar(&self.lienzo); + } +} + +/// La consola global de renaser. Se funde en el arranque; despues, las tareas +/// asincronas y las capacidades del userspace escriben en ella tras su `Mutex`. +pub(crate) static CONSOLA: Once> = Once::new(); + +/// Puerta del kernel para la capacidad `sys_render_frame` del userspace WASM: +/// compone sobre la consola global un fotograma —cuyos limites el host ya +/// verifico matematicamente contra la memoria lineal del modulo— dentro de la +/// region de pantalla que el kernel asigno a esa aplicacion. +pub(crate) fn volcar_marco_wasm(region: RegionPantalla, datos: &[u8]) { + if let Some(consola) = CONSOLA.get() { + consola.lock().volcar_marco(region, datos); + } +} + +/// Tatua la baliza de desalojo sobre la region de una aplicacion que el kernel +/// ha dado por terminada. El color delata la causa —purpura para una falla de +/// ejecucion o de combustible, amarillo palido para un desbordo de memoria—. Es +/// una advertencia NO fatal: la app muere, el kernel y sus vecinas siguen vivos. +pub(crate) fn pintar_desalojo(region: RegionPantalla, color: Color) { + if let Some(consola) = CONSOLA.get() { + consola.lock().pintar_region(region, color); + } +} diff --git a/renaser/kernel/src/drivers/disco.rs b/renaser/kernel/src/drivers/disco.rs new file mode 100644 index 0000000..e18f07a --- /dev/null +++ b/renaser/kernel/src/drivers/disco.rs @@ -0,0 +1,562 @@ +// ============================================================================= +// renaser :: kernel/src/drivers/disco.rs — Fase 6.2 :: el disco asincrono +// ----------------------------------------------------------------------------- +// La Fase 6.1 hizo hablar al disco; pero lo hacia por SONDEO: el procesador se +// quedaba en un bucle de espera activa vigilando el «used ring» de virtio, +// incapaz de atender nada mas. La Fase 6.2 lo libera. La E/S de bloques pasa a +// ser REACTIVA, guiada por la interrupcion fisica del dispositivo: +// +// * `EsperaDisco` — un `Future` nativo: enviada la peticion, cede la CPU; la +// IRQ del disco lo despertara cuando el bloque este listo. +// * `atender_irq` — el punto al que salta el manejador de la IRQ del disco: +// reconoce la interrupcion en el dispositivo y despierta a quien aguardaba. +// * `bloquear_en` — el puente para los contextos SINCRONOS (el arranque, las +// capacidades WASM): lleva un `Future` de disco hasta su final durmiendo la +// CPU con `hlt` —jamas en espera activa una vez el sistema esta en marcha—. +// +// Subsisten de la Fase 6.1 el asignador de marcos por mapa de bits (con +// liberacion real) y `KernelHal`, el puente DMA hacia `virtio-drivers`. +// ============================================================================= + +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; +use core::future::Future; +use core::pin::Pin; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicU64, AtomicU8, Ordering}; +use core::task::{Context, Poll, Waker}; + +use spin::{Mutex, Once}; +use virtio_drivers::device::blk::{BlkReq, BlkResp, VirtIOBlk, SECTOR_SIZE}; +use virtio_drivers::transport::pci::bus::{Command, DeviceFunction, PciRoot}; +use virtio_drivers::transport::pci::PciTransport; +use virtio_drivers::{BufferDirection, Hal, PhysAddr}; +use x86_64::instructions::interrupts; + +use super::pci::CamPuertos; + +/// Tamaño de una pagina / marco fisico, en bytes. +const PAGINA: u64 = 4096; + +/// Techo de marcos que gestiona el asignador de DMA: 4096 marcos => una arena +/// de 16 MiB. El DMA del disco —colas virtio y buferes rebote— necesita una +/// fraccion minima de eso; el techo solo acota el tamaño del mapa de bits. +const MAX_MARCOS: usize = 4096; + +/// Vendor ID de VirtIO; Device IDs de un disco de bloques (transicional/moderno). +const VENDOR_VIRTIO: u16 = 0x1AF4; +const VIRTIO_BLK_IDS: [u16; 2] = [0x1001, 0x1042]; + +/// El tamaño de un sector, reexportado para el resto del kernel. +pub const TAM_SECTOR: usize = SECTOR_SIZE; + +// ============================================================================= +// EL OFFSET DE MEMORIA FISICA Y EL ASIGNADOR DE MARCOS +// ============================================================================= + +/// Desplazamiento al que el cargador mapeo toda la memoria fisica: una +/// direccion fisica `p` es accesible por el kernel en la virtual `p + offset`. +static OFFSET_FISICO: AtomicU64 = AtomicU64::new(0); + +/// Asignador de marcos por MAPA DE BITS: gestiona una arena de RAM fisica +/// contigua y LIBERA. Cada bit representa un marco de 4 KiB; `1` es ocupado, +/// `0` libre. Un almacen de objetos vivo asigna y devuelve marcos sin descanso. +struct AsignadorMarcos { + /// Direccion fisica del primer marco gestionado, alineada a pagina. + base: u64, + /// Numero de marcos que abarca la arena. + total: usize, + /// Mapa de ocupacion: un bit por marco. + mapa: Vec, +} + +impl AsignadorMarcos { + /// ¿Esta libre el marco `i`? + fn libre(&self, i: usize) -> bool { + (self.mapa[i / 64] >> (i % 64)) & 1 == 0 + } + + /// Marca el marco `i` como ocupado. + fn ocupar(&mut self, i: usize) { + self.mapa[i / 64] |= 1 << (i % 64); + } + + /// Marca el marco `i` como libre. + fn soltar(&mut self, i: usize) { + self.mapa[i / 64] &= !(1u64 << (i % 64)); + } + + /// Reserva `paginas` marcos CONTIGUOS y devuelve la direccion fisica del + /// primero. `None` si no hay un hueco contiguo bastante grande. + fn asignar(&mut self, paginas: usize) -> Option { + if paginas == 0 || paginas > self.total { + return None; + } + let mut i = 0; + while i + paginas <= self.total { + // Buscar `paginas` marcos libres consecutivos a partir de `i`. + match (i..i + paginas).find(|&k| !self.libre(k)) { + // Un marco ocupado rompe la racha: reanudar tras el. + Some(ocupado) => i = ocupado + 1, + // Racha completa: ocuparla y entregar su direccion fisica. + None => { + for k in i..i + paginas { + self.ocupar(k); + } + return Some(self.base + (i as u64) * PAGINA); + } + } + } + None + } + + /// Devuelve a la arena `paginas` marcos que arrancan en la direccion + /// fisica `fisica`. Direcciones ajenas a la arena se ignoran sin daño. + fn liberar(&mut self, fisica: u64, paginas: usize) { + if fisica < self.base { + return; + } + let inicio = ((fisica - self.base) / PAGINA) as usize; + for k in inicio..(inicio + paginas).min(self.total) { + self.soltar(k); + } + } +} + +/// El asignador global de marcos para DMA. Se funde en `init`. +static ASIGNADOR: Once> = Once::new(); + +/// Funda el subsistema de disco: registra el offset de memoria fisica y forja +/// el asignador de marcos sobre la region de RAM libre que el cargador reporto. +/// Una sola vez, antes de montar el disco. +pub fn init(offset_fisico: u64, region_inicio: u64, region_fin: u64) { + OFFSET_FISICO.store(offset_fisico, Ordering::Relaxed); + let base = alinear_arriba(region_inicio, PAGINA); + let disponibles = region_fin.saturating_sub(base) / PAGINA; + let total = (disponibles as usize).min(MAX_MARCOS); + ASIGNADOR.call_once(|| { + Mutex::new(AsignadorMarcos { + base, + total, + mapa: vec![0u64; total.div_ceil(64)], + }) + }); +} + +/// Redondea `valor` hacia arriba al multiplo de `alineacion` (potencia de dos). +fn alinear_arriba(valor: u64, alineacion: u64) -> u64 { + (valor + alineacion - 1) & !(alineacion - 1) +} + +/// Reserva `paginas` marcos fisicos contiguos de 4 KiB y devuelve su direccion +/// fisica. Agotar la arena es un fallo del kernel, no recuperable aqui: el +/// rasgo `Hal` no admite que `dma_alloc` falle. +fn asignar_marcos(paginas: usize) -> u64 { + ASIGNADOR + .get() + .expect("DMA :: el asignador de marcos no esta fundado") + .lock() + .asignar(paginas) + .expect("DMA :: la arena de marcos fisicos se agoto") +} + +/// Devuelve `paginas` marcos fisicos a la arena. El reverso de `asignar_marcos`. +fn liberar_marcos(fisica: u64, paginas: usize) { + if let Some(asignador) = ASIGNADOR.get() { + asignador.lock().liberar(fisica, paginas); + } +} + +/// Traduce una direccion fisica a la virtual que el kernel puede desreferenciar. +fn a_virtual(fisica: u64) -> *mut u8 { + (fisica + OFFSET_FISICO.load(Ordering::Relaxed)) as *mut u8 +} + +// ============================================================================= +// KernelHal — EL PUENTE ENTRE `virtio-drivers` Y LA MEMORIA DE renaser +// ============================================================================= + +/// La implementacion del rasgo `Hal` de `virtio-drivers`. Sin estado propio: +/// se apoya en el asignador de marcos y el offset fisico, ambos globales. +pub struct KernelHal; + +// SEGURIDAD: cada metodo respeta su contrato — `dma_alloc` entrega marcos +// fisicos exclusivos, contiguos, alineados a pagina y a cero; `dma_dealloc` y +// `unshare` los devuelven a la arena; las traducciones de direccion son validas +// porque el cargador mapeo toda la memoria fisica. +unsafe impl Hal for KernelHal { + fn dma_alloc(paginas: usize, _direccion: BufferDirection) -> (PhysAddr, NonNull) { + let fisica = asignar_marcos(paginas); + let virtual_ = a_virtual(fisica); + // SEGURIDAD: `asignar_marcos` entrego `paginas` marcos exclusivos y + // contiguos; `virtual_` es su imagen valida y escribible en el mapeo + // de memoria fisica. El rasgo exige que las paginas vayan a cero. + unsafe { + core::ptr::write_bytes(virtual_, 0, paginas * PAGINA as usize); + } + (fisica, NonNull::new(virtual_).expect("DMA :: marco fisico nulo")) + } + + unsafe fn dma_dealloc(fisica: PhysAddr, _virtual_: NonNull, paginas: usize) -> i32 { + // Con un asignador real, la liberacion ya NO es un gesto vacio: los + // marcos vuelven a la arena y quedan disponibles para el proximo DMA. + liberar_marcos(fisica, paginas); + 0 + } + + unsafe fn mmio_phys_to_virt(fisica: PhysAddr, _tam: usize) -> NonNull { + // El cargador mapeo, como minimo, los primeros 4 GiB de memoria fisica; + // todo BAR MMIO de PCI cae dentro y es accesible en `fisica + offset`. + NonNull::new(a_virtual(fisica)).expect("MMIO :: direccion fisica nula") + } + + unsafe fn share(bufer: NonNull<[u8]>, direccion: BufferDirection) -> PhysAddr { + let longitud = bufer.len(); + let paginas = longitud.div_ceil(PAGINA as usize); + let fisica = asignar_marcos(paginas); + // Si el bufer viaja HACIA el dispositivo, copiarlo al area DMA rebote. + if !matches!(direccion, BufferDirection::DeviceToDriver) { + // SEGURIDAD: el rasgo garantiza que `bufer` apunta a `longitud` + // bytes validos; el area DMA recien reservada los abarca de sobra. + unsafe { + core::ptr::copy_nonoverlapping( + bufer.as_ptr() as *const u8, + a_virtual(fisica), + longitud, + ); + } + } + fisica + } + + unsafe fn unshare(fisica: PhysAddr, bufer: NonNull<[u8]>, direccion: BufferDirection) { + let longitud = bufer.len(); + // Si el bufer viene DESDE el dispositivo, copiar el area DMA de vuelta. + if !matches!(direccion, BufferDirection::DriverToDevice) { + // SEGURIDAD: `fisica` lo entrego `share` para este mismo `bufer`; + // ambas regiones abarcan los `longitud` bytes que se copian. + unsafe { + core::ptr::copy_nonoverlapping( + a_virtual(fisica), + bufer.as_ptr() as *mut u8, + longitud, + ); + } + } + // Devolver a la arena los marcos del area rebote. + liberar_marcos(fisica, longitud.div_ceil(PAGINA as usize)); + } +} + +// ============================================================================= +// EL DISCO PERSISTENTE +// ============================================================================= + +/// El disco virtio-blk, ya montado. Envuelve al `VirtIOBlk` para poder ligarlo +/// a un `static`. +struct Disco(VirtIOBlk); + +// SEGURIDAD: `Disco` encierra punteros crudos a las colas virtio y al MMIO del +// dispositivo. renaser es un kernel de un solo nucleo y todo acceso al disco se +// serializa tras el `Mutex` global `DISCO`; jamas se comparte entre hilos +// reales. Todo acceso normal toma el cerrojo con las interrupciones acalladas, +// de modo que la IRQ del disco jamas lo encuentra ocupado. +unsafe impl Send for Disco {} + +/// El disco global de renaser. Se monta una sola vez, en `montar`. +static DISCO: Once> = Once::new(); + +/// La linea de IRQ del disco, descubierta al montarlo. Vale 0 si el firmware no +/// enruto una linea util: en ese caso la E/S recae en el sondeo, con la red de +/// seguridad del temporizador. +static IRQ_DISCO: AtomicU8 = AtomicU8::new(0); + +/// Cuenta de interrupciones del disco atendidas desde el arranque. Es el +/// testigo vivo de que la E/S asincrona late de verdad. +static PULSOS_DISCO: AtomicU64 = AtomicU64::new(0); + +/// El waker de la (unica) espera de disco en curso. Las operaciones de disco se +/// serializan, de modo que una sola ranura basta. La IRQ del disco lo invoca. +static WAKER_DISCO: Mutex> = Mutex::new(None); + +/// Enumera el bus PCI, localiza el disco virtio-blk, lo monta y lo deja tras el +/// `Mutex` global. Descubre ademas su linea de IRQ, registra el manejador y +/// abre la linea en el PIC: desde aqui el disco puede interrumpir. Devuelve la +/// capacidad del disco en sectores. Toda falla se devuelve como `Err`. +pub fn montar() -> Result { + let mut raiz = PciRoot::new(CamPuertos); + + // 1. Localizar el primer disco virtio-blk recorriendo el bus. + let mut hallado: Option = None; + 'busqueda: for bus in 0..=255u8 { + for (device_function, info) in raiz.enumerate_bus(bus) { + if info.vendor_id == VENDOR_VIRTIO && VIRTIO_BLK_IDS.contains(&info.device_id) { + hallado = Some(device_function); + break 'busqueda; + } + } + } + let device_function = hallado.ok_or("virtio-blk no hallado en el bus PCI")?; + + // 2. Habilitar E/S, espacio de memoria y BUS-MASTER. Sin bus-master el + // dispositivo no puede iniciar transferencias DMA por su cuenta. + raiz.set_command( + device_function, + Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER, + ); + + // 3. Montar el transporte PCI moderno de virtio y el dispositivo de bloques. + let transporte = PciTransport::new::(&mut raiz, device_function) + .map_err(|_| "no se pudo montar el transporte PCI de virtio")?; + let mut disco = VirtIOBlk::::new(transporte) + .map_err(|_| "no se pudo inicializar el dispositivo virtio-blk")?; + let capacidad = disco.capacity(); + + // 4. Pedir al dispositivo que EMITA una interrupcion al completar cada + // peticion — el corazon de la E/S asincrona de esta fase. + disco.enable_interrupts(); + DISCO.call_once(|| Mutex::new(Disco(disco))); + + // 5. Descubrir la linea de IRQ que el firmware asigno al dispositivo y + // enrutarla: registrar el manejador en la IDT y abrir la linea en el + // PIC. Las IRQ 0 y 1 son del temporizador y el teclado; un valor fuera + // de 2..15 (p. ej. 0xFF, «sin conexion») significa que no hay linea + // util — la E/S seguira funcionando, pero por sondeo. + let irq = super::pci::linea_irq(device_function); + if (2..=15).contains(&irq) { + crate::interrupts::registrar_irq_disco(irq); + crate::pic::desenmascarar(irq); + IRQ_DISCO.store(irq, Ordering::SeqCst); + } + + Ok(capacidad) +} + +/// La linea de IRQ del disco, si el firmware enruto una util. +pub fn irq() -> Option { + match IRQ_DISCO.load(Ordering::SeqCst) { + 0 => None, + n => Some(n), + } +} + +/// Numero de interrupciones del disco atendidas desde el arranque. +pub fn pulsos_disco() -> u64 { + PULSOS_DISCO.load(Ordering::Relaxed) +} + +/// Punto de entrada DESDE el manejador de la IRQ del disco. Reconoce la +/// interrupcion en el dispositivo —leer su registro ISR baja la linea INTx— y +/// despierta a la tarea que aguardaba el bloque. Breve y libre de panicos: +/// corre en contexto de interrupcion. +pub fn atender_irq() { + // Estamos en contexto de interrupcion (IF=0). Todo acceso normal a `DISCO` + // toma su cerrojo con las interrupciones acalladas, de modo que aqui jamas + // esta ocupado: tomarlo no puede interbloquear. + if let Some(disco) = DISCO.get() { + let _ = disco.lock().0.ack_interrupt(); + } + PULSOS_DISCO.fetch_add(1, Ordering::Relaxed); + if let Some(waker) = WAKER_DISCO.lock().take() { + waker.wake(); + } +} + +/// Inscribe el waker de la espera de disco en curso. Una sola ranura: las +/// operaciones de disco estan serializadas. El cerrojo lo disputa el manejador +/// de IRQ — tomarlo con las interrupciones acalladas hace imposible el bloqueo. +fn registrar_waker(waker: Waker) { + interrupts::without_interrupts(|| *WAKER_DISCO.lock() = Some(waker)); +} + +// ============================================================================= +// EsperaDisco — UNA OPERACION DE BLOQUE COMO `Future` +// ============================================================================= + +/// Una transferencia de bloques en vuelo, expresada como `Future`. Posee sus +/// propios buferes DMA —el encabezado de peticion, la respuesta y los datos—, +/// que `virtio-drivers` mantiene prestados hasta que la operacion concluye. +/// +/// Su `poll` envia la peticion la primera vez y, despues, comprueba el «used +/// ring»: si el dispositivo aun no ha terminado, inscribe el waker y cede; la +/// IRQ del disco lo reanudara. Una operacion a la vez — basta para renaser. +pub struct EsperaDisco { + /// Encabezado de la peticion virtio. En el heap: direccion estable mientras + /// el dispositivo lo tiene prestado, sin importar si el `Future` se mueve. + req: Box, + /// Respuesta de estado del dispositivo. En el heap, por la misma razon. + resp: Box, + /// Los datos: destino de una lectura, origen de una escritura. + buf: Vec, + /// Primer sector de la transferencia. + sector: u64, + /// `true` si la operacion escribe; `false` si lee. + es_escritura: bool, + /// Token que `virtio-drivers` devolvio al enviar la peticion. `None` hasta + /// que el primer `poll` la envia. + token: Option, +} + +impl EsperaDisco { + /// Hace avanzar la operacion: la envia si aun no lo estaba y comprueba si el + /// dispositivo la completo. `None` => sigue en vuelo; `Some` => terminada. + fn avanzar(&mut self) -> Option, &'static str>> { + // Todo el dialogo con el dispositivo, con las interrupciones acalladas: + // asi la IRQ del disco jamas encuentra ocupado el cerrojo de `DISCO`. + interrupts::without_interrupts(|| { + let disco = match DISCO.get() { + Some(disco) => disco, + None => return Some(Err("disco no montado")), + }; + let mut guardia = disco.lock(); + let blk = &mut guardia.0; + + // 1. Enviar la peticion la primera vez que se sondea esta espera. + if self.token.is_none() { + // SEGURIDAD: `req`, `buf` y `resp` viven en esta `EsperaDisco`, + // que no se libera ni se accede por otra via hasta que el + // `complete_*` de mas abajo cierre la operacion. + let envio = unsafe { + if self.es_escritura { + blk.write_blocks_nb( + self.sector as usize, + &mut self.req, + self.buf.as_slice(), + &mut self.resp, + ) + } else { + blk.read_blocks_nb( + self.sector as usize, + &mut self.req, + self.buf.as_mut_slice(), + &mut self.resp, + ) + } + }; + match envio { + Ok(token) => self.token = Some(token), + Err(_) => return Some(Err("no se pudo enviar la peticion al disco")), + } + } + let token = self.token.unwrap(); + + // 2. ¿Ha colocado el dispositivo este token en el «used ring»? + if blk.peek_used() != Some(token) { + return None; // aun en vuelo + } + + // 3. Completado: recoger el resultado y desligar los buferes DMA. + // SEGURIDAD: se pasan los MISMOS buferes que recibio el `*_nb`. + let fin = unsafe { + if self.es_escritura { + blk.complete_write_blocks(token, &self.req, self.buf.as_slice(), &mut self.resp) + } else { + blk.complete_read_blocks( + token, + &self.req, + self.buf.as_mut_slice(), + &mut self.resp, + ) + } + }; + Some(match fin { + Ok(()) => Ok(core::mem::take(&mut self.buf)), + Err(_) => Err("la operacion de disco no se completo"), + }) + }) + } +} + +impl Future for EsperaDisco { + /// Al terminar, una lectura entrega sus datos; una escritura, un vector + /// vacio. El error es siempre un mensaje estable. + type Output = Result, &'static str>; + + fn poll(self: Pin<&mut Self>, contexto: &mut Context<'_>) -> Poll { + // `EsperaDisco` es `Unpin` —solo `Box`, `Vec` y escalares—: el `Pin` + // no impone nada y el acceso mutable es directo. + let this = self.get_mut(); + // Inscribir el waker ANTES de comprobar: si la IRQ se cuela entre la + // comprobacion y la inscripcion, el waker ya esta puesto y el despertar + // no se pierde — el mismo blindaje que usa `EsperaFrame` (ver `reloj`). + registrar_waker(contexto.waker().clone()); + match this.avanzar() { + Some(resultado) => Poll::Ready(resultado), + None => Poll::Pending, + } + } +} + +/// Prepara la LECTURA asincrona de `n_sectores` sectores desde `sector`. El +/// `Future` que devuelve no toca el disco hasta que se le sondea. +pub fn leer_bloques(sector: u64, n_sectores: usize) -> EsperaDisco { + EsperaDisco { + req: Box::new(BlkReq::default()), + resp: Box::new(BlkResp::default()), + buf: vec![0u8; n_sectores * TAM_SECTOR], + sector, + es_escritura: false, + token: None, + } +} + +/// Prepara la ESCRITURA asincrona de `datos` a partir de `sector`. La longitud +/// de `datos` debe ser multiplo de un sector. +pub fn escribir_bloques(sector: u64, datos: Vec) -> EsperaDisco { + EsperaDisco { + req: Box::new(BlkReq::default()), + resp: Box::new(BlkResp::default()), + buf: datos, + sector, + es_escritura: true, + token: None, + } +} + +// ============================================================================= +// bloquear_en — EL PUENTE PARA LOS CONTEXTOS SINCRONOS +// ============================================================================= + +/// Lleva un `Future` de disco hasta su final desde un contexto SINCRONO — el +/// arranque, o una capacidad WASM, que no pueden `.await`-ear—. Mientras el +/// disco trabaja: +/// +/// * si las interrupciones estan activas, duerme la CPU con `hlt`; la +/// despertara la IRQ del disco —o el temporizador, como red de seguridad—; +/// * si no —en el arranque, antes de habilitarlas—, sondea en bucle. +/// +/// Asi, una vez el sistema esta en marcha, la espera de disco JAMAS malgasta +/// ciclos en espera activa. +fn bloquear_en(futuro: F) -> F::Output { + let mut futuro = core::pin::pin!(futuro); + let waker = Waker::noop(); + let mut contexto = Context::from_waker(waker); + loop { + match futuro.as_mut().poll(&mut contexto) { + Poll::Ready(salida) => return salida, + Poll::Pending => { + if interrupts::are_enabled() { + x86_64::instructions::hlt(); + } else { + core::hint::spin_loop(); + } + } + } + } +} + +/// Lee `buf.len() / 512` sectores consecutivos a partir de `sector`. Sincrono: +/// construido sobre la maquinaria asincrona via `bloquear_en`. El bufer debe +/// medir un multiplo entero de un sector. +pub fn leer_sectores(sector: u64, buf: &mut [u8]) -> Result<(), &'static str> { + let datos = bloquear_en(leer_bloques(sector, buf.len() / TAM_SECTOR))?; + buf.copy_from_slice(&datos); + Ok(()) +} + +/// Escribe `buf.len() / 512` sectores consecutivos a partir de `sector`. +/// Sincrono, sobre la misma maquinaria asincrona. +pub fn escribir_sectores(sector: u64, buf: &[u8]) -> Result<(), &'static str> { + bloquear_en(escribir_bloques(sector, buf.to_vec())).map(|_| ()) +} diff --git a/renaser/kernel/src/drivers/mod.rs b/renaser/kernel/src/drivers/mod.rs new file mode 100644 index 0000000..91318fd --- /dev/null +++ b/renaser/kernel/src/drivers/mod.rs @@ -0,0 +1,15 @@ +// ============================================================================= +// renaser :: kernel/src/drivers — Fase 6.1 :: el puente hacia el hardware real +// ----------------------------------------------------------------------------- +// Hasta aqui renaser solo tocaba el silicio que el firmware le servia en +// bandeja: el framebuffer GOP, el temporizador, el teclado. Los `drivers` +// abren la primera via hacia hardware que el kernel debe DESCUBRIR y reclamar +// por si mismo: +// +// * `pci` — acceso al espacio de configuracion del bus PCI (0xCF8/0xCFC). +// * `disco` — el disco virtio-blk: asignador de marcos DMA, `Hal` y la +// lectura, por sondeo, de su primer sector. +// ============================================================================= + +pub mod disco; +pub mod pci; diff --git a/renaser/kernel/src/drivers/pci.rs b/renaser/kernel/src/drivers/pci.rs new file mode 100644 index 0000000..b1fd3f5 --- /dev/null +++ b/renaser/kernel/src/drivers/pci.rs @@ -0,0 +1,76 @@ +// ============================================================================= +// renaser :: kernel/src/drivers/pci.rs — Fase 6.1 :: el acceso al bus PCI +// ----------------------------------------------------------------------------- +// El cargador `bootloader` entrega el mapa de memoria y el framebuffer, pero +// NO un censo de perifericos: descubrir el disco es tarea del kernel. Aqui +// renaser habla con el espacio de configuracion del bus PCI a traves del +// mecanismo #1 —los puertos 0xCF8 (direccion) y 0xCFC (datos)—. +// +// Este modulo provee `CamPuertos`, una implementacion del rasgo +// `ConfigurationAccess` de `virtio-drivers`: la crate enumera el bus y mapea +// los BARs del dispositivo apoyandose en estas dos funciones de acceso. +// ============================================================================= + +use virtio_drivers::transport::pci::bus::{ConfigurationAccess, DeviceFunction}; +use x86_64::instructions::port::Port; + +/// Puerto de DIRECCION del mecanismo de configuracion PCI #1. +const CONFIG_ADDRESS: u16 = 0xCF8; +/// Puerto de DATOS del mecanismo de configuracion PCI #1. +const CONFIG_DATA: u16 = 0xCFC; + +/// Compone la palabra de direccion del mecanismo #1: bit 31 de habilitacion, +/// bus, dispositivo, funcion y offset de registro alineado a dword. +fn direccion(device_function: DeviceFunction, offset: u8) -> u32 { + 0x8000_0000 + | ((device_function.bus as u32) << 16) + | ((device_function.device as u32) << 11) + | ((device_function.function as u32) << 8) + | ((offset as u32) & 0xFC) +} + +/// Lee un registro de 32 bits del espacio de configuracion PCI. +fn leer(device_function: DeviceFunction, offset: u8) -> u32 { + // SEGURIDAD: 0xCF8/0xCFC son los puertos del mecanismo de configuracion + // PCI #1, fijos en la arquitectura PC. La direccion lleva su bit de + // habilitacion y el offset alineado a dword, como exige el protocolo. + unsafe { + Port::::new(CONFIG_ADDRESS).write(direccion(device_function, offset)); + Port::::new(CONFIG_DATA).read() + } +} + +/// Escribe un registro de 32 bits en el espacio de configuracion PCI. +fn escribir(device_function: DeviceFunction, offset: u8, dato: u32) { + // SEGURIDAD: vease `leer` — mismos puertos, mismo protocolo del 8259/PCI. + unsafe { + Port::::new(CONFIG_ADDRESS).write(direccion(device_function, offset)); + Port::::new(CONFIG_DATA).write(dato); + } +} + +/// Lee el registro «Interrupt Line» (byte bajo del offset 0x3C): la linea de +/// IRQ del PIC que el firmware UEFI enruto y asigno a este dispositivo. Es el +/// puente entre el descubrimiento PCI y el manejo de interrupciones (Fase 6.2). +pub fn linea_irq(device_function: DeviceFunction) -> u8 { + (leer(device_function, 0x3C) & 0xFF) as u8 +} + +/// Acceso al espacio de configuracion PCI por puertos de E/S — el mecanismo #1. +/// Es un tipo sin estado: toda la informacion viaja en cada llamada. +pub struct CamPuertos; + +impl ConfigurationAccess for CamPuertos { + fn read_word(&self, device_function: DeviceFunction, register_offset: u8) -> u32 { + leer(device_function, register_offset) + } + + fn write_word(&mut self, device_function: DeviceFunction, register_offset: u8, data: u32) { + escribir(device_function, register_offset, data); + } + + unsafe fn unsafe_clone(&self) -> Self { + // `CamPuertos` no tiene estado: clonarlo es trivial y sin riesgo. + CamPuertos + } +} diff --git a/renaser/kernel/src/gdt.rs b/renaser/kernel/src/gdt.rs new file mode 100644 index 0000000..4c7831c --- /dev/null +++ b/renaser/kernel/src/gdt.rs @@ -0,0 +1,80 @@ +// ============================================================================= +// renaser :: kernel/src/gdt.rs — Fase 2 :: cimientos del manejo de fallos +// ----------------------------------------------------------------------------- +// La GDT y el TSS no se ven jamas, pero sostienen todo lo demas. Su cometido +// esencial en renaser es reservar un stack de emergencia inquebrantable: si la +// pila del kernel se desborda, el manejador de doble fallo aterrizara sobre +// terreno firme en lugar de arrastrar al sistema hacia un fallo triple. +// +// Una sola verdad, instalada una sola vez, que PERSISTE bajo cada interrupcion. +// ============================================================================= + +use x86_64::instructions::segmentation::{Segment, CS, DS, ES, SS}; +use x86_64::instructions::tables::load_tss; +use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}; +use x86_64::structures::tss::TaskStateSegment; +use x86_64::VirtAddr; + +use crate::CeldaSync; + +/// Indice, dentro de la Interrupt Stack Table del TSS, del stack reservado +/// para el manejador de doble fallo. +pub const IST_DOBLE_FALLO: u16 = 0; + +/// Tamaño del stack de emergencia: 20 KiB. Holgura suficiente para diagnosticar +/// el fallo y encender la baliza sin volver a tocar la pila comprometida. +const TAM_STACK_EMERGENCIA: usize = 4096 * 5; + +/// Stack de emergencia del doble fallo. Vive en `.bss`; la IST apuntara a su +/// extremo superior, pues la pila de x86_64 crece hacia direcciones menores. +static STACK_EMERGENCIA: CeldaSync<[u8; TAM_STACK_EMERGENCIA]> = + CeldaSync::nueva([0; TAM_STACK_EMERGENCIA]); + +/// El Task State Segment. En 64 bits ya no describe «tareas»: lo conservamos +/// unicamente por su Interrupt Stack Table. +static TSS: CeldaSync = CeldaSync::nueva(TaskStateSegment::new()); + +/// La Global Descriptor Table propia de renaser. +static GDT: CeldaSync = CeldaSync::nueva(GlobalDescriptorTable::new()); + +/// Instala una GDT propia con su TSS y arma el stack de emergencia. +/// +/// Debe invocarse una sola vez, durante el arranque, ANTES de cargar la IDT: +/// la IDT inscribe en cada entrada el selector de codigo vigente en ese momento. +pub fn init() { + // --- 1. Inscribir en el TSS la cima del stack de emergencia. --- + // La pila crece hacia abajo: la IST exige la direccion MAS ALTA del bloque. + let cima_stack = VirtAddr::from_ptr(STACK_EMERGENCIA.puntero()) + TAM_STACK_EMERGENCIA as u64; + // SEGURIDAD: `init` corre una sola vez, en arranque secuencial de un solo + // hilo; nada mas referencia el TSS todavia. + unsafe { + (*TSS.puntero()).interrupt_stack_table[IST_DOBLE_FALLO as usize] = cima_stack; + } + + // --- 2. Construir la GDT: codigo de kernel, datos de kernel y el TSS. --- + // SEGURIDAD: mismo argumento de unicidad; la GDT aun no esta cargada y + // ningun otro flujo posee referencias a estas celdas. + let gdt: &'static mut GlobalDescriptorTable = unsafe { &mut *GDT.puntero() }; + let tss: &'static TaskStateSegment = unsafe { &*TSS.puntero() }; + + let sel_codigo: SegmentSelector = gdt.append(Descriptor::kernel_code_segment()); + let sel_datos: SegmentSelector = gdt.append(Descriptor::kernel_data_segment()); + let sel_tss: SegmentSelector = gdt.append(Descriptor::tss_segment(tss)); + + // --- 3. Activar la GDT y recargar TODOS los registros de segmento. --- + let gdt_estatica: &'static GlobalDescriptorTable = gdt; + gdt_estatica.load(); + // SEGURIDAD: los tres selectores apuntan a entradas validas de la GDT + // recien cargada. Recargar SS/DS/ES es IMPRESCINDIBLE, no opcional: el + // cargador nos deja esos registros con selectores de SU tabla; si SS + // conservara el valor heredado, el primer `iretq` de una rutina de + // excepcion intentaria recargar un selector que en nuestra GDT ya no + // describe un segmento de datos, y degeneraria en un #GP. + unsafe { + CS::set_reg(sel_codigo); + SS::set_reg(sel_datos); + DS::set_reg(sel_datos); + ES::set_reg(sel_datos); + load_tss(sel_tss); + } +} diff --git a/renaser/kernel/src/grafico.rs b/renaser/kernel/src/grafico.rs new file mode 100644 index 0000000..721d12b --- /dev/null +++ b/renaser/kernel/src/grafico.rs @@ -0,0 +1,325 @@ +// ============================================================================= +// renaser :: kernel/src/grafico.rs — el sustrato grafico del sistema +// ----------------------------------------------------------------------------- +// En renaser el texto es un caso particular del dibujo, y el dibujo descansa +// sobre este modulo: el color, el framebuffer fisico (`Pantalla`) y el lienzo +// intermedio en RAM (`Lienzo`) que sostiene la tecnica de doble bufer. +// ============================================================================= + +use core::cell::UnsafeCell; +use core::ptr; +use core::sync::atomic::{AtomicBool, Ordering}; + +use bootloader_api::info::{FrameBuffer, FrameBufferInfo, PixelFormat}; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::geometry::{OriginDimensions, Size}; +use embedded_graphics::pixelcolor::{Rgb888, RgbColor}; +use embedded_graphics::Pixel; + +/// Ancho maximo de lienzo soportado (Full HD). +pub(crate) const ANCHO_MAX: usize = 1920; +/// Alto maximo de lienzo soportado (Full HD). +pub(crate) const ALTO_MAX: usize = 1080; +/// Capacidad del lienzo intermedio, en pixeles de 32 bits. +const PIXELES_MAX: usize = ANCHO_MAX * ALTO_MAX; + +// ============================================================================= +// COLOR — la unidad indivisible del lenguaje visual de renaser +// ============================================================================= + +/// Color de 24 bits, independiente del formato fisico del framebuffer. +#[derive(Clone, Copy)] +pub(crate) struct Color { + pub(crate) r: u8, + pub(crate) g: u8, + pub(crate) b: u8, +} + +impl Color { + /// Reposo del lienzo: un indigo casi negro, sereno y persistente. + pub(crate) const LIENZO_EN_REPOSO: Color = Color { + r: 0x12, + g: 0x16, + b: 0x20, + }; + + /// Alerta de colapso: un rojo saturado, imposible de ignorar. + pub(crate) const ALERTA: Color = Color { + r: 0xD4, + g: 0x1E, + b: 0x2C, + }; + + /// Agotamiento de memoria (OOM): un naranja de advertencia. + pub(crate) const OOM: Color = Color { + r: 0xFF, + g: 0xA5, + b: 0x00, + }; + + /// Tinta del texto: un blanco suave, legible sobre el indigo. + pub(crate) const TEXTO: Color = Color { + r: 0xE8, + g: 0xEC, + b: 0xF4, + }; + + /// Desalojo de una aplicacion: un purpura inequivoco. Distinto del rojo de + /// colapso del kernel y del naranja de OOM — porque esto NO es un colapso: + /// es el kernel conteniendo a un inquilino discolo y siguiendo vivo. + pub(crate) const DESALOJO: Color = Color { + r: 0x8B, + g: 0x5C, + b: 0xF6, + }; + + /// Desalojo por desbordo de memoria: un amarillo palido. Distingue al + /// inquilino que revento su techo ESPACIAL del que agoto su tiempo (purpura). + pub(crate) const DESALOJO_MEMORIA: Color = Color { + r: 0xFF, + g: 0xFF, + b: 0xE0, + }; +} + +// ============================================================================= +// REGION — la sub-superficie que el kernel asigna a cada aplicacion +// ============================================================================= + +/// Una sub-region rectangular de la pantalla, en pixeles. El kernel asigna una +/// a cada aplicacion del userspace: es, a la vez, su ventana al mundo y su +/// confinamiento — una app jamas pinta un pixel fuera de la suya. +#[derive(Clone, Copy)] +pub(crate) struct RegionPantalla { + /// Desplazamiento horizontal de la esquina superior izquierda. + pub(crate) x: usize, + /// Desplazamiento vertical de la esquina superior izquierda. + pub(crate) y: usize, + /// Ancho de la region, en pixeles. + pub(crate) ancho: usize, + /// Alto de la region, en pixeles. + pub(crate) alto: usize, +} + +impl RegionPantalla { + /// Numero total de pixeles que abarca la region. + pub(crate) const fn pixeles(&self) -> usize { + self.ancho * self.alto + } +} + +/// Traduce un [`Color`] logico al valor nativo de 32 bits que el framebuffer +/// espera, respetando el orden de canales que reporta el firmware UEFI. +pub(crate) fn codificar(formato: PixelFormat, color: Color) -> u32 { + let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32); + match formato { + PixelFormat::Rgb => r | (g << 8) | (b << 16), + PixelFormat::Bgr => b | (g << 8) | (r << 16), + PixelFormat::U8 => (r * 54 + g * 183 + b * 19) >> 8, + PixelFormat::Unknown { + red_position, + green_position, + blue_position, + } => (r << red_position) | (g << green_position) | (b << blue_position), + _ => r | (g << 8) | (b << 16), + } +} + +// ============================================================================= +// ESCRITURA VOLATIL — la unica celula que toca memoria de video real +// ============================================================================= + +/// Deposita un pixel ya codificado en una direccion del framebuffer fisico. +/// Las escrituras son **volatiles**: el optimizador no puede elidirlas. +/// +/// # Seguridad +/// +/// `destino` debe apuntar a memoria de video valida y escribible para +/// `bytes_por_pixel` bytes, correctamente alineada para el ancho de escritura. +#[inline] +pub(crate) unsafe fn escribir_pixel_volatil(destino: *mut u8, valor: u32, bytes_por_pixel: usize) { + match bytes_por_pixel { + 4 => unsafe { ptr::write_volatile(destino.cast::(), valor) }, + 3 => unsafe { + ptr::write_volatile(destino, valor as u8); + ptr::write_volatile(destino.add(1), (valor >> 8) as u8); + ptr::write_volatile(destino.add(2), (valor >> 16) as u8); + }, + 2 => unsafe { ptr::write_volatile(destino.cast::(), valor as u16) }, + _ => unsafe { ptr::write_volatile(destino, valor as u8) }, + } +} + +// ============================================================================= +// LIENZO INTERMEDIO — el corazon del doble bufer +// ============================================================================= + +/// Respaldo estatico del lienzo, alineado a pagina. Vive en `.bss`. +#[repr(align(4096))] +struct LienzoEstatico(UnsafeCell<[u32; PIXELES_MAX]>); + +// SEGURIDAD: el acceso se serializa mediante `LIENZO_ENTREGADO`, que garantiza +// un unico prestamo mutable durante toda la vida del sistema. +unsafe impl Sync for LienzoEstatico {} + +/// Memoria de respaldo del lienzo intermedio. +static MEMORIA_LIENZO: LienzoEstatico = LienzoEstatico(UnsafeCell::new([0u32; PIXELES_MAX])); + +/// Centinela de entrega unica del lienzo. +static LIENZO_ENTREGADO: AtomicBool = AtomicBool::new(false); + +/// Entrega — exactamente una vez — el prestamo mutable de la memoria del lienzo. +pub(crate) fn reclamar_memoria_lienzo() -> Option<&'static mut [u32]> { + if LIENZO_ENTREGADO.swap(true, Ordering::AcqRel) { + return None; + } + // SEGURIDAD: el `swap` anterior garantiza que este es el unico prestamo + // mutable de `MEMORIA_LIENZO` durante toda la ejecucion. + let arreglo: &'static mut [u32; PIXELES_MAX] = unsafe { &mut *MEMORIA_LIENZO.0.get() }; + Some(arreglo.as_mut_slice()) +} + +/// Superficie de dibujo en RAM. Cada pixel se almacena ya codificado. +pub(crate) struct Lienzo { + pub(crate) pixeles: &'static mut [u32], + pub(crate) ancho: usize, + pub(crate) alto: usize, + pub(crate) formato: PixelFormat, +} + +impl Lienzo { + /// Construye un lienzo sobre la memoria de respaldo reclamada. + pub(crate) fn nuevo( + memoria: &'static mut [u32], + ancho: usize, + alto: usize, + formato: PixelFormat, + ) -> Lienzo { + Lienzo { + pixeles: memoria, + ancho, + alto, + formato, + } + } + + /// Pinta un unico pixel. Las coordenadas fuera del lienzo se ignoran. + pub(crate) fn pintar_pixel(&mut self, x: usize, y: usize, color: Color) { + if x < self.ancho && y < self.alto { + self.pixeles[y * self.ancho + x] = codificar(self.formato, color); + } + } + + /// Rellena un rectangulo, recortado con firmeza a los limites del lienzo. + pub(crate) fn rellenar_rect( + &mut self, + x0: usize, + y0: usize, + ancho: usize, + alto: usize, + color: Color, + ) { + let valor = codificar(self.formato, color); + let x_ini = x0.min(self.ancho); + let y_ini = y0.min(self.alto); + let x_fin = (x0 + ancho).min(self.ancho); + let y_fin = (y0 + alto).min(self.alto); + + let mut y = y_ini; + while y < y_fin { + let base = y * self.ancho; + let mut x = x_ini; + while x < x_fin { + self.pixeles[base + x] = valor; + x += 1; + } + y += 1; + } + } + + /// Inunda el lienzo entero con un color plano. + pub(crate) fn limpiar(&mut self, color: Color) { + self.rellenar_rect(0, 0, self.ancho, self.alto, color); + } +} + +// `embedded-graphics` ve el lienzo como un `DrawTarget`: sus primitivas +// vectoriales pueden dibujar directamente sobre el. +impl DrawTarget for Lienzo { + type Color = Rgb888; + type Error = core::convert::Infallible; + + fn draw_iter(&mut self, pixeles: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for Pixel(punto, color) in pixeles { + if let (Ok(x), Ok(y)) = (usize::try_from(punto.x), usize::try_from(punto.y)) { + self.pintar_pixel( + x, + y, + Color { + r: color.r(), + g: color.g(), + b: color.b(), + }, + ); + } + } + Ok(()) + } +} + +impl OriginDimensions for Lienzo { + fn size(&self) -> Size { + Size::new(self.ancho as u32, self.alto as u32) + } +} + +// ============================================================================= +// PANTALLA — el framebuffer fisico GOP, envuelto en seguridad +// ============================================================================= + +/// Vista segura del framebuffer lineal entregado por el firmware UEFI. +pub(crate) struct Pantalla { + pub(crate) base: *mut u8, + pub(crate) ancho: usize, + pub(crate) alto: usize, + pub(crate) paso_bytes: usize, + pub(crate) bytes_por_pixel: usize, +} + +impl Pantalla { + /// Adopta el framebuffer descrito por `info`. La memoria de video es + /// permanente, asi que conservar su puntero crudo es legitimo. + pub(crate) fn adoptar(framebuffer: &mut FrameBuffer, info: FrameBufferInfo) -> Pantalla { + let base = framebuffer.buffer_mut().as_mut_ptr(); + Pantalla { + base, + ancho: info.width, + alto: info.height, + paso_bytes: info.stride * info.bytes_per_pixel, + bytes_por_pixel: info.bytes_per_pixel, + } + } + + /// Vuelca el lienzo intermedio sobre la pantalla fisica de un solo gesto. + pub(crate) fn presentar(&mut self, lienzo: &Lienzo) { + let ancho = self.ancho.min(lienzo.ancho); + let alto = self.alto.min(lienzo.alto); + + for y in 0..alto { + let fila_fisica = y * self.paso_bytes; + let fila_lienzo = y * lienzo.ancho; + for x in 0..ancho { + let pixel = lienzo.pixeles[fila_lienzo + x]; + // SEGURIDAD: x e y estan acotados por las dimensiones reales + // del framebuffer; el desplazamiento cae siempre dentro de el. + unsafe { + let destino = self.base.add(fila_fisica + x * self.bytes_por_pixel); + escribir_pixel_volatil(destino, pixel, self.bytes_por_pixel); + } + } + } + } +} diff --git a/renaser/kernel/src/interrupts.rs b/renaser/kernel/src/interrupts.rs new file mode 100644 index 0000000..9d5e46a --- /dev/null +++ b/renaser/kernel/src/interrupts.rs @@ -0,0 +1,148 @@ +// ============================================================================= +// renaser :: kernel/src/interrupts.rs — Fase 2/3 :: la IDT y los reflejos +// ----------------------------------------------------------------------------- +// La Interrupt Descriptor Table es la tabla de reflejos del procesador. ante +// cada excepcion o IRQ, la CPU abandona lo que hacia y salta a la entrada que +// le corresponde. renaser distingue tres naturalezas de impulso: +// +// * RECUPERABLE — el breakpoint (#BP): se atiende y la ejecucion prosigue. +// * FATAL — el resto de excepciones: se entregan al `panic!`. +// * HARDWARE — temporizador, teclado y disco: ya NO escriben estado +// rustico, sino que alimentan el reactor asincrono. +// ============================================================================= + +use core::sync::atomic::{AtomicU8, Ordering}; + +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}; + +use crate::{gdt, pic, CeldaSync}; + +/// La Interrupt Descriptor Table propia de renaser. +static IDT: CeldaSync = + CeldaSync::nueva(InterruptDescriptorTable::new()); + +/// Vector de la IDT asignado a la IRQ del disco. Vale 0 hasta que el montaje +/// del disco (Fase 6.2) descubra su linea de IRQ y la registre — ninguna IRQ +/// legitima del disco vive en el vector 0 (reservado a las excepciones). +static VECTOR_DISCO: AtomicU8 = AtomicU8::new(0); + +/// Construye y activa la Interrupt Descriptor Table. +/// +/// Debe invocarse una sola vez, durante el arranque, DESPUES de [`gdt::init`]. +pub fn init() { + // SEGURIDAD: `init` corre una sola vez, en arranque secuencial de un solo + // hilo; nada mas referencia la IDT todavia. + let idt: &'static mut InterruptDescriptorTable = unsafe { &mut *IDT.puntero() }; + + // --- Excepciones de CPU --- + idt.breakpoint.set_handler_fn(reflejo_breakpoint); + idt.invalid_opcode.set_handler_fn(reflejo_opcode_invalido); + idt.divide_error.set_handler_fn(reflejo_division); + idt.general_protection_fault + .set_handler_fn(reflejo_proteccion_general); + idt.page_fault.set_handler_fn(reflejo_fallo_pagina); + + // El doble fallo se atiende SIEMPRE sobre el stack de emergencia del TSS: + // ni un desbordamiento de la pila del kernel impedira su diagnostico. + // SEGURIDAD: el indice IST referido fue armado previamente por `gdt::init`. + unsafe { + idt.double_fault + .set_handler_fn(reflejo_doble_fallo) + .set_stack_index(gdt::IST_DOBLE_FALLO); + } + + // --- Interrupciones de hardware, ya remapeadas por el PIC --- + idt[pic::VECTOR_TEMPORIZADOR].set_handler_fn(irq_temporizador); + idt[pic::VECTOR_TECLADO].set_handler_fn(irq_teclado); + + let idt_estatica: &'static InterruptDescriptorTable = idt; + idt_estatica.load(); +} + +/// Registra el manejador de la IRQ del disco virtio-blk en la entrada de la IDT +/// que corresponde a `irq` (Fase 6.2). Lo invoca el montaje del disco, una vez +/// descubierta la linea de IRQ que el firmware le asigno. +/// +/// Se llama durante el arranque secuencial, con las interrupciones aun +/// desactivadas y la linea todavia enmascarada en el PIC: la entrada que se +/// escribe no puede dispararse mientras se escribe. +pub fn registrar_irq_disco(irq: u8) { + let vector = pic::vector_irq(irq); + VECTOR_DISCO.store(vector, Ordering::SeqCst); + // SEGURIDAD: el arranque es secuencial y de un solo hilo; las interrupciones + // estan desactivadas y la linea del disco, enmascarada. La IDT ya esta + // cargada (`init` hizo `lidt`), pero la CPU relee cada entrada en cada + // interrupcion: modificar esta entrada en memoria surte efecto de inmediato. + let idt: &'static mut InterruptDescriptorTable = unsafe { &mut *IDT.puntero() }; + idt[vector].set_handler_fn(irq_disco); +} + +// ============================================================================= +// REFLEJOS DE EXCEPCION — las rutinas a las que la CPU salta ante cada fallo +// ============================================================================= + +/// #BP — Breakpoint. Excepcion RECUPERABLE: se atiende sin ruido y, al +/// retornar, la CPU reanuda la ejecucion justo tras la instruccion `int3`. +extern "x86-interrupt" fn reflejo_breakpoint(_marco: InterruptStackFrame) {} + +/// #UD — Opcode invalido. Fatal: la corriente de instrucciones es incoherente. +extern "x86-interrupt" fn reflejo_opcode_invalido(marco: InterruptStackFrame) { + panic!("EXCEPCION FATAL :: opcode invalido (#UD)\n{marco:#?}"); +} + +/// #DE — Error de division. Fatal. +extern "x86-interrupt" fn reflejo_division(marco: InterruptStackFrame) { + panic!("EXCEPCION FATAL :: error de division (#DE)\n{marco:#?}"); +} + +/// #GP — Fallo de proteccion general. Fatal. +extern "x86-interrupt" fn reflejo_proteccion_general(marco: InterruptStackFrame, codigo: u64) { + panic!("EXCEPCION FATAL :: proteccion general (#GP) codigo={codigo:#x}\n{marco:#?}"); +} + +/// #PF — Fallo de pagina. Fatal en esta fase (sin memoria virtual dinamica). +extern "x86-interrupt" fn reflejo_fallo_pagina( + marco: InterruptStackFrame, + codigo: PageFaultErrorCode, +) { + let direccion = x86_64::registers::control::Cr2::read_raw(); + panic!("EXCEPCION FATAL :: fallo de pagina (#PF) en {direccion:#x} {codigo:?}\n{marco:#?}"); +} + +/// #DF — Doble fallo. Fatal e irreversible: por definicion, diverge. Se ejecuta +/// sobre el stack de emergencia del TSS, nunca sobre la pila comprometida. +extern "x86-interrupt" fn reflejo_doble_fallo(marco: InterruptStackFrame, _codigo: u64) -> ! { + panic!("EXCEPCION FATAL :: doble fallo (#DF)\n{marco:#?}"); +} + +// ============================================================================= +// IMPULSOS DE HARDWARE — productores de eventos para el reactor asincrono +// ============================================================================= + +/// IRQ0 — Temporizador. Desde la Fase 5 marca el compas del userspace: cada +/// pulso avanza el `reloj` y despierta a las tareas que aguardaban su fotograma. +extern "x86-interrupt" fn irq_temporizador(_marco: InterruptStackFrame) { + crate::async_system::reloj::pulso(); + pic::fin_de_interrupcion(pic::VECTOR_TEMPORIZADOR); +} + +/// IRQ1 — Teclado. Ya no escribe estado rustico: lee el scancode crudo y lo +/// entrega al reactor asincrono, que despertara a la tarea del teclado. +extern "x86-interrupt" fn irq_teclado(_marco: InterruptStackFrame) { + // SEGURIDAD: 0x60 es el puerto de datos del controlador PS/2, fijo en la + // arquitectura PC. Leerlo, ademas, libera al controlador para el siguiente. + let scancode: u8 = unsafe { x86_64::instructions::port::Port::new(0x60).read() }; + crate::async_system::teclado::recibir_scancode(scancode); + pic::fin_de_interrupcion(pic::VECTOR_TECLADO); +} + +/// IRQ del disco — virtio-blk (Fase 6.2). El disco ha terminado una +/// transferencia: `atender_irq` reconoce la interrupcion en el dispositivo +/// —lo que libera su linea— y despierta a la tarea que aguardaba el bloque. +/// Asi la E/S de disco deja de bloquear el planificador por sondeo. +extern "x86-interrupt" fn irq_disco(_marco: InterruptStackFrame) { + crate::drivers::disco::atender_irq(); + // El EOI se cierra DESPUES de reconocer al dispositivo: una IRQ legada de + // PCI es de nivel — anunciar el fin sin haber bajado la linea la reavivaria. + pic::fin_de_interrupcion(VECTOR_DISCO.load(Ordering::SeqCst)); +} diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs new file mode 100644 index 0000000..1c24ec9 --- /dev/null +++ b/renaser/kernel/src/main.rs @@ -0,0 +1,342 @@ +// ============================================================================= +// renaser :: kernel/src/main.rs — el punto de entrada y la orquestacion +// ----------------------------------------------------------------------------- +// Aqui no nace una terminal: nace una superficie. renaser es un kernel +// asincrono de Espacio de Direccionamiento Unico (SASOS) que rompe con el +// paradigma POSIX — sin TTYs, sin archivos planos, sin capas GNU. +// +// Este archivo es deliberadamente delgado: solo el arranque y el cableado. +// Cada subsistema vive en su propio modulo (ver `ARCHITECTURE.md`): +// +// grafico — color, framebuffer fisico y lienzo de doble bufer. +// consola — superficie de texto/imagen; rasteriza glifos con fontdue. +// baliza — red de seguridad visual; manejadores de panico y de OOM. +// sync — `CeldaSync`, la celda de inicializacion unica. +// gdt — GDT + TSS + stack de emergencia del doble fallo. +// interrupts — IDT, excepciones de CPU e interrupciones de hardware. +// pic — el par 8259 remapeado y el temporizador (PIT). +// drivers — descubrimiento de hardware y E/S de disco asincrona por +// interrupcion: el bus PCI y el virtio-blk (Fases 6.1, 6.2). +// almacen — el grafo de objetos direccionado por contenido (Fase 6.1c). +// memory — el heap dinamico del kernel (`#[global_allocator]`). +// async_system — el reactor cooperativo: ejecutor, tareas, wakers, teclado +// y el reloj que marca el compas de los fotogramas (Fase 5). +// texto — rasterizacion de tipografia vectorial (fontdue). +// wasm — el runtime WebAssembly, la matriz de capacidades y el +// escudo de combustible que acota el tiempo de cada app. +// ============================================================================= + +#![no_std] // Prohibido `std`: bajo nosotros no hay sistema operativo alguno. +#![no_main] // El punto de entrada lo define el cargador, no la convencion C. +#![feature(abi_x86_interrupt)] // ABI de las rutinas de excepcion (Fase 2). +#![feature(alloc_error_handler)] // Manejador propio de agotamiento de heap (Fase 3). +#![deny(unsafe_op_in_unsafe_fn)] // Cada operacion `unsafe` se justifica explicitamente. + +extern crate alloc; // El heap esta vivo: `alloc::*` queda disponible (Fase 3). + +use alloc::format; + +use bootloader_api::config::{BootloaderConfig, Mapping}; +use bootloader_api::info::{FrameBufferInfo, MemoryRegionKind, MemoryRegions, PixelFormat}; +use bootloader_api::{entry_point, BootInfo}; +use spin::Mutex; + +// --- Subsistemas del kernel --- +mod almacen; +mod async_system; +mod baliza; +mod consola; +mod drivers; +mod gdt; +mod grafico; +mod interrupts; +mod memory; +mod pic; +mod sync; +mod texto; +mod wasm; + +// Reexportaciones para que los submodulos conserven rutas `crate::` estables. +pub(crate) use consola::volcar_marco_wasm; +pub(crate) use sync::CeldaSync; + +use async_system::executor::Executor; +use baliza::BALIZA_PANICO; +use consola::{Consola, CONSOLA}; +use grafico::{ + codificar, reclamar_memoria_lienzo, Color, Lienzo, Pantalla, RegionPantalla, ALTO_MAX, + ANCHO_MAX, +}; + +/// El modulo WASM del userspace, empotrado en el binario del kernel para esta +/// fase de pruebas. Es un `.wasm` puro, compilado aparte para `wasm32`. La +/// Fase 5 lo instancia DOS veces —el mismo bytecode, dos regiones distintas— +/// para demostrar la multitarea cooperativa sobre el espacio unico. +static APP_WASM: &[u8] = include_bytes!("../assets/app.wasm"); + +/// La aplicacion DISCOLA: un modulo WASM cuyo `tick` cae en un bucle cerrado y +/// jamas retorna. Existe para una sola cosa — demostrar que el guardarrail de +/// combustible la fulmina sin despeinar al kernel ni a sus vecinas. +static DISCOLA_WASM: &[u8] = include_bytes!("../assets/discola.wasm"); + +/// La aplicacion GLOTONA: un modulo WASM que reclama memoria lineal sin freno. +/// Demuestra el guardarrail ESPACIAL — el techo de memoria la desaloja con la +/// baliza amarilla, gemela de la purpura del desalojo por combustible. +static GLOTONA_WASM: &[u8] = include_bytes!("../assets/glotona.wasm"); + +/// La aplicacion CRONISTA: la primera ciudadana del userspace que escribe en el +/// almacenamiento PERSISTENTE. En cada arranque graba un objeto en el grafo +/// —enlazado al del arranque anterior—, lo corona como raiz y pinta una celda +/// por cada arranque registrado. El disco recuerda; la cuenta sobrevive a los +/// reinicios. Demuestra las capacidades `sys_object_*` de la Fase 6.1c. +static CRONISTA_WASM: &[u8] = include_bytes!("../assets/cronista.wasm"); + +/// Configuracion que el cargador `bootloader` aplicara antes de cedernos la CPU. +static CONFIG_ARRANQUE: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + // Pedimos la memoria fisica mapeada: cimiento para futuras fases. + config.mappings.physical_memory = Some(Mapping::Dynamic); + config +}; + +// `entry_point!` genera el simbolo `_start`, valida la firma de `kernel_main` +// y nos transfiere el control con seguridad de tipos. +entry_point!(kernel_main, config = &CONFIG_ARRANQUE); + +/// Detiene la CPU de forma definitiva: `hlt` la duerme hasta una interrupcion. +pub(crate) fn detener() -> ! { + loop { + x86_64::instructions::hlt(); + } +} + +/// Tarea cooperativa de una aplicacion WASM. En cada pulso del reloj le concede +/// un `tick` —un fotograma de trabajo— y cede la CPU hasta el siguiente; entre +/// medias corren sus vecinas. Si la app falla o agota su combustible, se la +/// DESALOJA: se tatua su region con la baliza de purpura y la tarea concluye. +/// El ejecutor la retira del censo, su memoria se libera, el kernel sigue vivo. +async fn tarea_aplicacion(mut app: wasm::AplicacionWasm) { + loop { + async_system::reloj::EsperaFrame::nueva().await; + if let Err(falla) = app.tick() { + // El color de la baliza delata la causa: purpura si agoto su tiempo + // o aborto, amarillo si reviento su techo de memoria. + consola::pintar_desalojo(app.region(), falla.color_baliza()); + return; + } + } +} + +/// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el +/// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las +/// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el +/// bloque esta listo. Deja en la consola el resultado y cuantas interrupciones +/// de disco se atendieron — el testigo de que la E/S por sondeo quedo atras. +async fn tarea_sonda_disco() { + let resultado = drivers::disco::leer_bloques(0, 1).await; + let Some(consola) = CONSOLA.get() else { + return; + }; + let mut consola = consola.lock(); + match resultado { + Ok(_) => consola.escribir(&format!( + "disco :: sonda asincrona OK -- {} IRQ de disco atendidas\n", + drivers::disco::pulsos_disco(), + )), + Err(motivo) => { + consola.escribir(&format!("disco :: sonda asincrona fallida -- {motivo}\n")) + } + } + consola.presentar(); +} + +/// Da vida a una aplicacion del userspace: la carga en su region y, si lo +/// logra, la despacha como tarea cooperativa del reactor. Una carga fallida se +/// salda pintando su region con la baliza de desalojo — el kernel no se inmuta. +fn encender_app(ejecutor: &mut Executor, bytecode: &'static [u8], region: RegionPantalla) { + match wasm::AplicacionWasm::cargar(bytecode, region) { + Ok(app) => ejecutor.spawn(tarea_aplicacion(app)), + Err(_) => consola::pintar_desalojo(region, Color::DESALOJO), + } +} + +/// Localiza la mayor region de RAM libre que el cargador reporto — la cantera +/// de la que el DMA del disco tomara sus marcos fisicos. +fn mayor_region_usable(regiones: &MemoryRegions) -> Option<(u64, u64)> { + regiones + .iter() + .filter(|region| matches!(region.kind, MemoryRegionKind::Usable)) + .map(|region| (region.start, region.end)) + .max_by_key(|&(inicio, fin)| fin - inicio) +} + +/// FASE 6.1c — funda el grafo de objetos. Monta el disco virtio-blk, lee o +/// forja el superbloque, reconstruye el indice recorriendo el log y deja +/// constancia visual: cuantos sectores tiene el disco, cuantos objetos viven +/// ya en el grafo y si el arranque encontro —o no— una raiz de la que tirar. +fn informar_almacen() { + // Fundar el almacen ANTES de tomar el cerrojo de la consola: el montaje + // del disco hace E/S por sondeo y nada de ello reclama la consola. + let resultado = almacen::init(); + let Some(consola) = CONSOLA.get() else { + return; + }; + let mut consola = consola.lock(); + match resultado { + Ok(resumen) => { + let estado = if resumen.formateado { + "disco formateado" + } else { + "grafo montado" + }; + consola.escribir(&format!( + "almacen :: {} :: {} sectores :: {} objetos :: raiz {}\n", + estado, + resumen.capacidad, + resumen.objetos, + if resumen.raiz { "presente" } else { "ausente" }, + )); + } + Err(motivo) => { + consola.escribir(&format!("almacen :: fallo :: {motivo}\n")); + } + } + // FASE 6.2 — dejar constancia de la linea de IRQ por la que el disco + // anunciara, ya sin sondeo, el fin de cada transferencia. + match drivers::disco::irq() { + Some(irq) => { + consola.escribir(&format!("disco :: virtio-blk en IRQ {irq} -- E/S asincrona\n")) + } + None => consola.escribir("disco :: IRQ no enrutada -- E/S por sondeo\n"), + } + consola.presentar(); +} + +// ============================================================================= +// PUNTO DE ENTRADA DEL KERNEL +// ============================================================================= + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + // --- 1. Recuperar el framebuffer GOP que el firmware nos confio. --- + let framebuffer = match boot_info.framebuffer.as_mut() { + Some(fb) => fb, + None => detener(), + }; + let info: FrameBufferInfo = framebuffer.info(); + let formato: PixelFormat = info.pixel_format; + let pantalla = Pantalla::adoptar(framebuffer, info); + + // Datos para la sonda de disco (Fase 6.1b): el offset al que el cargador + // mapeo la memoria fisica y la mayor region de RAM libre para el DMA. + let offset_fisico = boot_info.physical_memory_offset.into_option(); + let region_dma = mayor_region_usable(&boot_info.memory_regions); + + // --- 2. Encender la baliza: la red de seguridad visual va primero. --- + BALIZA_PANICO.encender( + &pantalla, + codificar(formato, Color::ALERTA), + codificar(formato, Color::OOM), + ); + + // --- 3. Cimientos de fallos e interrupciones (Fases 2.0 y 2.1). --- + gdt::init(); + interrupts::init(); + pic::init(); + + // --- 4. FASE 3 :: fundar el heap. A partir de aqui, `alloc` esta vivo. --- + memory::init(); + + // --- 5. Con el heap activo, fundar lo que depende de el: el canal de + // scancodes, el reloj de fotogramas y la tipografia vectorial. --- + async_system::teclado::init(); + async_system::reloj::init(); + texto::init(); + + // --- 6. Construir el lienzo y la consola; pintar el rotulo inicial, + // ya rasterizado por fontdue, y publicar la consola globalmente. --- + let memoria = match reclamar_memoria_lienzo() { + Some(m) => m, + None => detener(), + }; + let mut lienzo = Lienzo::nuevo( + memoria, + info.width.min(ANCHO_MAX), + info.height.min(ALTO_MAX), + formato, + ); + lienzo.limpiar(Color::LIENZO_EN_REPOSO); + + let mut consola = Consola::nueva(lienzo, pantalla); + consola.escribir("renaser :: fase 6.2 -- E/S de disco asincrona por interrupcion\n"); + consola.presentar(); + CONSOLA.call_once(|| Mutex::new(consola)); + + // --- 6.5. FASE 6.1c :: fundar el subsistema de disco y, sobre el, el grafo + // de objetos: enumerar el bus PCI, montar el transporte virtio-blk, + // y leer o forjar el superbloque del almacen direccionado por + // contenido. El kernel adquiere, por fin, una memoria que perdura. --- + match (offset_fisico, region_dma) { + (Some(offset), Some((inicio, fin))) => { + drivers::disco::init(offset, inicio, fin); + informar_almacen(); + } + _ => { + if let Some(consola) = CONSOLA.get() { + let mut consola = consola.lock(); + consola.escribir("virtio-blk :: omitido -- memoria fisica sin mapear\n"); + consola.presentar(); + } + } + } + + // --- 7. FASE 5/6 :: levantar el reactor y poblar el userspace con CINCO + // aplicaciones WASM concurrentes, cada una en su propia region: + // + // * App 1 — instancia de hello_wasm, a la izquierda, gobernada + // por el teclado. + // * App 2 — segunda instancia del MISMO bytecode, a la derecha: + // un unico modulo en la RAM unificada, dos vidas aisladas. + // * App discola — su `tick` es un bucle cerrado: el escudo de + // COMBUSTIBLE la desaloja (baliza purpura) en su 1er fotograma. + // * App glotona — reclama memoria sin freno: el escudo ESPACIAL + // la desaloja (baliza amarilla) en su 1er fotograma. + // * App cronista — escribe en el GRAFO DE OBJETOS persistente: + // cada arranque deja un objeto enlazado al anterior y pinta una + // celda por arranque. El disco recuerda entre reinicios. + // + // Las interrupciones se habilitan AHORA: el temporizador marcara el + // compas de los fotogramas y la IRQ del teclado difundira cada + // scancode a los canales que las apps consultan. --- + let mut ejecutor = Executor::nuevo(); + encender_app( + &mut ejecutor, + APP_WASM, + RegionPantalla { x: 100, y: 120, ancho: 480, alto: 560 }, + ); + encender_app( + &mut ejecutor, + APP_WASM, + RegionPantalla { x: 700, y: 120, ancho: 480, alto: 560 }, + ); + encender_app( + &mut ejecutor, + DISCOLA_WASM, + RegionPantalla { x: 60, y: 700, ancho: 360, alto: 80 }, + ); + encender_app( + &mut ejecutor, + GLOTONA_WASM, + RegionPantalla { x: 460, y: 700, ancho: 360, alto: 80 }, + ); + encender_app( + &mut ejecutor, + CRONISTA_WASM, + RegionPantalla { x: 860, y: 700, ancho: 360, alto: 80 }, + ); + // FASE 6.2 :: una tarea mas del reactor — no una app WASM— que sondea el + // disco de forma ASINCRONA: la demostracion de que la IRQ del disco + // conduce la E/S sin detener a las aplicaciones visuales. + ejecutor.spawn(tarea_sonda_disco()); + x86_64::instructions::interrupts::enable(); + ejecutor.run(); +} diff --git a/renaser/kernel/src/memory/allocator.rs b/renaser/kernel/src/memory/allocator.rs new file mode 100644 index 0000000..4164c04 --- /dev/null +++ b/renaser/kernel/src/memory/allocator.rs @@ -0,0 +1,42 @@ +// ============================================================================= +// renaser :: kernel/src/memory/allocator.rs — Fase 3 :: el asignador global +// ----------------------------------------------------------------------------- +// Reutilizamos un algoritmo probado (`linked_list_allocator`) en lugar de +// arriesgar un asignador propio a bugs de fragmentacion. El heap es una region +// estatica que PERSISTE durante toda la vida del kernel. +// ============================================================================= + +use linked_list_allocator::LockedHeap; + +use crate::CeldaSync; + +/// 64 MiB de heap para el kernel. La arquitectura estimaba 16, pero `fontdue`, +/// al analizar una tipografia real, exige mas holgura; el manejador de OOM —la +/// franja naranja— fue justamente lo que delato esa cota demasiado corta. +const TAM_HEAP: usize = 64 * 1024 * 1024; + +/// Region de respaldo del heap, alineada a pagina, residente en `.bss`. Su +/// campo solo se alcanza via puntero crudo —asi lo exige `GlobalAlloc`—, de ahi +/// el `allow(dead_code)`: la memoria se usa, aunque no por una via «normal». +#[repr(align(4096))] +#[allow(dead_code)] +struct RegionHeap([u8; TAM_HEAP]); + +/// La memoria fisica del heap. Nace a ceros, no engorda el binario. +static REGION_HEAP: CeldaSync = CeldaSync::nueva(RegionHeap([0u8; TAM_HEAP])); + +/// El asignador global. Todo `alloc::*` —`Box`, `Vec`, `BTreeMap`, `Arc`...— +/// se apoya, en silencio, sobre este. +#[global_allocator] +static ASIGNADOR: LockedHeap = LockedHeap::empty(); + +/// Funda el heap del kernel. Debe invocarse UNA sola vez, en el arranque, +/// antes del primer uso de cualquier estructura de `alloc`. +pub fn init() { + let inicio: *mut u8 = REGION_HEAP.puntero().cast::(); + // SEGURIDAD: la region es estatica, de uso exclusivo del asignador, vive + // tanto como el kernel, y `init` se invoca una unica vez en el arranque. + unsafe { + ASIGNADOR.lock().init(inicio, TAM_HEAP); + } +} diff --git a/renaser/kernel/src/memory/mod.rs b/renaser/kernel/src/memory/mod.rs new file mode 100644 index 0000000..c858b3f --- /dev/null +++ b/renaser/kernel/src/memory/mod.rs @@ -0,0 +1,11 @@ +// ============================================================================= +// renaser :: kernel/src/memory — la memoria dinamica del kernel (Fase 3) +// ----------------------------------------------------------------------------- +// El heap no es, en renaser, una utilidad pasiva al estilo C. Existe para +// sostener algo vivo: la cola de futuros y los reactores asincronos sobre los +// que, fase a fase, se ejecutara el bytecode WASM aislado por software. +// ============================================================================= + +pub mod allocator; + +pub use allocator::init; diff --git a/renaser/kernel/src/pic.rs b/renaser/kernel/src/pic.rs new file mode 100644 index 0000000..7e1ff03 --- /dev/null +++ b/renaser/kernel/src/pic.rs @@ -0,0 +1,147 @@ +// ============================================================================= +// renaser :: kernel/src/pic.rs — Fase 2.1 :: el par 8259 y el metronomo +// ----------------------------------------------------------------------------- +// El controlador de interrupciones 8259 (PIC) nace, por herencia historica, +// con sus vectores solapados sobre los de las excepciones de CPU. Antes de +// encender las interrupciones HAY que remapearlo: desplazar sus vectores +// fuera del rango 0..31. Aqui tambien programamos el PIT, el temporizador +// que dara a renaser su latido regular — el origen de su fluidez. +// ============================================================================= + +use x86_64::instructions::port::Port; + +// --- Puertos del par de 8259 (maestro + esclavo en cascada). --- +const CMD_MAESTRO: u16 = 0x20; +const DATOS_MAESTRO: u16 = 0x21; +const CMD_ESCLAVO: u16 = 0xA0; +const DATOS_ESCLAVO: u16 = 0xA1; + +// --- Puertos del temporizador de intervalos programable (PIT). --- +const PIT_COMANDO: u16 = 0x43; +const PIT_CANAL0: u16 = 0x40; +/// Frecuencia base del cristal del PIT, en Hz. +const PIT_BASE_HZ: u32 = 1_193_182; + +/// Orden de «fin de interrupcion» que el PIC espera tras cada IRQ atendida. +const EOI: u8 = 0x20; + +/// Vector base del PIC maestro tras el remapeo (justo encima de las 32 +/// excepciones reservadas de la arquitectura). +const OFFSET_MAESTRO: u8 = 0x20; +/// Vector base del PIC esclavo tras el remapeo. +const OFFSET_ESCLAVO: u8 = 0x28; + +/// Vector de la IRQ0 — el temporizador (PIT). +pub const VECTOR_TEMPORIZADOR: u8 = OFFSET_MAESTRO; // 0x20 +/// Vector de la IRQ1 — el teclado. +pub const VECTOR_TECLADO: u8 = OFFSET_MAESTRO + 1; // 0x21 + +/// Remapea el par 8259 y programa el PIT a 100 Hz. +/// +/// Debe invocarse una sola vez, tras cargar la IDT y ANTES de habilitar las +/// interrupciones con `sti`. +pub fn init() { + remapear(); + configurar_temporizador(100); +} + +/// Reprograma los vectores del 8259 fuera del rango de las excepciones de CPU. +fn remapear() { + // SEGURIDAD: estos son los puertos de E/S estandar del 8259 en la + // arquitectura PC; la secuencia ICW1..ICW4 es su protocolo de inicio. + unsafe { + let mut cmd_m = Port::::new(CMD_MAESTRO); + let mut dat_m = Port::::new(DATOS_MAESTRO); + let mut cmd_e = Port::::new(CMD_ESCLAVO); + let mut dat_e = Port::::new(DATOS_ESCLAVO); + // Escribir en un puerto inerte da al 8259 el respiro que necesita + // entre palabras de control en hardware antiguo. + let mut respiro = Port::::new(0x80); + + // ICW1 — iniciar la secuencia: en cascada, con ICW4 presente. + cmd_m.write(0x11u8); + respiro.write(0u8); + cmd_e.write(0x11u8); + respiro.write(0u8); + // ICW2 — el remapeo en si: desplazar los vectores lejos de 0..31. + dat_m.write(OFFSET_MAESTRO); + respiro.write(0u8); + dat_e.write(OFFSET_ESCLAVO); + respiro.write(0u8); + // ICW3 — declarar el cableado de la cascada (el esclavo en la IRQ2). + dat_m.write(0b0000_0100u8); + respiro.write(0u8); + dat_e.write(0b0000_0010u8); + respiro.write(0u8); + // ICW4 — modo 8086. + dat_m.write(0x01u8); + respiro.write(0u8); + dat_e.write(0x01u8); + respiro.write(0u8); + // Mascaras: el maestro deja pasar la IRQ0 (temporizador) y la IRQ1 + // (teclado); todo lo demas, en silencio hasta que renaser lo reclame. + dat_m.write(0b1111_1100u8); + dat_e.write(0b1111_1111u8); + } +} + +/// Programa el canal 0 del PIT para que emita la IRQ0 a `frecuencia_hz`. +fn configurar_temporizador(frecuencia_hz: u32) { + let divisor = (PIT_BASE_HZ / frecuencia_hz) as u16; + // SEGURIDAD: 0x43 y 0x40 son los puertos estandar del PIT en el PC. + unsafe { + let mut comando = Port::::new(PIT_COMANDO); + let mut canal0 = Port::::new(PIT_CANAL0); + // Canal 0, acceso lobyte+hibyte, modo 3 (generador de onda cuadrada). + comando.write(0x36u8); + canal0.write((divisor & 0xFF) as u8); + canal0.write((divisor >> 8) as u8); + } +} + +/// Notifica al PIC el «fin de interrupcion» de la IRQ recien atendida. Sin +/// este aviso, el PIC jamas volveria a emitir esa interrupcion. +pub fn fin_de_interrupcion(vector: u8) { + // SEGURIDAD: enviar EOI al puerto de comandos del PIC tras atender su IRQ + // es el cierre obligatorio del protocolo del 8259. + unsafe { + // Si la IRQ provino del esclavo, ambos PIC deben recibir el EOI. + if vector >= OFFSET_ESCLAVO { + Port::::new(CMD_ESCLAVO).write(EOI); + } + Port::::new(CMD_MAESTRO).write(EOI); + } +} + +/// Vector de la IDT que corresponde a una linea de IRQ (0..15). El remapeo dejo +/// las 16 lineas del par 8259 en vectores contiguos desde `OFFSET_MAESTRO`: la +/// IRQ8 cae en `OFFSET_ESCLAVO`, que es justo `OFFSET_MAESTRO + 8`. +pub fn vector_irq(irq: u8) -> u8 { + OFFSET_MAESTRO + irq +} + +/// Levanta la mascara de una linea de IRQ — el PIC empezara a emitirla. Si la +/// linea vive en el PIC esclavo (8..15), abre tambien la cascada del maestro +/// (la IRQ2), sin la cual el esclavo jamas alcanzaria a la CPU. +/// +/// Debe invocarse en el arranque, antes de habilitar las interrupciones. +pub fn desenmascarar(irq: u8) { + // SEGURIDAD: 0x21 y 0xA1 son los puertos de mascara del par 8259 en la + // arquitectura PC; leer-modificar-escribir es la via de tocar una linea + // sola sin perturbar a las demas. + unsafe { + if irq < 8 { + let mut datos = Port::::new(DATOS_MAESTRO); + let mascara = datos.read(); + datos.write(mascara & !(1 << irq)); + } else { + let mut datos_e = Port::::new(DATOS_ESCLAVO); + let mascara_e = datos_e.read(); + datos_e.write(mascara_e & !(1 << (irq - 8))); + // La cascada: el esclavo entrega sus IRQ al maestro por la IRQ2. + let mut datos_m = Port::::new(DATOS_MAESTRO); + let mascara_m = datos_m.read(); + datos_m.write(mascara_m & !(1 << 2)); + } + } +} diff --git a/renaser/kernel/src/sync.rs b/renaser/kernel/src/sync.rs new file mode 100644 index 0000000..5af3f1c --- /dev/null +++ b/renaser/kernel/src/sync.rs @@ -0,0 +1,29 @@ +// ============================================================================= +// renaser :: kernel/src/sync.rs — la celda de inicializacion unica +// ----------------------------------------------------------------------------- +// Las estructuras globales del kernel (GDT, TSS, IDT, el heap...) nacen una +// sola vez, durante el arranque secuencial y de un solo hilo, y despues solo +// se leen. `CeldaSync` envuelve ese unico `unsafe` en una abstraccion comun: +// un contrato de unicidad que el codigo de arranque garantiza por construccion. +// ============================================================================= + +use core::cell::UnsafeCell; + +/// Celda `Sync` para estado global de inicializacion unica. +pub(crate) struct CeldaSync(UnsafeCell); + +// SEGURIDAD: cada celda se escribe una sola vez, durante el arranque, antes de +// que existan interrupciones o concurrencia; despues es de solo lectura. +unsafe impl Sync for CeldaSync {} + +impl CeldaSync { + /// Crea una celda con su valor inicial. + pub(crate) const fn nueva(valor: T) -> Self { + CeldaSync(UnsafeCell::new(valor)) + } + + /// Puntero crudo al contenido. Quien lo usa asume el contrato de unicidad. + pub(crate) fn puntero(&self) -> *mut T { + self.0.get() + } +} diff --git a/renaser/kernel/src/texto.rs b/renaser/kernel/src/texto.rs new file mode 100644 index 0000000..9f71bea --- /dev/null +++ b/renaser/kernel/src/texto.rs @@ -0,0 +1,36 @@ +// ============================================================================= +// renaser :: kernel/src/texto.rs — Fase 3 :: el texto como caso del dibujo +// ----------------------------------------------------------------------------- +// Con el heap activo, el texto deja de ser un mapa de bits estatico. Una +// tipografia vectorial se empotra en el binario y se rasteriza glifo a glifo, +// bajo demanda: el texto se vuelve, literalmente, un caso particular del +// dibujo — la promesa fundacional de renaser. +// ============================================================================= + +use alloc::vec::Vec; + +use fontdue::{Font, FontSettings, Metrics}; +use spin::Once; + +/// La tipografia vectorial, empotrada en el propio binario del kernel. +static FUENTE_TTF: &[u8] = include_bytes!("../assets/font.ttf"); + +/// La fuente ya analizada. Se funde una sola vez, tras activar el heap. +static FUENTE: Once = Once::new(); + +/// Analiza la tipografia empotrada. Requiere el heap ya activo. +pub fn init() { + FUENTE.call_once(|| { + Font::from_bytes(FUENTE_TTF, FontSettings::default()) + .expect("renaser :: la tipografia empotrada es invalida") + }); +} + +/// Rasteriza un glifo al vuelo: devuelve sus metricas de colocacion y un mapa +/// de cobertura (un byte de opacidad, 0..=255, por cada pixel del glifo). +pub fn rasterizar(caracter: char, tam_px: f32) -> (Metrics, Vec) { + FUENTE + .get() + .expect("renaser :: la tipografia no fue fundida") + .rasterize(caracter, tam_px) +} diff --git a/renaser/kernel/src/wasm/env.rs b/renaser/kernel/src/wasm/env.rs new file mode 100644 index 0000000..bbed9d1 --- /dev/null +++ b/renaser/kernel/src/wasm/env.rs @@ -0,0 +1,349 @@ +// ============================================================================= +// renaser :: kernel/src/wasm/env.rs — Fase 4/5/6 :: la matriz de capacidades +// ----------------------------------------------------------------------------- +// El aislamiento de renaser no descansa en `int 0x80` ni en `sysenter`: no hay +// vectores de syscall. Una aplicacion WASM solo puede hacer aquello para lo +// que el kernel le haya inyectado una FUNCION DEL HOST. Esta matriz concede: +// +// * sys_render_frame — componer un fotograma en su region de pantalla; +// * sys_get_scancode — consultar su canal de teclado; +// * sys_object_put — grabar un objeto en el grafo (Fase 6.1c); +// * sys_object_datos — leer la carga util de un objeto; +// * sys_object_hijo — recorrer las aristas del DAG; +// * sys_object_raiz — leer la raiz del grafo; +// * sys_object_fijar_raiz — coronar un objeto como raiz. +// +// GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le +// entrega contra los limites reales de su memoria lineal. No se confia en que +// el runtime lo haga; se verifica aqui, antes de leer o escribir un solo byte. +// +// DOS CLASES DE FALLO. Un puntero fuera de limites es CULPA DE LA APP: se +// devuelve un `Error` que la ABORTA (el kernel la captura y la desaloja). Un +// fallo del almacenamiento —disco, objeto inexistente— NO es culpa de la app: +// se le devuelve un codigo de error negativo, y la app decide que hacer. +// ============================================================================= + +use wasmi::{Caller, Error, Extern, Linker, Memory, StoreLimits}; + +use crate::almacen::Hash; +use crate::async_system::teclado::CanalTeclado; +use crate::grafico::RegionPantalla; + +/// El estado del host adscrito al `Store` de una aplicacion: cuanto necesita +/// una capacidad para servir a ESA app y a ninguna otra — su region de pantalla, +/// su canal de teclado y sus cuotas de recursos. Dos apps jamas comparten nada. +pub(crate) struct ContextoCapacidades { + /// La sub-region de pantalla asignada a la aplicacion. + pub(crate) region: RegionPantalla, + /// El canal de teclado propio de la aplicacion. + pub(crate) canal: CanalTeclado, + /// El techo de recursos de la aplicacion — hoy, su memoria lineal maxima. + /// `wasmi` lo consulta en cada `memory.grow` via `Store::limiter`. + pub(crate) limites: StoreLimits, +} + +/// Recupera la memoria lineal exportada por el modulo. Que no la exporte es un +/// modulo mal formado: se aborta. +fn obtener_memoria(caller: &Caller<'_, ContextoCapacidades>) -> Result { + caller + .get_export("memory") + .and_then(Extern::into_memory) + .ok_or_else(|| Error::new("WASM :: el modulo no exporta su memoria lineal")) +} + +/// VALIDACION INFRANQUEABLE DE LIMITES. Comprueba que `[ptr, ptr + len)` cae +/// entera dentro de la memoria lineal `m` y devuelve ese sub-slice. Un rango +/// que se desborde aborta la app — el `Error` se traduce en una trampa de WASM. +fn rango<'a>(m: &'a [u8], ptr: u32, len: usize, fallo: &'static str) -> Result<&'a [u8], Error> { + let inicio = ptr as usize; + match inicio.checked_add(len) { + Some(fin) if fin <= m.len() => Ok(&m[inicio..fin]), + _ => Err(Error::new(fallo)), + } +} + +/// Lee un hash de 32 bytes de la memoria lineal, con sus limites verificados. +fn leer_hash(m: &[u8], ptr: u32, fallo: &'static str) -> Result { + let bytes = rango(m, ptr, 32, fallo)?; + let mut hash = [0u8; 32]; + hash.copy_from_slice(bytes); + Ok(hash) +} + +/// Inyecta en el enlazador la matriz de capacidades del modulo WASM. Todo lo +/// que no se defina aqui le queda, al modulo, fisicamente fuera de alcance. +/// +/// Devuelve `Err` si una capacidad no se pudo enlazar — un fallo del kernel, +/// no de la app; aun asi se propaga como `Result` para no incendiar nada. +pub(crate) fn enlazar_capacidades( + enlazador: &mut Linker, +) -> Result<(), Error> { + // --- CAPACIDAD 1 :: sys_render_frame(ptr, len) --- + // El modulo entrega (ptr, len) hacia su PROPIA memoria lineal; el kernel + // valida esos limites y, solo entonces, compone el fotograma DENTRO de la + // region asignada a la app. + enlazador.func_wrap( + "renaser", + "sys_render_frame", + |caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result<(), Error> { + let region = caller.data().region; + + // El fotograma debe medir EXACTAMENTE los pixeles de la region. Un + // tamaño distinto delata a una app que pinta fuera de su ventana: + // se aborta antes de tocar un byte. + let esperado = region.pixeles() * 4; + if len as usize != esperado { + return Err(Error::new( + "WASM :: sys_render_frame con un fotograma ajeno a la region asignada", + )); + } + + let memoria = obtener_memoria(&caller)?; + let datos: &[u8] = memoria.data(&caller); + + // VALIDACION INFRANQUEABLE: si (ptr, len) se sale de la memoria + // lineal del modulo, se aborta la app —no el kernel—. + let marco = rango( + datos, + ptr, + len as usize, + "WASM :: sys_render_frame desbordo la memoria lineal del modulo", + )?; + + // Limites verificados: la region es segura de leer y componer. + crate::volcar_marco_wasm(region, marco); + Ok(()) + }, + )?; + + // --- CAPACIDAD 2 :: sys_get_scancode() -> u32 --- + // Expone, sin bloquear, el siguiente scancode del canal PROPIO de la app. + enlazador.func_wrap( + "renaser", + "sys_get_scancode", + |caller: Caller<'_, ContextoCapacidades>| -> u32 { + caller.data().canal.pop().unwrap_or(0) as u32 + }, + )?; + + // --- CAPACIDAD 3 :: sys_object_put(datos, datos_len, hijos, hijos_cnt, salida) -> i32 --- + // Graba un objeto en el grafo. El modulo entrega, en su memoria lineal, la + // carga util y un arreglo de `hijos_cnt` hashes de 32 bytes (las aristas). + // El kernel escribe el hash resultante —la identidad del objeto— en + // `salida`. Devuelve 0 si el objeto se grabo (o ya existia), -1 si el + // almacenamiento fallo. + enlazador.func_wrap( + "renaser", + "sys_object_put", + |mut caller: Caller<'_, ContextoCapacidades>, + datos_ptr: u32, + datos_len: u32, + hijos_ptr: u32, + hijos_cnt: u32, + salida: u32| + -> Result { + let memoria = obtener_memoria(&caller)?; + + // --- Leer las entradas de la memoria lineal, con limites firmes. --- + let (datos, hijos) = { + let m = memoria.data(&caller); + + let datos = rango( + m, + datos_ptr, + datos_len as usize, + "WASM :: sys_object_put desbordo la memoria lineal (datos)", + )? + .to_vec(); + + // El arreglo de hijos: `hijos_cnt` hashes contiguos de 32 bytes. + let bytes_hijos = (hijos_cnt as usize).checked_mul(32).ok_or_else(|| { + Error::new("WASM :: sys_object_put con un conteo de hijos imposible") + })?; + let crudo = rango( + m, + hijos_ptr, + bytes_hijos, + "WASM :: sys_object_put desbordo la memoria lineal (hijos)", + )?; + let mut hijos: alloc::vec::Vec = + alloc::vec::Vec::with_capacity(hijos_cnt as usize); + for trozo in crudo.chunks_exact(32) { + let mut h = [0u8; 32]; + h.copy_from_slice(trozo); + hijos.push(h); + } + + // Verificar que el hash de salida cabe ANTES de tocar el disco. + rango( + m, + salida, + 32, + "WASM :: sys_object_put desbordo la memoria lineal (salida)", + )?; + + (datos, hijos) + }; + + // --- Grabar. Un fallo del almacen NO es culpa de la app: -1. --- + match crate::almacen::almacenar(datos, hijos) { + Ok(hash) => { + let m = memoria.data_mut(&mut caller); + m[salida as usize..salida as usize + 32].copy_from_slice(&hash); + Ok(0) + } + Err(_) => Ok(-1), + } + }, + )?; + + // --- CAPACIDAD 4 :: sys_object_datos(hash, salida, capacidad) -> i32 --- + // Copia la carga util del objeto `hash` en `salida`. Devuelve el numero de + // bytes copiados, o -1 si el objeto no existe, -2 si `capacidad` no basta, + // -3 si el almacenamiento fallo. + enlazador.func_wrap( + "renaser", + "sys_object_datos", + |mut caller: Caller<'_, ContextoCapacidades>, + hash_ptr: u32, + salida: u32, + capacidad: u32| + -> Result { + let memoria = obtener_memoria(&caller)?; + + let hash = { + let m = memoria.data(&caller); + leer_hash( + m, + hash_ptr, + "WASM :: sys_object_datos desbordo la memoria lineal (hash)", + )? + }; + + let objeto = match crate::almacen::recuperar(&hash) { + Ok(Some(objeto)) => objeto, + Ok(None) => return Ok(-1), + Err(_) => return Ok(-3), + }; + if objeto.datos.len() > capacidad as usize { + return Ok(-2); + } + + // Verificar que el destino cabe, y solo entonces copiar. + { + let m = memoria.data(&caller); + rango( + m, + salida, + objeto.datos.len(), + "WASM :: sys_object_datos desbordo la memoria lineal (salida)", + )?; + } + let n = objeto.datos.len(); + let m = memoria.data_mut(&mut caller); + m[salida as usize..salida as usize + n].copy_from_slice(&objeto.datos); + Ok(n as i32) + }, + )?; + + // --- CAPACIDAD 5 :: sys_object_hijo(hash, indice, salida) -> i32 --- + // Recorre las aristas del DAG. Devuelve el NUMERO de hijos del objeto + // `hash`; si `indice` es valido, ademas escribe el hash de ese hijo en + // `salida`. Devuelve -1 si el objeto no existe, -3 si el almacen fallo. + enlazador.func_wrap( + "renaser", + "sys_object_hijo", + |mut caller: Caller<'_, ContextoCapacidades>, + hash_ptr: u32, + indice: u32, + salida: u32| + -> Result { + let memoria = obtener_memoria(&caller)?; + + let hash = { + let m = memoria.data(&caller); + leer_hash( + m, + hash_ptr, + "WASM :: sys_object_hijo desbordo la memoria lineal (hash)", + )? + }; + + let objeto = match crate::almacen::recuperar(&hash) { + Ok(Some(objeto)) => objeto, + Ok(None) => return Ok(-1), + Err(_) => return Ok(-3), + }; + let total = objeto.hijos.len(); + + // Si el indice apunta a un hijo real, entregar su hash. + if let Some(hijo) = objeto.hijos.get(indice as usize) { + { + let m = memoria.data(&caller); + rango( + m, + salida, + 32, + "WASM :: sys_object_hijo desbordo la memoria lineal (salida)", + )?; + } + let m = memoria.data_mut(&mut caller); + m[salida as usize..salida as usize + 32].copy_from_slice(hijo); + } + Ok(total as i32) + }, + )?; + + // --- CAPACIDAD 6 :: sys_object_raiz(salida) -> i32 --- + // Escribe en `salida` el hash de la raiz del grafo. Devuelve 1 si hay + // raiz, 0 si el grafo aun no tiene ninguna. + enlazador.func_wrap( + "renaser", + "sys_object_raiz", + |mut caller: Caller<'_, ContextoCapacidades>, salida: u32| -> Result { + let memoria = obtener_memoria(&caller)?; + match crate::almacen::raiz() { + Some(hash) => { + { + let m = memoria.data(&caller); + rango( + m, + salida, + 32, + "WASM :: sys_object_raiz desbordo la memoria lineal (salida)", + )?; + } + let m = memoria.data_mut(&mut caller); + m[salida as usize..salida as usize + 32].copy_from_slice(&hash); + Ok(1) + } + None => Ok(0), + } + }, + )?; + + // --- CAPACIDAD 7 :: sys_object_fijar_raiz(hash) -> i32 --- + // Corona el objeto `hash` como raiz del grafo. Devuelve 0 si se logro, -3 + // si el almacenamiento fallo. + enlazador.func_wrap( + "renaser", + "sys_object_fijar_raiz", + |caller: Caller<'_, ContextoCapacidades>, hash_ptr: u32| -> Result { + let memoria = obtener_memoria(&caller)?; + let hash = { + let m = memoria.data(&caller); + leer_hash( + m, + hash_ptr, + "WASM :: sys_object_fijar_raiz desbordo la memoria lineal (hash)", + )? + }; + match crate::almacen::fijar_raiz(hash) { + Ok(()) => Ok(0), + Err(_) => Ok(-3), + } + }, + )?; + + Ok(()) +} diff --git a/renaser/kernel/src/wasm/mod.rs b/renaser/kernel/src/wasm/mod.rs new file mode 100644 index 0000000..5710331 --- /dev/null +++ b/renaser/kernel/src/wasm/mod.rs @@ -0,0 +1,200 @@ +// ============================================================================= +// renaser :: kernel/src/wasm — Fase 4/5 :: el escudo de aislamiento (WASM) +// ----------------------------------------------------------------------------- +// Aqui renaser sustituye las costosas fronteras de hardware (la MMU, los +// anillos de la CPU) por limites MATEMATICOS sobre el bytecode. Una aplicacion +// WebAssembly se ejecuta en su propia memoria lineal; sus unicas puertas al +// exterior son las capacidades que el enlazador del host le concede. Lo que +// no este importado no existe: no hay camino fisico para ejecutarlo. +// +// FASE 5 :: el aislamiento deja de ser solo ESPACIAL (memoria) y pasa a ser +// tambien TEMPORAL (tiempo de CPU). Cada `tick` se ejecuta con un presupuesto +// estricto de COMBUSTIBLE (fuel): si una app lo agota —un bucle infinito, un +// trabajo desmedido—, el runtime lanza una trampa, el kernel recupera el mando +// y la desaloja. Ningun modulo, por discolo que sea, secuestra el procesador. +// ============================================================================= + +pub mod env; + +use wasmi::{ + CompilationMode, Config, Engine, Linker, Module, Store, StoreLimitsBuilder, TrapCode, TypedFunc, +}; + +use crate::grafico::{Color, RegionPantalla}; +use env::ContextoCapacidades; + +/// Combustible concedido a `init`. Cubre con holgura el pintado inicial del +/// fondo de una region a pantalla casi completa — un gasto unico, de arranque. +const FUEL_ARRANQUE: u64 = 20_000_000; + +/// Combustible concedido a cada `tick`. Sobra para un fotograma honesto (unos +/// cientos de miles de operaciones); una app en bucle infinito lo agota en +/// milisegundos y es desalojada. Este numero ES el techo temporal del userspace. +const FUEL_FOTOGRAMA: u64 = 2_000_000; + +/// Techo de memoria lineal por aplicacion: 4 MiB. Un modulo que intente crecer +/// su memoria mas alla es desalojado — el aislamiento ESPACIAL del userspace, +/// gemelo del techo TEMPORAL que impone el combustible. +const TECHO_MEMORIA: usize = 4 * 1024 * 1024; + +/// Por que el kernel da por terminada —desaloja— una aplicacion WASM. +#[derive(Clone, Copy)] +pub enum FallaApp { + /// El modulo no se pudo cargar, validar, enlazar o instanciar. + Carga, + /// La aplicacion agoto su combustible dentro de un `tick`: bucle infinito + /// o trabajo desmedido. El guardarrail TEMPORAL en accion. + SinCombustible, + /// La aplicacion intento crecer su memoria lineal mas alla de su cuota. + /// El guardarrail ESPACIAL en accion. + SinMemoria, + /// La aplicacion ejecuto una trampa: acceso fuera de limites, instruccion + /// `unreachable`, una capacidad violada... su propio codigo la abortó. + Trampa, +} + +impl FallaApp { + /// El color de la baliza de desalojo segun la causa de la falla: amarillo + /// palido si reviento su techo de memoria, purpura para cualquier otra. + pub fn color_baliza(self) -> Color { + match self { + FallaApp::SinMemoria => Color::DESALOJO_MEMORIA, + _ => Color::DESALOJO, + } + } +} + +/// Una aplicacion WebAssembly viva: su estado PERSISTE entre fotogramas. A +/// diferencia de la Fase 4 —que instanciaba y cedia el control de un gesto—, +/// aqui la instancia se conserva y el kernel la hace avanzar `tick` a `tick`. +pub struct AplicacionWasm { + /// El almacen: todo el estado de ESTA instancia — su memoria lineal, sus + /// globales y el contexto de capacidades con su region de pantalla. + almacen: Store, + /// El punto de entrada de fotograma, ya resuelto y con seguridad de tipos. + /// `TypedFunc` es un asa autosuficiente dentro del `Store`: conservada esta, + /// el handle de la `Instance` no aporta nada y no se retiene. + func_tick: TypedFunc<(), ()>, + /// La region de pantalla de la app — su ventana, y donde se tatua su baliza + /// de desalojo si llega a fallar. + region: RegionPantalla, +} + +impl AplicacionWasm { + /// Carga, valida, instancia y arranca una aplicacion WASM aislada, ligada a + /// una region de pantalla. Si algo falla en el camino, se devuelve la falla + /// en lugar de incendiar el kernel. + /// + /// El nuevo ABI del userspace exige dos exportaciones: `init` —invocada una + /// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por + /// el reactor en cada pulso del reloj. + pub fn cargar(bytecode: &[u8], region: RegionPantalla) -> Result { + // 1. El motor, con metricas de combustible y compilacion ANTICIPADA: la + // traduccion del modulo ocurre ahora, de modo que el `fuel` mida + // despues solo EJECUCION, jamas compilacion diferida. + let mut config = Config::default(); + config.consume_fuel(true); + config.compilation_mode(CompilationMode::Eager); + let motor = Engine::new(&config); + + // 2. Validar y traducir el modulo — ya instrumentado con fuel. + let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?; + + // 3. El almacen, con el contexto de capacidades de ESTA app: su region + // de pantalla, su canal de teclado y su techo de memoria. El canal + // se crea ahora pero se inscribe en la difusion de la IRQ1 al final, + // ya con la app cargada: una carga fallida no deja canales huerfanos. + let canal = crate::async_system::teclado::crear_canal(); + let limites = StoreLimitsBuilder::new() + .memory_size(TECHO_MEMORIA) + // Una expansion denegada se convierte en TRAMPA, no en un -1 que la + // app pudiera ignorar: asi el kernel la captura y la desaloja. + .trap_on_grow_failure(true) + .build(); + let mut almacen = Store::new( + &motor, + ContextoCapacidades { + region, + canal, + limites, + }, + ); + // Ligar el limitador de recursos: `wasmi` lo consultara en cada + // `memory.grow`, tambien durante la instanciacion. + almacen.limiter(|contexto| &mut contexto.limites); + // Dotar de combustible ANTES de instanciar: la instanciacion no debe + // quedarse a cero y abortar. + almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?; + + // 4. El enlazador y la matriz de capacidades (ver `env`). + let mut enlazador: Linker = Linker::new(&motor); + env::enlazar_capacidades(&mut enlazador).map_err(|_| FallaApp::Carga)?; + + // 5. Instanciar, resolviendo las importaciones contra las capacidades. + let instancia = enlazador + .instantiate_and_start(&mut almacen, &modulo) + .map_err(|_| FallaApp::Carga)?; + + // 6. Resolver los dos puntos del ABI de fotograma: `init` y `tick`. + let func_init = instancia + .get_typed_func::<(), ()>(&almacen, "init") + .map_err(|_| FallaApp::Carga)?; + let func_tick = instancia + .get_typed_func::<(), ()>(&almacen, "tick") + .map_err(|_| FallaApp::Carga)?; + + // 7. Arranque unico: `init` prepara el estado inicial de la aplicacion. + almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?; + func_init + .call(&mut almacen, ()) + .map_err(|_| FallaApp::Carga)?; + + // 8. Con la app ya cargada e instanciada, inscribir su canal de teclado + // en la difusion de la IRQ1: desde aqui recibe cada pulsacion. + crate::async_system::teclado::registrar_canal(&almacen.data().canal); + + Ok(AplicacionWasm { + almacen, + func_tick, + region, + }) + } + + /// Hace avanzar la aplicacion un fotograma. Recarga su presupuesto de + /// combustible y le cede el control con `tick`. Si la app lo agota o ejecuta + /// una trampa, el kernel recupera el mando y la falla se devuelve para que + /// la tarea proceda al desalojo. El kernel nunca pierde el control. + pub fn tick(&mut self) -> Result<(), FallaApp> { + // Recargar el deposito: cada fotograma parte con su techo intacto. + self.almacen + .set_fuel(FUEL_FOTOGRAMA) + .map_err(|_| FallaApp::Trampa)?; + + match self.func_tick.call(&mut self.almacen, ()) { + Ok(()) => Ok(()), + // `as_trap_code` da un codigo publico y univoco para cada causa: + // `OutOfFuel` pliega toda variante de agotamiento de combustible; + // `GrowthOperationLimited` es la cuota de memoria denegada. + Err(error) => match error.as_trap_code() { + Some(TrapCode::OutOfFuel) => Err(FallaApp::SinCombustible), + Some(TrapCode::GrowthOperationLimited) => Err(FallaApp::SinMemoria), + _ => Err(FallaApp::Trampa), + }, + } + } + + /// La region de pantalla asignada a la aplicacion. + pub fn region(&self) -> RegionPantalla { + self.region + } +} + +/// Reconciliacion del ciclo de vida. Cuando una `AplicacionWasm` muere —porque +/// fue desalojada y su tarea concluyo—, su canal de teclado debe darse de baja +/// de la difusion de la IRQ1. Sin esto, el manejador de interrupciones seguiria +/// empujando scancodes a una cola muerta: una fuga lenta pero segura. +impl Drop for AplicacionWasm { + fn drop(&mut self) { + crate::async_system::teclado::cerrar_canal(&self.almacen.data().canal); + } +} diff --git a/renaser/rust-toolchain.toml b/renaser/rust-toolchain.toml new file mode 100644 index 0000000..84f180a --- /dev/null +++ b/renaser/rust-toolchain.toml @@ -0,0 +1,14 @@ +# ============================================================================= +# renaser :: fijacion del toolchain +# ----------------------------------------------------------------------------- +# Nightly por dos razones que PERSISTEN: las dependencias de artefacto +# (`-Zbindeps`) y el script de construccion de la crate `bootloader`, que +# compila su propio cargador UEFI/BIOS y necesita `rust-src` + `llvm-tools`. +# El kernel en si ya NO necesita `build-std`: usa el target nativo precompilado. +# ============================================================================= + +[toolchain] +channel = "nightly" +components = ["rust-src", "llvm-tools", "rustfmt", "clippy"] +targets = ["x86_64-unknown-none", "x86_64-unknown-uefi"] +profile = "minimal"