tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Definitions

⎇ 07ab095d4202aa5ff30d9a109e610d517a434e74 renaser/boot/src/main.rs Rust · 355 lines · 16.2 KB History Who touched what raw

feat(renaser): Fase 19 — voz del userspace hacia la red (pregon) 07ab095d4 · sergio ·

1// =============================================================================
2//  renaser :: boot/src/main.rs — Fase 1.5 :: el puente hacia el silicio
3// -----------------------------------------------------------------------------
4//  Un kernel bare-metal no nace solo: alguien debe fusionarlo con un cargador,
5//  sellarlo en una imagen de disco arrancable y entregarlo al hardware. Esa es
6//  la mision de este orquestador de ANFITRION.
7//
8//  Desde la Fase 7b hace algo mas: SIEMBRA el grafo. El kernel ya no empotra
9//  el userspace —ni un solo `include_bytes!` de un `.wasm`—; en su lugar, este
10//  constructor pre-puebla el disco de objetos con el bytecode de las apps de
11//  genesis y el Manifiesto de Genesis que dicta cuales arrancan, en que region
12//  y con que cuota. Para ello habla el MISMO formato del grafo que el kernel,
13//  a traves de la crate compartida `formato`.
14//
15//  El flujo es deliberadamente lineal y sin ambiguedad:
16//
17//    1. Localizar el ELF nativo del kernel (lo inyecta la dep. de artefacto).
18//    2. Fusionarlo con el cargador UEFI en una imagen de disco GPT.
19//    3. Sembrar el disco de objetos: el grafo poblado con el bytecode del
20//       userspace y el Manifiesto de Genesis (Fase 7b).
21//    4. Lanzar QEMU con la imagen, el disco de objetos y el firmware OVMF.
22//
23//  Cada paso que pueda fallar lo hace en voz alta, con un mensaje accionable:
24//  preferimos un error claro a un arranque silencioso hacia la nada.
25// =============================================================================
26
27use std::collections::BTreeMap;
28use std::io::Write;
29use std::path::{Path, PathBuf};
30use std::process::Command;
31
32// El formato del grafo de objetos en disco — el MISMO nucleo `no_std` que
33// enlaza el kernel. Gracias a el, lo que `boot` siembra y lo que el kernel lee
34// es, byte a byte, el mismo idioma.
35use formato::{
36    EntradaApp, Hash, Manifiesto, Objeto, SuperBloque, MAGIA, MAX_OBJETO, TAM_SECTOR,
37    VERSION_MANIFIESTO, VERSION_SUPERBLOQUE,
38};
39
40/// Ruta del ELF del kernel, ya compilado para `x86_64-unknown-none`.
41///
42/// La dependencia de artefacto define esta variable de entorno en tiempo de
43/// compilacion: cuando este binario de anfitrion existe, el kernel ya existe.
44const KERNEL_ELF: &str = env!("CARGO_BIN_FILE_KERNEL_kernel");
45
46/// Firmware UEFI OVMF tal como lo empaqueta Artix Linux (paquete `edk2-ovmf`).
47/// Es la imagen combinada codigo+variables, apta para `-bios`.
48const OVMF_POR_DEFECTO: &str = "/usr/share/edk2/x64/OVMF.4m.fd";
49
50/// Nombre de la imagen de disco UEFI que renaser genera.
51const NOMBRE_IMAGEN: &str = "renaser-uefi.img";
52
53/// Ruta del disco de objetos del grafo persistente (Fase 6.1c). Relativa al
54/// directorio de trabajo —la raiz del repo—, comun a `boot` y a QEMU.
55const NOMBRE_DISCO: &str = "target/disk.img";
56
57/// Tamaño del disco de objetos: 32 MiB. La imagen sembrada ocupa solo unos
58/// pocos KiB; el resto queda a cero —espacio libre para que el grafo crezca—.
59const TAM_DISCO: u64 = 32 * 1024 * 1024;
60
61fn main() {
62    if let Err(fallo) = orquestar() {
63        // Un error de orquestacion se anuncia en rojo y aborta con codigo 1:
64        // ninguna falla del anfitrion debe disfrazarse de exito.
65        eprintln!("\x1b[1;31m[renaser/boot] fallo:\x1b[0m {fallo}");
66        std::process::exit(1);
67    }
68}
69
70/// Ejecuta, en orden, las operaciones de la Fase 1.5.
71fn orquestar() -> Result<(), String> {
72    // --- 1. Localizar el artefacto del kernel. ---
73    let kernel = Path::new(KERNEL_ELF);
74    if !kernel.is_file() {
75        return Err(format!(
76            "no se encontro el ELF del kernel en {}\n  \
77             (¿se interrumpio la compilacion de la dependencia de artefacto?)",
78            kernel.display()
79        ));
80    }
81    println!("[renaser/boot] kernel localizado :: {}", kernel.display());
82
83    // --- 2. Fusionar kernel + cargador UEFI en una imagen de disco. ---
84    let imagen = ruta_imagen(kernel);
85    println!("[renaser/boot] forjando imagen UEFI :: {}", imagen.display());
86    bootloader::UefiBoot::new(kernel)
87        .create_disk_image(&imagen)
88        .map_err(|e| format!("la crate `bootloader` no pudo crear la imagen UEFI: {e:?}"))?;
89
90    // --- 3. Garantizar —y, si es virgen, SEMBRAR— el disco de objetos. ---
91    preparar_disco_objetos()?;
92
93    // --- 4. Lanzar QEMU sobre esa imagen. ---
94    let ovmf = localizar_ovmf()?;
95    lanzar_qemu(&imagen, &ovmf)
96}
97
98// =============================================================================
99//  Fase 7b — la siembra del grafo: el userspace nace de la imagen de disco
100// =============================================================================
101
102/// Una app de genesis: su nombre legible, el `.wasm` que la encarna y la
103/// ventana del framebuffer que habitara — `(x, y, ancho, alto)` en pixeles.
104struct AppGenesis {
105    nombre: &'static str,
106    archivo: &'static str,
107    region: (u32, u32, u32, u32),
108}
109
110/// El userspace de genesis — las nueve aplicaciones que pueblan un disco recien
111/// forjado. La `bitacora` (Fase 17, editor que persiste), el `pregon` (Fase 19,
112/// la primera voz hacia la red), la melodia visual `tonada` (Fase 12), el
113/// compas visual `pulso` (Fase 11), un saludo (`hola`), la `memoriosa`
114/// interactiva que recuerda entre sesiones (Fase 7c), y tres demos de los
115/// guardarrailes del kernel: `discola` (combustible), `glotona` (memoria) y
116/// `cronista` (la cronica de los arranques).
117const GENESIS: [AppGenesis; 9] = [
118    AppGenesis { nombre: "bitacora", archivo: "bitacora.wasm", region: (100, 120, 480, 280) },
119    AppGenesis { nombre: "pregon", archivo: "pregon.wasm", region: (100, 120, 480, 160) },
120    AppGenesis { nombre: "tonada", archivo: "tonada.wasm", region: (100, 120, 360, 120) },
121    AppGenesis { nombre: "pulso", archivo: "pulso.wasm", region: (100, 120, 360, 120) },
122    AppGenesis { nombre: "hola", archivo: "app.wasm", region: (100, 120, 480, 560) },
123    AppGenesis { nombre: "memoriosa", archivo: "memoriosa.wasm", region: (700, 120, 360, 80) },
124    AppGenesis { nombre: "discola", archivo: "discola.wasm", region: (60, 700, 360, 80) },
125    AppGenesis { nombre: "glotona", archivo: "glotona.wasm", region: (460, 700, 360, 80) },
126    AppGenesis { nombre: "cronista", archivo: "cronista.wasm", region: (860, 700, 360, 80) },
127];
128
129/// Techo de memoria lineal de cada app de genesis: 4 MiB. Un modulo que intente
130/// crecer su memoria mas alla es desalojado por el kernel.
131const TECHO_GENESIS: u32 = 4 * 1024 * 1024;
132
133/// Garantiza la existencia del disco de objetos del grafo persistente. Si ya
134/// existe, lo RESPETA — el grafo perdura entre arranques (la cuenta de la
135/// cronista, el estado del userspace). Si no existe, lo forja Y LO SIEMBRA:
136/// graba el grafo ya poblado con el bytecode de las apps y su Manifiesto de
137/// Genesis. El kernel jamas vuelve a empotrar una sola app.
138fn preparar_disco_objetos() -> Result<(), String> {
139    let disco = Path::new(NOMBRE_DISCO);
140    if disco.is_file() {
141        println!(
142            "[renaser/boot] disco de objetos presente :: {} — el grafo perdura",
143            disco.display()
144        );
145        return Ok(());
146    }
147    if let Some(directorio) = disco.parent() {
148        std::fs::create_dir_all(directorio)
149            .map_err(|e| format!("no se pudo crear el directorio del disco de objetos: {e}"))?;
150    }
151
152    // Sembrar el grafo: el bytecode del userspace y el Manifiesto de Genesis.
153    let (imagen, objetos) = sembrar_grafo()?;
154    if imagen.len() as u64 > TAM_DISCO {
155        return Err(format!(
156            "el grafo sembrado ({} bytes) no cabe en el disco de objetos ({TAM_DISCO} bytes)",
157            imagen.len()
158        ));
159    }
160
161    // Escribir la imagen sembrada y extender el fichero a 32 MiB: el log queda
162    // al principio; el resto, a cero —`set_len` lo deja disperso—.
163    let mut fichero = std::fs::File::create(disco)
164        .map_err(|e| format!("no se pudo crear el disco de objetos «{}»: {e}", disco.display()))?;
165    fichero
166        .write_all(&imagen)
167        .map_err(|e| format!("no se pudo escribir el grafo sembrado: {e}"))?;
168    fichero
169        .set_len(TAM_DISCO)
170        .map_err(|e| format!("no se pudo dimensionar el disco de objetos: {e}"))?;
171    println!(
172        "[renaser/boot] disco de objetos sembrado :: {} ({objetos} objetos, manifiesto anclado)",
173        disco.display()
174    );
175    Ok(())
176}
177
178/// Lee el bytecode `.wasm` de una app de genesis desde `kernel/assets/`. La
179/// ruta se ancla al directorio de ESTE crate —no al de trabajo—: el
180/// constructor funciona se invoque desde donde se invoque.
181fn leer_wasm(archivo: &str) -> Result<Vec<u8>, String> {
182    let ruta = Path::new(env!("CARGO_MANIFEST_DIR"))
183        .join("../kernel/assets")
184        .join(archivo);
185    std::fs::read(&ruta)
186        .map_err(|e| format!("no se pudo leer el bytecode «{}»: {e}", ruta.display()))
187}
188
189/// Anexa un objeto al log: compone su registro `[longitud][payload][relleno]`,
190/// lo añade a la imagen y avanza el cursor. Devuelve el hash del objeto — su
191/// identidad en el grafo direccionado por contenido.
192fn anexar_objeto(log: &mut Vec<u8>, cursor: &mut u64, payload: &[u8]) -> Result<Hash, String> {
193    if payload.is_empty() || payload.len() > MAX_OBJETO {
194        return Err(format!(
195            "un objeto del grafo tiene un tamaño invalido: {} bytes",
196            payload.len()
197        ));
198    }
199    let hash = formato::hash(payload);
200    log.extend_from_slice(&formato::componer_registro(payload));
201    *cursor += formato::sectores_registro(payload.len());
202    Ok(hash)
203}
204
205/// Siembra el grafo de objetos de un disco virgen: graba el bytecode de cada
206/// app de genesis como un objeto del grafo, compone el Manifiesto de Genesis
207/// —con sus regiones y cuotas—, lo graba con las aristas hacia los objetos de
208/// bytecode, y forja el superbloque que lo ancla. Devuelve la imagen del disco
209/// (superbloque en el sector 0 + el log de registros) y el numero de objetos
210/// sembrados. Habla, byte a byte, el formato que el kernel leera al montar.
211fn sembrar_grafo() -> Result<(Vec<u8>, usize), String> {
212    // El log de registros: del sector 1 en adelante. El sector 0 es el
213    // superbloque, que aun no podemos escribir —no conocemos el cursor final—.
214    let mut log: Vec<u8> = Vec::new();
215    let mut cursor: u64 = 1;
216
217    // --- 1. Los objetos de bytecode, DEDUPLICADOS por archivo. Dos apps que
218    //        comparten el mismo `.wasm` comparten un unico objeto del grafo. ---
219    let mut hash_de: BTreeMap<&str, Hash> = BTreeMap::new();
220    let mut hijos_manifiesto: Vec<Hash> = Vec::new();
221    let mut apps: Vec<EntradaApp> = Vec::new();
222
223    for app in &GENESIS {
224        let bytecode = match hash_de.get(app.archivo) {
225            // Ya grabado: el grafo no guarda dos veces el mismo contenido.
226            Some(&hash) => hash,
227            None => {
228                let datos = leer_wasm(app.archivo)?;
229                let objeto = Objeto { datos, hijos: Vec::new() };
230                let payload = objeto.serializar().map_err(|e| e.to_string())?;
231                let hash = anexar_objeto(&mut log, &mut cursor, &payload)?;
232                hash_de.insert(app.archivo, hash);
233                hijos_manifiesto.push(hash);
234                hash
235            }
236        };
237        let (x, y, ancho, alto) = app.region;
238        apps.push(EntradaApp {
239            nombre: app.nombre.to_string(),
240            bytecode,
241            region_x: x,
242            region_y: y,
243            region_ancho: ancho,
244            region_alto: alto,
245            techo_memoria: TECHO_GENESIS,
246            estado: None,
247        });
248    }
249
250    // --- 2. El objeto del Manifiesto de Genesis. Sus `hijos` son los objetos
251    //        de bytecode: el grafo lo lee como el nodo padre del userspace. ---
252    let manifiesto = Manifiesto { version: VERSION_MANIFIESTO, apps };
253    let man_datos = manifiesto.serializar().map_err(|e| e.to_string())?;
254    let man_objeto = Objeto { datos: man_datos, hijos: hijos_manifiesto };
255    let man_payload = man_objeto.serializar().map_err(|e| e.to_string())?;
256    let hash_manifiesto = anexar_objeto(&mut log, &mut cursor, &man_payload)?;
257
258    // El grafo sembrado: un objeto por cada `.wasm` unico, mas el manifiesto.
259    let objetos = hash_de.len() + 1;
260
261    // --- 3. El superbloque: el ancla del grafo, en el sector 0. `raiz` queda
262    //        vacia —el userspace la fija; `manifiesto` apunta a la genesis. ---
263    let superbloque = SuperBloque {
264        magia: MAGIA,
265        version: VERSION_SUPERBLOQUE,
266        cursor,
267        raiz: None,
268        manifiesto: Some(hash_manifiesto),
269    };
270    let sb_bytes = superbloque.serializar().map_err(|e| e.to_string())?;
271    if sb_bytes.len() > TAM_SECTOR {
272        return Err("el superbloque sembrado no cabe en un sector".to_string());
273    }
274
275    // La imagen: el superbloque en el sector 0 (relleno a cero) y, tras el, el
276    // log de registros que acabamos de componer.
277    let mut imagen = vec![0u8; TAM_SECTOR];
278    imagen[..sb_bytes.len()].copy_from_slice(&sb_bytes);
279    imagen.extend_from_slice(&log);
280
281    Ok((imagen, objetos))
282}
283
284/// Calcula la ruta de la imagen: junto al propio ELF del kernel, es decir,
285/// dentro de `target/`. Una ubicacion predecible y siempre escribible.
286fn ruta_imagen(kernel: &Path) -> PathBuf {
287    kernel
288        .parent()
289        .unwrap_or_else(|| Path::new("."))
290        .join(NOMBRE_IMAGEN)
291}
292
293/// Resuelve la ruta del firmware OVMF. Permite sobreescribirla con la variable
294/// de entorno `RENASER_OVMF` para entornos cuyo `edk2-ovmf` viva en otra ruta.
295fn localizar_ovmf() -> Result<String, String> {
296    let ruta = std::env::var("RENASER_OVMF").unwrap_or_else(|_| OVMF_POR_DEFECTO.to_string());
297    if Path::new(&ruta).is_file() {
298        Ok(ruta)
299    } else {
300        Err(format!(
301            "firmware UEFI OVMF no encontrado en «{ruta}»\n  \
302             instala el paquete `edk2-ovmf`, o exporta RENASER_OVMF=<ruta a OVMF.fd>"
303        ))
304    }
305}
306
307/// Invoca QEMU como subproceso. Los argumentos se ciñen a las primitivas
308/// minimas necesarias para que el Framebuffer GOP cobre vida:
309///
310///   * `-bios`            firmware UEFI OVMF.
311///   * `-drive raw`       la imagen de disco UEFI, sin capa de traduccion.
312///   * `-vga std`         VGA estandar => framebuffer lineal que el GOP expone.
313///   * `-serial stdio`    telemetria serial del procesador hacia esta consola.
314///   * `--no-reboot`      un fallo triple detiene la maquina en vez de reiniciar
315///                        en bucle: asi la baliza de panico permanece visible.
316///   * `virtio-blk-pci`   el disco de objetos, sobre el bus PCI (q35 es x86_64;
317///                        `virtio-blk-device`, su gemelo MMIO, es cosa de ARM).
318fn lanzar_qemu(imagen: &Path, ovmf: &str) -> Result<(), String> {
319    println!("[renaser/boot] arrancando QEMU :: la superficie indigo nace ahora\n");
320
321    let mut qemu = Command::new("qemu-system-x86_64");
322    // `accel=kvm:tcg` intenta KVM y, si no esta disponible, recae en TCG puro.
323    qemu.arg("-machine").arg("q35,accel=kvm:tcg")
324        .arg("-m").arg("256M")
325        .arg("-bios").arg(ovmf)
326        .arg("-drive").arg(format!("format=raw,file={}", imagen.display()))
327        .arg("-vga").arg("std")
328        .arg("-serial").arg("stdio")
329        .arg("--no-reboot")
330        // El disco de objetos, como dispositivo virtio-blk sobre el bus PCI.
331        .arg("-drive").arg(format!("format=raw,file={NOMBRE_DISCO},if=none,id=drv0"))
332        .arg("-device").arg("virtio-blk-pci,drive=drv0")
333        // FASE 18 :: la tarjeta de red — `user mode networking` de QEMU, un
334        // NAT virtual hacia el host. Sin opciones extra: gateway en 10.0.2.2,
335        // DHCP/DNS en 10.0.2.3, el invitado en 10.0.2.15. El kernel envia un
336        // ARP request al gateway en cuanto arranca como prueba de vida.
337        .arg("-netdev").arg("user,id=net0")
338        .arg("-device").arg("virtio-net-pci,netdev=net0");
339
340    // Cualquier argumento extra tras `--` se reenvia a QEMU intacto.
341    // Ejemplo: `cargo run -p boot -- -display none -d int`.
342    qemu.args(std::env::args().skip(1));
343
344    match qemu.status() {
345        Ok(estado) if estado.success() => {
346            println!("\n[renaser/boot] QEMU finalizo limpiamente.");
347            Ok(())
348        }
349        Ok(estado) => Err(format!("QEMU termino con estado anomalo: {estado}")),
350        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(
351            "`qemu-system-x86_64` no esta en el PATH; instala el paquete `qemu-full`".to_string(),
352        ),
353        Err(e) => Err(format!("no se pudo ejecutar QEMU: {e}")),
354    }
355}