feat(renaser): Fase 19 — voz del userspace hacia la red (pregon)

Tres capacidades nuevas en wasm/env (12-14):

- sys_net_mac(salida) -> i32: escribe los seis bytes del MAC del
  dispositivo. 0 OK, -1 si no hay red.
- sys_net_enviar(ptr, len) -> i32: envia un frame Ethernet crudo.
  Valida rango contra la memoria lineal del modulo.
- sys_net_recibir(salida, capacidad) -> i32: drena UN paquete por
  llamada hacia el buffer del modulo. Devuelve los bytes copiados, 0
  si nada pendiente, codigos negativos diagnosticos.

Añadida red::recibir_en(buf) -> usize como su contraparte del driver:
gemelo cooperativo de drenar_rx que aterriza en un buffer del usuario.

App nueva pregon (apps/pregon/, 4.2 KiB WASM): lienzo 480x160, tipografia
8x8 (font8x8) escalada x2. Al init pide su MAC y anuncia su presencia
con un broadcast Ethernet — destino FF:FF:FF:FF:FF:FF, EtherType
experimental 0x88B5, payload ASCII 'renaser :: hola desde mi red'. En
cada tick drena un paquete con sys_net_recibir y muestra el titulo, el
MAC propio, las cuentas TX/RX, y los datos del ultimo frame entrante.

GENESIS 8 -> 9 apps (pregon en posicion 2 detras de bitacora);
CELDA_TASKBAR_ANCHO 130 -> 116 px para que las nueve pestañas + lanzador
+ reloj caben holgadas en 1280 px.

tarea_red del kernel ya no drena RX (la cola pertenece al userspace),
conserva solo el envio del ARP de prueba al arrancar.

Verificada en QEMU con -object filter-dump. El pcap captura tres frames
en orden: (1) broadcast 88B5 de pregon con su payload, (2) ARP request
del kernel, (3) ARP reply del gateway 52:55:0a:00:02:02. La consola
anuncia 'manifiesto :: 9 apps nacidas del grafo'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-23 04:26:22 +00:00
parent b1be94e7c4
commit 07ab095d42
13 changed files with 586 additions and 39 deletions
+66
View File
@@ -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`.
+6 -3
View File
@@ -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/<app> # hello_wasm, discola, glotona, cronista, memoriosa, pulso, tonada, bitacora
cd apps/<app> # hello_wasm, discola, glotona, cronista, memoriosa, pulso, tonada, bitacora, pregon
cargo build --target wasm32-unknown-unknown --release
cp target/wasm32-unknown-unknown/release/<app>.wasm ../../kernel/assets/<app>.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
+24
View File
@@ -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.*
+34
View File
@@ -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.
+16
View File
@@ -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",
]
+30
View File
@@ -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
+276
View File
@@ -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(&ETHER_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..]
}
+9 -7
View File
@@ -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) },
BIN
View File
Binary file not shown.
+3 -3
View File
@@ -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.
+30 -1
View File
@@ -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<F: FnMut(&[u8])>(mut callback: F) {
let Some(tarjeta) = TARJETA.get() else {
return;
@@ -211,6 +215,31 @@ pub fn drenar_rx<F: FnMut(&[u8])>(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
// =============================================================================
+5 -24
View File
@@ -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
+87 -1
View File
@@ -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<i32, Error> {
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<i32, Error> {
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<i32, Error> {
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<u8> = 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(())
}