diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index d88a7fc..6e9b7b5 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -1156,3 +1156,69 @@ el kernel abre ahora una boca y una oreja al exterior: una tarjeta de red. El src del paquete entrante codifica la IP del gateway dentro del MAC (`52:55:0a:00:02:02` = QEMU prefix + `10.0.2.2`): la respuesta ARP es legítima. Renaser ya habla con la red. + +--- + +## Fase 19 — Voz del userspace hacia la red — 2026-05-23 + +La Fase 18 hizo que el kernel hablara con la red. La Fase 19 le da la +misma capacidad a los apps: tres capacidades nuevas (`sys_net_mac`, +`sys_net_enviar`, `sys_net_recibir`) y una primera app que las usa. + +### Añadido +- **`drivers::red::recibir_en(buf) -> usize`** — recibe UN paquete por + llamada, lo copia al buffer del llamante, recicla el descriptor a la + cola RX. Devuelve 0 si no hay paquete pendiente. El gemelo cooperativo + de `drenar_rx`, pensado para la interfaz que el host expone a los apps. +- **Tres capacidades nuevas en `wasm/env`** (índices 12, 13, 14): + - `sys_net_mac(salida: u32) -> i32` — escribe seis bytes con la MAC del + dispositivo en `(salida, 6)`. Devuelve `0` OK o `-1` si no hay red. + - `sys_net_enviar(ptr: u32, len: u32) -> i32` — envía el frame Ethernet + crudo. Devuelve `0` OK, `-1` red caída, `-2` longitud inválida, + `-3` rango fuera de la memoria lineal. + - `sys_net_recibir(salida: u32, capacidad: u32) -> i32` — drena UN + paquete (si hay) hacia `(salida, capacidad)`. Devuelve los bytes + copiados, `0` si nada pendiente, `-1` si no hay red, `-2` si la + capacidad excede la memoria lineal. + Todas validan `rango()` contra la memoria del módulo y registran + diagnóstico por COM1 si falla la validación. +- **App `pregon` (`apps/pregon/`, 4.2 KiB WASM)** — la primera voz del + userspace hacia la red. Al arrancar: + 1. Pide su MAC al host vía `sys_net_mac`. + 2. Si la tiene, anuncia su presencia con un broadcast Ethernet + (destino `FF:FF:FF:FF:FF:FF`, EtherType experimental `0x88B5`, + payload ASCII `"renaser :: hola desde mi red"` — 42 bytes en el cable). + En cada `tick` drena un paquete con `sys_net_recibir` y pinta: + el título, el MAC propio, las cuentas TX/RX, y los datos del último + frame entrante (tamaño, EtherType, src MAC) — todo en tipografía 8×8 + embebida (crate `font8x8`), escalada x2 sobre un lienzo 480×160 índigo. +- **`pregon` en el manifiesto de genesis** (`boot/src/main.rs`): la novena + app, posición 2 (detrás de `bitacora`). El disco se siembra ahora con + 9 entradas; el kernel reporta `manifiesto :: 9 apps nacidas del grafo`. + +### Cambiado +- **`tarea_red`** del kernel ya no drena la cola RX: la posesión de la + cola pasa al userspace vía `sys_net_recibir`. La tarea conserva el + envío del ARP de prueba al gateway en cuanto el dispositivo se + estabiliza, y termina. +- **`CELDA_TASKBAR_ANCHO`** baja de 130 a 116 px para que las nueve + pestañas + lanzador + reloj caben holgadas en 1280 px. +- **`ETHER_TYPE_RENASER`** del driver pasa a `#[allow(dead_code)]` — + ahora vive en el userspace (en `pregon`); el kernel la conserva como + referencia para futuras pilas nativas. + +### Verificado +- QEMU headless con `-object filter-dump,id=fd,netdev=net0,file=/tmp/renaser.pcap`. + El pcap captura tres frames en orden: + ``` + #1 incl=42 dst=ff:ff:ff:ff:ff:ff src=52:54:00:12:34:56 etype=0x88b5 + payload: 'renaser :: hola desde mi red' + #2 incl=42 dst=ff:ff:ff:ff:ff:ff src=52:54:00:12:34:56 etype=0x0806 (ARP REQ) + #3 incl=64 dst=52:54:00:12:34:56 src=52:55:0a:00:02:02 etype=0x0806 (ARP REP) + ``` + El primero es `pregon`; el segundo el kernel; el tercero es el gateway + respondiéndonos — el ciclo completo de salida y entrada en una sola + fase. +- Captura de pantalla: la ventana `pregon :: voz hacia la red` aparece + teselada, su pestaña sale en la barra de tareas entre `bitacora` y + `tonada`, y la consola anuncia `manifiesto :: 9 apps nacidas del grafo`. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index 823a4a1..d78cc73 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -28,7 +28,7 @@ Reconstruir una app WASM del userspace tras tocarla. Los `.wasm` viven en modulo `hello_wasm` se copia como `app.wasm`, el resto conserva su nombre: ```sh -cd apps/ # hello_wasm, discola, glotona, cronista, memoriosa, pulso, tonada, bitacora +cd apps/ # hello_wasm, discola, glotona, cronista, memoriosa, pulso, tonada, bitacora, pregon cargo build --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/.wasm ../../kernel/assets/.wasm # (hello_wasm es la excepcion: su destino es kernel/assets/app.wasm) @@ -90,9 +90,12 @@ arrancar, repique al lanzar o cerrar, bajo al desalojar, con prioridad sobre `sys_tono`— la Fase 16 COMPLETA —la barra viva: botón «+» lanzador a la izquierda y reloj `mm:ss` a la derecha que late cada segundo— la Fase 17 COMPLETA —`bitacora`, editor de texto que persiste entre -arranques en el grafo de objetos (tipografía 8×8 embebida)— y la Fase 18 +arranques en el grafo de objetos (tipografía 8×8 embebida)— la Fase 18 COMPLETA —red: virtio-net + ARP al gateway de QEMU + recepción de -paquetes registrada por COM1—. Todo verificado en QEMU. Ver `ROADMAP.md`. +paquetes registrada por COM1— y la Fase 19 COMPLETA —voz del userspace +hacia la red: capacidades `sys_net_mac` / `sys_net_enviar` / `sys_net_recibir` ++ la app `pregon` que pregona su presencia con un broadcast Ethernet +y muestra el tráfico entrante—. Todo verificado en QEMU. Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index b1ee7ad..aff99c2 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -584,4 +584,28 @@ sola. --- +## La voz pasa a los inquilinos — el pregón + +Ayer la casa abrió la puerta lateral y saludó. Hoy ese saludo deja de ser +suyo y empieza a ser de quien la habita. Antes, sólo el conserje —el +kernel— podía asomarse a la red. Hoy le entrega tres llaves a los +inquilinos: una para preguntar «¿cuál es mi dirección?», otra para +gritar algo, otra para recoger lo que entra. Llaves pequeñas, una a la +vez, pero suficientes. + +Y para estrenarlas nace un huésped nuevo, *pregón*. Su único oficio es +ese: pregonar. Apenas despierta, mira su credencial, se asoma a la +puerta y suelta su grito —«renaser :: hola desde mi red»— para que +quien quiera lo escuche. Luego se queda en su ventana, esperando lo +que llegue, y en cuanto algo entra lo apunta en su pizarra: cuántas +letras, de quién, qué tipo de cosa. Una bitácora viva, en tiempo real, +del tráfico que entra y sale. + +Si uno graba lo que pasa por la red mientras la casa despierta, +encuentra exactamente tres notas: el pregón del huésped, la pregunta +del conserje, la respuesta del vecino. Tres frases bastan para mostrar +que la voz ya no es de uno solo. Ahora la casa habla en coro. + +--- + *El diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index fef51bb..a47af82 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -323,6 +323,40 @@ Líneas abiertas posteriores: capacidades `sys_net_*` para que los apps también hablen; una pila mínima ARP/IP/UDP; reciclado de las ranuras de ventana cerradas; audio con varias voces (PCM). +## Fase 19 — voz del userspace hacia la red (completada) + +La Fase 18 hizo que el kernel hablara con la red. La Fase 19 le da la +misma capacidad a los apps con tres capacidades nuevas, y estrena la +primera app que las usa. + +- `drivers::red::recibir_en(buf) -> usize`: drena UN paquete por + llamada, recicla el descriptor a la cola RX. El gemelo cooperativo de + `drenar_rx`, dimensionado para el patrón que los apps usan. +- Tres capacidades `wasm/env` nuevas: `sys_net_mac` (escribe la MAC en + un buffer del módulo), `sys_net_enviar(ptr, len)` (envía un frame + Ethernet crudo) y `sys_net_recibir(salida, capacidad)` (drena un + paquete, devuelve los bytes copiados). Todas validan rango contra la + memoria lineal del módulo y devuelven códigos negativos diagnósticos. +- App `pregon` (4.2 KiB WASM): al arrancar pide su MAC, anuncia su + presencia con un broadcast Ethernet (EtherType `0x88B5`, payload + `"renaser :: hola desde mi red"`) y en cada `tick` drena un paquete y + lo muestra en su lienzo 480×160 — título, MAC, cuentas TX/RX y datos + del último frame entrante (tamaño, EtherType, src MAC). +- `tarea_red` ya no drena RX: la posesión de la cola pasa al userspace. + El kernel conserva sólo el envío del ARP de prueba al arrancar. +- `GENESIS` crece de 8 a 9 apps; `pregon` queda en posición 2 (detrás + de `bitacora`). `CELDA_TASKBAR_ANCHO` baja de 130 a 116 px para que + las nueve pestañas + lanzador + reloj caben en 1280 px. + +Verificada con `-object filter-dump,...,file=/tmp/renaser.pcap`. El +pcap captura tres frames: el broadcast `0x88B5` de `pregon`, el ARP +request del kernel y el ARP reply del gateway. El primero es el del +huésped — la voz del userspace ya pasó al cable. + +Líneas abiertas posteriores: una pila mínima ARP/IP/UDP para los apps; +un servidor de eco / un cliente DNS de juguete; reciclado de las +ranuras de ventana cerradas; audio con varias voces (PCM). + ## Principios que persisten entre fases - Reutilizar infraestructura madura de la comunidad antes que reinventar. diff --git a/renaser/apps/pregon/Cargo.lock b/renaser/apps/pregon/Cargo.lock new file mode 100644 index 0000000..a6b52f4 --- /dev/null +++ b/renaser/apps/pregon/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "font8x8" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e" + +[[package]] +name = "pregon" +version = "0.1.0" +dependencies = [ + "font8x8", +] diff --git a/renaser/apps/pregon/Cargo.toml b/renaser/apps/pregon/Cargo.toml new file mode 100644 index 0000000..18750a2 --- /dev/null +++ b/renaser/apps/pregon/Cargo.toml @@ -0,0 +1,30 @@ +# ============================================================================= +# renaser :: apps/pregon — Fase 19 :: el primer app que sale a la red +# ----------------------------------------------------------------------------- +# Pregona la presencia de renaser en la red con un frame Ethernet broadcast, +# y pone en pantalla lo que va sucediendo: su MAC, paquetes enviados y +# recibidos, y los primeros bytes del ultimo frame entrante. Demuestra las +# capacidades `sys_net_*` de la Fase 19. +# ============================================================================= + +[package] +name = "pregon" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM — voz hacia la red, demo de sys_net_*" + +[workspace] + +[dependencies] +font8x8 = { version = "0.3", default-features = false, features = ["unicode"] } + +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/pregon/src/lib.rs b/renaser/apps/pregon/src/lib.rs new file mode 100644 index 0000000..1031961 --- /dev/null +++ b/renaser/apps/pregon/src/lib.rs @@ -0,0 +1,276 @@ +// ============================================================================= +// renaser :: apps/pregon — Fase 19 :: voz del userspace hacia la red +// ----------------------------------------------------------------------------- +// La Fase 18 hizo que el kernel pudiera hablar con la red; la Fase 19 le da la +// misma capacidad a los apps via `sys_net_*`. `pregon` es el primer app que +// la usa: al arrancar pide su MAC al host, anuncia su presencia con un frame +// Ethernet broadcast (EtherType experimental 0x88B5) y, en cada `tick`, drena +// un paquete de la cola RX y lo muestra en pantalla. +// +// El kernel envia tambien al arrancar un ARP request al gateway de QEMU +// (Fase 18); `pregon` recibira su respuesta como uno de los primeros paquetes +// RX — visible aqui en la linea «ultimo». +// ============================================================================= + +#![no_std] + +use font8x8::legacy::BASIC_LEGACY; + +#[link(wasm_import_module = "renaser")] +extern "C" { + fn sys_render_frame(ptr: u32, len: u32); + fn sys_net_mac(salida: u32) -> i32; + fn sys_net_enviar(ptr: u32, len: u32) -> i32; + fn sys_net_recibir(salida: u32, capacidad: u32) -> i32; +} + +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +// --- Geometria del lienzo ---------------------------------------------------- + +const ANCHO: usize = 480; +const ALTO: usize = 160; +const PASO: usize = 16; +const MARGEN: usize = 16; + +const FONDO: u32 = 0x0A_18_30; +const TINTA: u32 = 0xE8_EC_F4; +const TINTA_TENUE: u32 = 0x6A_70_80; +const ETIQUETA: u32 = 0x8B_5C_F6; + +// --- Constantes del paquete que enviamos ------------------------------------ + +const ETHER_TYPE_RENASER: u16 = 0x88B5; +const MENSAJE: &[u8; 28] = b"renaser :: hola desde mi red"; +const FRAME_LEN: usize = 14 + 28; + +// --- Estado del app --------------------------------------------------------- + +static mut MAC: [u8; 6] = [0; 6]; +static mut MAC_OK: bool = false; +static mut TX: u32 = 0; +static mut RX: u32 = 0; +static mut BUFFER_RX: [u8; 1600] = [0; 1600]; +static mut ULTIMA_LEN: usize = 0; +static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO]; + +// --- ABI del userspace ------------------------------------------------------ + +#[no_mangle] +pub extern "C" fn init() { + // 1. Pedir la MAC al host. -1 significa «no hay red montada». + let mac = unsafe { &mut *core::ptr::addr_of_mut!(MAC) }; + // SEGURIDAD: `sys_net_mac` escribe seis bytes en (ptr, 6); el host lo valida. + let r = unsafe { sys_net_mac(mac.as_mut_ptr() as u32) }; + if r == 0 { + unsafe { + MAC_OK = true; + } + // 2. Anunciar la presencia con un broadcast Ethernet de EtherType + // experimental + un mensaje legible. El payload aparece tal cual en + // cualquier pcap, sin descifrar nada. + let mut frame = [0u8; FRAME_LEN]; + frame[0..6].copy_from_slice(&[0xff; 6]); // destino: broadcast + frame[6..12].copy_from_slice(mac); + frame[12..14].copy_from_slice(ÐER_TYPE_RENASER.to_be_bytes()); + frame[14..].copy_from_slice(MENSAJE); + // SEGURIDAD: `sys_net_enviar` lee (ptr, len) de nuestra memoria. + let r = unsafe { sys_net_enviar(frame.as_ptr() as u32, frame.len() as u32) }; + if r == 0 { + unsafe { + TX += 1; + } + } + } + pintar(); +} + +#[no_mangle] +pub extern "C" fn tick() { + // Drenar UN paquete por tick — si la cola RX tiene mas, se atendera en + // las proximas vueltas. Cero coste cuando no hay nada que leer. + let buf = unsafe { &mut *core::ptr::addr_of_mut!(BUFFER_RX) }; + let n = unsafe { sys_net_recibir(buf.as_mut_ptr() as u32, buf.len() as u32) }; + if n > 0 { + unsafe { + RX += 1; + ULTIMA_LEN = n as usize; + } + } + pintar(); +} + +// --- Renderizado ------------------------------------------------------------ + +fn pintar() { + let lienzo = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) }; + for p in lienzo.iter_mut() { + *p = FONDO; + } + + // Titulo + subrayado. + pintar_texto(lienzo, b"pregon :: voz hacia la red", MARGEN, 8, ETIQUETA); + let y_linea = 8 + PASO + 4; + for x in MARGEN..(ANCHO - MARGEN) { + lienzo[y_linea * ANCHO + x] = ETIQUETA; + lienzo[(y_linea + 1) * ANCHO + x] = ETIQUETA; + } + + let mac_ok = unsafe { MAC_OK }; + if !mac_ok { + pintar_texto(lienzo, b"sin red -- no hay tarjeta montada", MARGEN, 48, TINTA); + volcar(lienzo); + return; + } + + let mac = unsafe { &*core::ptr::addr_of!(MAC) }; + let tx = unsafe { TX }; + let rx = unsafe { RX }; + + // Linea MAC: "mac: xx:xx:xx:xx:xx:xx" + { + let mut linea = [b' '; 22]; + linea[0..5].copy_from_slice(b"mac: "); + let mut p = 5; + for (i, &b) in mac.iter().enumerate() { + let par = hex_byte(b); + linea[p..p + 2].copy_from_slice(&par); + p += 2; + if i < 5 { + linea[p] = b':'; + p += 1; + } + } + pintar_texto(lienzo, &linea, MARGEN, 44, TINTA); + } + + // Linea TX/RX: "tx: N rx: M" + { + let mut linea = [b' '; 20]; + linea[0..4].copy_from_slice(b"tx: "); + let mut buf = [0u8; 10]; + let s = dec(tx, &mut buf); + linea[4..4 + s.len()].copy_from_slice(s); + let p = 4 + s.len() + 4; + if p + 4 <= linea.len() { + linea[p..p + 4].copy_from_slice(b"rx: "); + let mut buf2 = [0u8; 10]; + let s2 = dec(rx, &mut buf2); + let q = p + 4; + if q + s2.len() <= linea.len() { + linea[q..q + s2.len()].copy_from_slice(s2); + } + } + pintar_texto(lienzo, &linea, MARGEN, 72, TINTA); + } + + // Linea «ultimo»: cuantos bytes, tipo y src. + let len = unsafe { ULTIMA_LEN }; + if len >= 14 { + let buf = unsafe { &*core::ptr::addr_of!(BUFFER_RX) }; + let etype = u16::from_be_bytes([buf[12], buf[13]]); + // Linea 1: "ultimo: N bytes type=0x????" + let mut linea = [b' '; 30]; + linea[0..8].copy_from_slice(b"ultimo: "); + let mut dbuf = [0u8; 10]; + let s = dec(len as u32, &mut dbuf); + linea[8..8 + s.len()].copy_from_slice(s); + let mut p = 8 + s.len(); + linea[p..p + 8].copy_from_slice(b" bytes "); + p += 8; + if p + 7 <= linea.len() { + linea[p..p + 7].copy_from_slice(b"type=0x"); + p += 7; + let hi = hex_byte((etype >> 8) as u8); + let lo = hex_byte((etype & 0xff) as u8); + if p + 4 <= linea.len() { + linea[p..p + 2].copy_from_slice(&hi); + linea[p + 2..p + 4].copy_from_slice(&lo); + } + } + pintar_texto(lienzo, &linea, MARGEN, 104, TINTA_TENUE); + + // Linea 2: "src: xx:xx:xx:xx:xx:xx" + let src = &buf[6..12]; + let mut linea2 = [b' '; 22]; + linea2[0..5].copy_from_slice(b"src: "); + let mut p = 5; + for (i, &b) in src.iter().enumerate() { + let par = hex_byte(b); + linea2[p..p + 2].copy_from_slice(&par); + p += 2; + if i < 5 { + linea2[p] = b':'; + p += 1; + } + } + pintar_texto(lienzo, &linea2, MARGEN, 132, TINTA); + } else { + pintar_texto(lienzo, b"esperando primer paquete...", MARGEN, 104, TINTA_TENUE); + } + + volcar(lienzo); +} + +fn volcar(lienzo: &[u32]) { + // SEGURIDAD: el host valida (ptr, len) contra nuestra memoria lineal. + unsafe { + sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32); + } +} + +fn pintar_texto(lienzo: &mut [u32], texto: &[u8], x: usize, y: usize, color: u32) { + let mut cx = x; + for &c in texto { + if cx + PASO > ANCHO { + break; + } + pintar_glifo(lienzo, c, cx, y, color); + cx += PASO; + } +} + +fn pintar_glifo(lienzo: &mut [u32], c: u8, x: usize, y: usize, color: u32) { + let glifo = if (c as usize) < 128 { + BASIC_LEGACY[c as usize] + } else { + BASIC_LEGACY[b'?' as usize] + }; + for row in 0..8 { + for col in 0..8 { + if glifo[row] & (1 << col) != 0 { + let px = x + col * 2; + let py = y + row * 2; + if px + 1 >= ANCHO || py + 1 >= ALTO { + continue; + } + lienzo[py * ANCHO + px] = color; + lienzo[py * ANCHO + px + 1] = color; + lienzo[(py + 1) * ANCHO + px] = color; + lienzo[(py + 1) * ANCHO + px + 1] = color; + } + } + } +} + +fn hex_byte(b: u8) -> [u8; 2] { + let n = |x: u8| if x < 10 { b'0' + x } else { b'a' + (x - 10) }; + [n(b >> 4), n(b & 0xf)] +} + +fn dec(mut n: u32, buf: &mut [u8; 10]) -> &[u8] { + if n == 0 { + buf[0] = b'0'; + return &buf[..1]; + } + let mut i = 10; + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + &buf[i..] +} diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs index b91762d..0f664df 100644 --- a/renaser/boot/src/main.rs +++ b/renaser/boot/src/main.rs @@ -107,14 +107,16 @@ struct AppGenesis { region: (u32, u32, u32, u32), } -/// El userspace de genesis — las ocho aplicaciones que pueblan un disco recien -/// forjado. La `bitacora` (Fase 17, editor que persiste), la melodia visual -/// `tonada` (Fase 12), el compas visual `pulso` (Fase 11), un saludo (`hola`), -/// la `memoriosa` interactiva que recuerda entre sesiones (Fase 7c), y tres -/// demos de los guardarrailes del kernel: `discola` (combustible), `glotona` -/// (memoria) y `cronista` (la cronica de los arranques). -const GENESIS: [AppGenesis; 8] = [ +/// El userspace de genesis — las nueve aplicaciones que pueblan un disco recien +/// forjado. La `bitacora` (Fase 17, editor que persiste), el `pregon` (Fase 19, +/// la primera voz hacia la red), la melodia visual `tonada` (Fase 12), el +/// compas visual `pulso` (Fase 11), un saludo (`hola`), la `memoriosa` +/// interactiva que recuerda entre sesiones (Fase 7c), y tres demos de los +/// guardarrailes del kernel: `discola` (combustible), `glotona` (memoria) y +/// `cronista` (la cronica de los arranques). +const GENESIS: [AppGenesis; 9] = [ AppGenesis { nombre: "bitacora", archivo: "bitacora.wasm", region: (100, 120, 480, 280) }, + AppGenesis { nombre: "pregon", archivo: "pregon.wasm", region: (100, 120, 480, 160) }, AppGenesis { nombre: "tonada", archivo: "tonada.wasm", region: (100, 120, 360, 120) }, AppGenesis { nombre: "pulso", archivo: "pulso.wasm", region: (100, 120, 360, 120) }, AppGenesis { nombre: "hola", archivo: "app.wasm", region: (100, 120, 480, 560) }, diff --git a/renaser/kernel/assets/pregon.wasm b/renaser/kernel/assets/pregon.wasm new file mode 100755 index 0000000..0a66914 Binary files /dev/null and b/renaser/kernel/assets/pregon.wasm differ diff --git a/renaser/kernel/src/compositor.rs b/renaser/kernel/src/compositor.rs index db2ad60..55922fe 100644 --- a/renaser/kernel/src/compositor.rs +++ b/renaser/kernel/src/compositor.rs @@ -68,9 +68,9 @@ const FRANJA_CONSOLA: usize = 296; const FRANJA_TASKBAR: usize = 40; /// Anchura de cada celda de la barra de tareas, en pixeles. Dimensionada para -/// que las ocho apps de genesis + el lanzador + el reloj caben holgados en una -/// pantalla de 1280 px. -const CELDA_TASKBAR_ANCHO: usize = 130; +/// que las nueve apps de genesis (Fase 19 anexa `pregon`) + el lanzador + el +/// reloj caben holgados en una pantalla de 1280 px. +const CELDA_TASKBAR_ANCHO: usize = 116; /// Hueco entre celdas adyacentes de la barra. const CELDA_TASKBAR_HUECO: usize = 6; /// Margen izquierdo y derecho de la barra de tareas. diff --git a/renaser/kernel/src/drivers/red.rs b/renaser/kernel/src/drivers/red.rs index 9291b56..83bb051 100644 --- a/renaser/kernel/src/drivers/red.rs +++ b/renaser/kernel/src/drivers/red.rs @@ -37,7 +37,10 @@ const PAQUETE_MAX: usize = 1600; const PROFUNDIDAD_COLA: usize = 16; /// EtherType experimental (rango 0x88B5-0x88B6, reservado por IEEE para uso -/// local). renaser lo usaria si quisiera definir su propio protocolo. +/// local). renaser lo usaria si quisiera definir su propio protocolo. Desde +/// la Fase 19 lo usa `pregon` desde el userspace; el kernel lo conserva como +/// referencia para diagnosticos y futuros protocolos nativos. +#[allow(dead_code)] pub const ETHER_TYPE_RENASER: u16 = 0x88B5; /// EtherType de ARP. pub const ETHER_TYPE_ARP: u16 = 0x0806; @@ -190,6 +193,7 @@ pub fn enviar(frame: &[u8]) -> Result<(), &'static str> { /// Drena los paquetes RX pendientes y aplica `callback` a cada uno. Cada /// bufer se recicla a la cola RX al terminar — el dispositivo tiene siempre /// receptores listos para la proxima IRQ. +#[allow(dead_code)] pub fn drenar_rx(mut callback: F) { let Some(tarjeta) = TARJETA.get() else { return; @@ -211,6 +215,31 @@ pub fn drenar_rx(mut callback: F) { }); } +/// Recibe UN paquete: copia su contenido en `buf` y devuelve los bytes +/// copiados, o `0` si no hay paquete pendiente. La interfaz que el host +/// expone a los apps via `sys_net_recibir` — un paquete por llamada (Fase 19). +pub fn recibir_en(buf: &mut [u8]) -> usize { + let Some(tarjeta) = TARJETA.get() else { + return 0; + }; + interrupts::without_interrupts(|| { + let mut tarjeta = tarjeta.lock(); + if !tarjeta.0.can_recv() { + return 0; + } + let rx = match tarjeta.0.receive() { + Ok(r) => r, + Err(_) => return 0, + }; + let pkt = rx.packet(); + let n = pkt.len().min(buf.len()); + buf[..n].copy_from_slice(&pkt[..n]); + let _ = tarjeta.0.recycle_rx_buffer(rx); + PAQUETES_RX.fetch_add(1, Ordering::Relaxed); + n + }) +} + // ============================================================================= // Composicion de un ARP request — el primer paquete que renaser saluda // ============================================================================= diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs index f612bb4..c730cdb 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -174,9 +174,10 @@ async fn tarea_compositor() { } } -/// FASE 18 — la prueba viva del enlace de red. Envia un ARP request al -/// gateway de QEMU (10.0.2.2) y registra por COM1 cada paquete que llegue de -/// vuelta. El primer "hola" de renaser hacia el exterior. +/// FASE 18/19 — la primera voz del kernel hacia la red. Envia un ARP request +/// al gateway de QEMU para anunciarse, y termina: a partir de la Fase 19, los +/// apps drenan la cola RX por su cuenta via `sys_net_recibir`, asi que el +/// kernel no le quita paquetes a nadie. async fn tarea_red(mac: drivers::red::Mac) { // Dejar un par de fotogramas para que la cola RX se estabilice. for _ in 0..10 { @@ -199,27 +200,7 @@ async fn tarea_red(mac: drivers::red::Mac) { let _ = writeln!(baliza::Serie, "red :: envio fallido :: {motivo}"); } } - // Loop perpetuo: drenar la cola RX y registrar cada paquete en COM1. - loop { - async_system::reloj::EsperaFrame::nueva().await; - drivers::red::drenar_rx(|payload| { - if payload.len() < 14 { - return; - } - let etype = u16::from_be_bytes([payload[12], payload[13]]); - let src = &payload[6..12]; - let dst = &payload[0..6]; - let _ = writeln!( - baliza::Serie, - "red :: RX {} bytes :: dst={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} \ - src={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} type={:#06x}", - payload.len(), - dst[0], dst[1], dst[2], dst[3], dst[4], dst[5], - src[0], src[1], src[2], src[3], src[4], src[5], - etype, - ); - }); - } + // La tarea termina aqui. Los apps se encargan del trafico desde ahora. } /// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el diff --git a/renaser/kernel/src/wasm/env.rs b/renaser/kernel/src/wasm/env.rs index f7efa45..66763d2 100644 --- a/renaser/kernel/src/wasm/env.rs +++ b/renaser/kernel/src/wasm/env.rs @@ -15,7 +15,10 @@ // * sys_estado_cargar — leer el estado persistido de la app (Fase 7c); // * sys_estado_guardar — anclar el estado persistido de la app (Fase 7c); // * sys_tiempo_mono — leer el reloj monotono del sistema (Fase 11); -// * sys_tono — hacer sonar la bocina del PC (Fase 12). +// * sys_tono — hacer sonar la bocina del PC (Fase 12); +// * sys_net_mac — leer la MAC de la tarjeta de red (Fase 19); +// * sys_net_enviar — enviar un frame Ethernet crudo (Fase 19); +// * sys_net_recibir — leer el siguiente frame recibido (Fase 19). // // 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 @@ -482,5 +485,88 @@ pub(crate) fn enlazar_capacidades( }, )?; + // --- CAPACIDAD 12 :: sys_net_mac(salida) -> i32 --- + // Copia los 6 bytes de la MAC de la tarjeta de red en `salida`. Devuelve 0 + // si la red esta montada; -1 si no hay tarjeta o aun no se monto. + enlazador.func_wrap( + "renaser", + "sys_net_mac", + |mut caller: Caller<'_, ContextoCapacidades>, salida: u32| -> Result { + let Some(mac) = crate::drivers::red::mac() else { + return Ok(-1); + }; + let memoria = obtener_memoria(&caller)?; + { + let m = memoria.data(&caller); + rango(m, salida, 6, "WASM :: sys_net_mac desbordo la memoria lineal")?; + } + let m = memoria.data_mut(&mut caller); + m[salida as usize..salida as usize + 6].copy_from_slice(&mac); + Ok(0) + }, + )?; + + // --- CAPACIDAD 13 :: sys_net_enviar(ptr, len) -> i32 --- + // Envia un frame Ethernet crudo (cabecera + payload, sin CRC). El app + // construye el frame entero en su memoria lineal. Devuelve 0 si el + // envio se entrego al dispositivo; -1 si fallo el envio o no hay red. + enlazador.func_wrap( + "renaser", + "sys_net_enviar", + |caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result { + let memoria = obtener_memoria(&caller)?; + let datos = memoria.data(&caller); + let frame = rango( + datos, + ptr, + len as usize, + "WASM :: sys_net_enviar desbordo la memoria lineal", + )?; + match crate::drivers::red::enviar(frame) { + Ok(()) => Ok(0), + Err(_) => Ok(-1), + } + }, + )?; + + // --- CAPACIDAD 14 :: sys_net_recibir(salida, capacidad) -> i32 --- + // Saca el siguiente frame de la cola RX del dispositivo y lo copia en + // `salida`. Devuelve los bytes copiados (>0), 0 si no hay frame pendiente, + // o -1 si no hay red montada. La cola RX es del dispositivo y se comparte + // entre los apps: el primero que pregunte se lleva el paquete. + enlazador.func_wrap( + "renaser", + "sys_net_recibir", + |mut caller: Caller<'_, ContextoCapacidades>, + salida: u32, + capacidad: u32| + -> Result { + if crate::drivers::red::mac().is_none() { + return Ok(-1); + } + let memoria = obtener_memoria(&caller)?; + // Verificar que el destino cabe ANTES de tocar la cola. + { + let m = memoria.data(&caller); + rango( + m, + salida, + capacidad as usize, + "WASM :: sys_net_recibir desbordo la memoria lineal", + )?; + } + // Bufer kernel-side donde el driver vuelca el frame; luego se copia + // a la memoria del app en una sola pasada. + let mut buf: alloc::vec::Vec = alloc::vec![0u8; capacidad as usize]; + let n = crate::drivers::red::recibir_en(&mut buf); + if n == 0 { + return Ok(0); + } + let m = memoria.data_mut(&mut caller); + m[salida as usize..salida as usize + n].copy_from_slice(&buf[..n]); + Ok(n as i32) + }, + )?; + Ok(()) }