feat: integra renaser (kernel SASOS bare-metal) al monorepo
renaser —kernel asíncrono de espacio de direcciones único, no-POSIX, `no_std` x86_64— entra al monorepo como su PROPIO workspace de Cargo, no fusionado: usa toolchain nightly, target `x86_64-unknown-none` y `panic = "abort"`, incompatibles con los perfiles globales de brahman. - `renaser/` — copia del proyecto (sin su `.git`; el repo original conserva su historia standalone). Workspace propio con su `rust-toolchain.toml` y `.cargo/`. - `exclude = ["renaser"]` en el workspace de brahman: Cargo lo trata como ajeno. - El kernel de renaser path-depende `mirada-layout` cruzando la frontera de workspace — primer núcleo compartido. Semilla de la Fase 8 (compositor): geometría de teselado compartida, framebuffer nativo de renaser; smithay se queda en el lado Linux. Verificado: `cargo build -p boot` compila kernel + imagen UEFI con mirada-layout enlazado para bare-metal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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"
|
||||
@@ -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
|
||||
@@ -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<Box<dyn Future>>`) 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`.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Generated
+1178
File diff suppressed because it is too large
Load Diff
@@ -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 <gerencia@jlsoltech.com>"]
|
||||
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
|
||||
@@ -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.*
|
||||
@@ -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
|
||||
@@ -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.
|
||||
Generated
+7
@@ -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"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Generated
+7
@@ -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"
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+7
@@ -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"
|
||||
@@ -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
|
||||
@@ -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 {}
|
||||
}
|
||||
Generated
+7
@@ -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"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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<String, String> {
|
||||
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=<ruta a OVMF.fd>"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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}")),
|
||||
}
|
||||
}
|
||||
@@ -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 <gerencia@jlsoltech.com>"]
|
||||
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" }
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -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<u8>,
|
||||
/// Los hashes de los objetos hijos: las aristas salientes del DAG.
|
||||
pub hijos: Vec<Hash>,
|
||||
}
|
||||
|
||||
/// 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<Hash>,
|
||||
}
|
||||
|
||||
/// 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<Hash>,
|
||||
/// Indice hash -> sector del registro. Se reconstruye al arrancar.
|
||||
indice: BTreeMap<Hash, u64>,
|
||||
/// Capacidad del disco, en sectores.
|
||||
capacidad: u64,
|
||||
}
|
||||
|
||||
/// El almacen global de renaser. Se funde una sola vez, en `init`.
|
||||
static ALMACEN: Once<Mutex<Almacen>> = 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<Resumen, &'static str> {
|
||||
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::<SuperBloque>(§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<BTreeMap<Hash, u64>, &'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<Option<Vec<u8>>, &'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<u8>, hijos: Vec<Hash>) -> Result<Hash, &'static str> {
|
||||
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<Option<Objeto>, &'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::<Objeto>(&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<Hash> {
|
||||
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)
|
||||
}
|
||||
@@ -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<TaskId, Task>,
|
||||
/// Cola de tareas listas para su proximo avance.
|
||||
cola_listas: ColaListas,
|
||||
/// Cache de wakers: uno por tarea, reutilizado entre avances.
|
||||
cache_wakers: BTreeMap<TaskId, Waker>,
|
||||
}
|
||||
|
||||
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<Output = ()> + 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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<Mutex<Vec<Waker>>> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Box<dyn Future<Output = ()> + Send + 'static>>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
/// Envuelve un `Future` en una tarea con identidad propia.
|
||||
pub fn nueva(futuro: impl Future<Output = ()> + 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)
|
||||
}
|
||||
}
|
||||
@@ -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<ArrayQueue<u8>>;
|
||||
|
||||
/// 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<Mutex<Vec<CanalTeclado>>> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Mutex<VecDeque<TaskId>>>;
|
||||
|
||||
/// 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>) {
|
||||
self.reinyectar();
|
||||
}
|
||||
|
||||
fn wake_by_ref(self: &Arc<Self>) {
|
||||
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 }))
|
||||
}
|
||||
@@ -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<u8>,
|
||||
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()
|
||||
}
|
||||
@@ -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<Mutex<Consola>> = 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);
|
||||
}
|
||||
}
|
||||
@@ -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<u64>,
|
||||
}
|
||||
|
||||
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<u64> {
|
||||
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<Mutex<AsignadorMarcos>> = 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<u8>) {
|
||||
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<u8>, 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<u8> {
|
||||
// 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<KernelHal, PciTransport>);
|
||||
|
||||
// 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<Mutex<Disco>> = 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<Option<Waker>> = 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<u64, &'static str> {
|
||||
let mut raiz = PciRoot::new(CamPuertos);
|
||||
|
||||
// 1. Localizar el primer disco virtio-blk recorriendo el bus.
|
||||
let mut hallado: Option<DeviceFunction> = 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::<KernelHal, _>(&mut raiz, device_function)
|
||||
.map_err(|_| "no se pudo montar el transporte PCI de virtio")?;
|
||||
let mut disco = VirtIOBlk::<KernelHal, _>::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<u8> {
|
||||
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<BlkReq>,
|
||||
/// Respuesta de estado del dispositivo. En el heap, por la misma razon.
|
||||
resp: Box<BlkResp>,
|
||||
/// Los datos: destino de una lectura, origen de una escritura.
|
||||
buf: Vec<u8>,
|
||||
/// 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<u16>,
|
||||
}
|
||||
|
||||
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<Result<Vec<u8>, &'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<Vec<u8>, &'static str>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, contexto: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// `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<u8>) -> 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<F: Future>(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(|_| ())
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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::<u32>::new(CONFIG_ADDRESS).write(direccion(device_function, offset));
|
||||
Port::<u32>::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::<u32>::new(CONFIG_ADDRESS).write(direccion(device_function, offset));
|
||||
Port::<u32>::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
|
||||
}
|
||||
}
|
||||
@@ -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<TaskStateSegment> = CeldaSync::nueva(TaskStateSegment::new());
|
||||
|
||||
/// La Global Descriptor Table propia de renaser.
|
||||
static GDT: CeldaSync<GlobalDescriptorTable> = 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);
|
||||
}
|
||||
}
|
||||
@@ -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::<u32>(), 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::<u16>(), 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<I>(&mut self, pixeles: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<InterruptDescriptorTable> =
|
||||
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));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<RegionHeap> = 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::<u8>();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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::<u8>::new(CMD_MAESTRO);
|
||||
let mut dat_m = Port::<u8>::new(DATOS_MAESTRO);
|
||||
let mut cmd_e = Port::<u8>::new(CMD_ESCLAVO);
|
||||
let mut dat_e = Port::<u8>::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::<u8>::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::<u8>::new(PIT_COMANDO);
|
||||
let mut canal0 = Port::<u8>::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::<u8>::new(CMD_ESCLAVO).write(EOI);
|
||||
}
|
||||
Port::<u8>::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::<u8>::new(DATOS_MAESTRO);
|
||||
let mascara = datos.read();
|
||||
datos.write(mascara & !(1 << irq));
|
||||
} else {
|
||||
let mut datos_e = Port::<u8>::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::<u8>::new(DATOS_MAESTRO);
|
||||
let mascara_m = datos_m.read();
|
||||
datos_m.write(mascara_m & !(1 << 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T>(UnsafeCell<T>);
|
||||
|
||||
// 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<T> Sync for CeldaSync<T> {}
|
||||
|
||||
impl<T> CeldaSync<T> {
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
@@ -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<Font> = 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<u8>) {
|
||||
FUENTE
|
||||
.get()
|
||||
.expect("renaser :: la tipografia no fue fundida")
|
||||
.rasterize(caracter, tam_px)
|
||||
}
|
||||
@@ -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<Memory, Error> {
|
||||
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<Hash, Error> {
|
||||
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<ContextoCapacidades>,
|
||||
) -> 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<i32, Error> {
|
||||
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<Hash> =
|
||||
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<i32, Error> {
|
||||
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<i32, Error> {
|
||||
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<i32, Error> {
|
||||
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<i32, Error> {
|
||||
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(())
|
||||
}
|
||||
@@ -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<ContextoCapacidades>,
|
||||
/// 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<AplicacionWasm, FallaApp> {
|
||||
// 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<ContextoCapacidades> = 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);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user