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:
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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.*
|
||||
|
||||
@@ -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.
|
||||
|
||||
Generated
+16
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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..]
|
||||
}
|
||||
@@ -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) },
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -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.
|
||||
|
||||
@@ -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
|
||||
// =============================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user