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
+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..]
}