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:
sergio
2026-05-22 14:37:14 +00:00
parent 1c6aafbc24
commit e2272c0ed3
55 changed files with 6668 additions and 0 deletions
+292
View File
@@ -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>(&sector0) {
// 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, &sector0)
}
/// 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, &registro)?;
// 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)
}
+103
View File
@@ -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();
}
}
}
+16
View File
@@ -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;
+101
View File
@@ -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
}
}
}
+45
View File
@@ -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);
}
}
}
+55
View File
@@ -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 }))
}
+128
View File
@@ -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()
}
+186
View File
@@ -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);
}
}
+562
View File
@@ -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(|_| ())
}
+15
View File
@@ -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;
+76
View File
@@ -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
}
}
+80
View File
@@ -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);
}
}
+325
View File
@@ -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);
}
}
}
}
}
+148
View File
@@ -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));
}
+342
View File
@@ -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();
}
+42
View File
@@ -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);
}
}
+11
View File
@@ -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;
+147
View File
@@ -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));
}
}
}
+29
View File
@@ -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()
}
}
+36
View File
@@ -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)
}
+349
View File
@@ -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(())
}
+200
View File
@@ -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);
}
}