From bdd088b89ebc722d6679e01ef8c5c9b174f797fc Mon Sep 17 00:00:00 2001 From: sergio Date: Sat, 23 May 2026 04:06:23 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=2018=20=E2=80=94=20red:?= =?UTF-8?q?=20virtio-net=20y=20el=20primer=20hola=20al=20exterior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renaser hablaba consigo mismo. Esta fase abre una boca y una oreja al exterior con una tarjeta de red, reutilizando el `KernelHal` del disco y el mapeador MMIO (la pieza estructural que hizo esto posible). - `drivers/red`: monta `VirtIONet`, expone `enviar(frame)` y `drenar_rx(callback)`. Sin pila TCP/IP — solo Ethernet crudo; la composición de paquetes la hace el llamante. - `componer_arp_request(mac, ip, objetivo)` construye el saludo inicial: «¿quien tiene 10.0.2.2?» dirigido al gateway de QEMU. - `interrupts::registrar_irq_red` + handler `irq_red`, gemelo del de disco. La IRQ del dispositivo activa `red::atender_irq`, que hace `ack_interrupt` y suelta la línea. - `tarea_red` en el reactor: al arrancar envía el ARP, después cada fotograma drena la cola RX y vuelca cada paquete a COM1. - QEMU args ganan `-netdev user,id=net0 -device virtio-net-pci`. Verificado con `-object filter-dump,...,file=/tmp/red.pcap`: red :: virtio-net :: MAC 52:54:00:12:34:56 :: IRQ Some(11) red :: ARP REQUEST enviado :: ¿quien tiene 10.0.2.2? red :: RX 64 bytes :: src=52:55:0a:00:02:02 type=0x0806 El src del paquete entrante (`52:55:0a:00:02:02`) codifica `10.0.2.2` dentro del MAC — es el gateway de QEMU respondiendo. Renaser ya habla con el exterior. Co-Authored-By: Claude Opus 4.7 --- renaser/CHANGELOG.md | 37 +++++ renaser/CLAUDE.md | 7 +- renaser/DIARIO.md | 14 ++ renaser/ROADMAP.md | 21 ++- renaser/boot/src/main.rs | 8 +- renaser/kernel/src/drivers/mod.rs | 3 + renaser/kernel/src/drivers/red.rs | 242 ++++++++++++++++++++++++++++++ renaser/kernel/src/interrupts.rs | 22 +++ renaser/kernel/src/main.rs | 72 +++++++++ 9 files changed, 420 insertions(+), 6 deletions(-) create mode 100644 renaser/kernel/src/drivers/red.rs diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index fd5c765..d88a7fc 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -1119,3 +1119,40 @@ sigue ahí. La huella vive en el grafo de objetos, como todo lo demás. texto justo donde quedó. El `almacen` reporta 24 objetos en el grafo (frente a 9 antes de escribir) y `raiz presente`: cada `guardar` anexó una versión al log direccionado por contenido. + +## Fase 18 — Red: virtio-net y el primer hola al exterior — 2026-05-23 + +renaser hablaba consigo mismo. Con la misma plantilla del disco —enumerar +PCI, montar el transporte virtio, ceder a `virtio-drivers` el diálogo de +bajo nivel— y reutilizando el `KernelHal` y el mapeador `memory::mmio`, +el kernel abre ahora una boca y una oreja al exterior: una tarjeta de red. + +### Añadido +- **Driver `drivers/red`** — monta `VirtIONet`, + enruta su IRQ vía el PIC, expone `enviar(frame)` y `drenar_rx(callback)`. + La cabeza del archivo guarda el MAC del dispositivo y las constantes de + IP de QEMU user-mode (`10.0.2.15` invitado, `10.0.2.2` gateway). +- **`componer_arp_request(mac, ip, objetivo)`** — construye un frame + Ethernet + ARP listo para enviar. +- **`interrupts::registrar_irq_red`** + `extern "x86-interrupt" irq_red` + — mismo patrón que la IRQ del disco. Acknowledge en el dispositivo, + EOI al PIC. +- **`KernelHal` se hace `pub`** para que `red` lo reutilice como su Hal + de DMA — la misma arena de marcos físicos que el disco. El mapeador + MMIO (Fase 13.5) cubre los BAR del nuevo dispositivo sin tocar nada. +- **`tarea_red`**: tras 10 fotogramas para que la cola RX se estabilice, + envía un ARP request al gateway de QEMU. Después, cada fotograma drena + la cola RX y vuelca cada paquete a COM1. +- **QEMU args**: `-netdev user,id=net0 -device virtio-net-pci,netdev=net0` + (user-mode networking, NAT virtual al host). + +### Verificado +- QEMU con `-object filter-dump,...,file=/tmp/red.pcap`. Trazas COM1: + ``` + red :: virtio-net :: MAC 52:54:00:12:34:56 :: IRQ Some(11) + red :: ARP REQUEST enviado :: ¿quien tiene 10.0.2.2? + red :: RX 64 bytes :: dst=52:54:00:12:34:56 src=52:55:0a:00:02:02 type=0x0806 + ``` + 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. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index f616ec0..823a4a1 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -89,9 +89,10 @@ clic-para-enfocar—, la Fase 15 COMPLETA —la voz del sistema: acorde al 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— y la Fase 17 COMPLETA —`bitacora`, editor de texto que persiste -entre arranques en el grafo de objetos (tipografía 8×8 embebida)—. -Todo verificado en QEMU. Ver `ROADMAP.md`. +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 +COMPLETA —red: virtio-net + ARP al gateway de QEMU + recepción de +paquetes registrada por COM1—. Todo verificado en QEMU. Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index b8bdc8f..b1ee7ad 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -568,6 +568,20 @@ apagar y volver a encender, el papel vuelve a su sitio con cada palabra intacta. Apaga, enciende, sigue escribiendo. La casa no olvida lo que se le confía. +## El primer saludo afuera — la red + +Hasta hoy, la casa era un islote completo: tenía cuartos, voces, manos y +ahora memoria, pero no había forma de hablar con nada de lo que estuviera +afuera. Hoy le crece una pequeña puerta lateral —una tarjeta de red— y por +ella sale un saludo. El primero: una pregunta sencilla a quien hubiese +escuchando. «¿Quién es 10.0.2.2?», dice. Y la red contesta: «yo». La casa +toma nota, lo deja escrito en su libro de marcas, y se queda atenta. + +A partir de aquí queda mucho por construir —entender lo que llega, hablar +en idiomas más altos como TCP, abrir capacidades para que sus inquilinos +también puedan dialogar— pero lo grueso ya está. La casa dejó de hablar +sola. + --- *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 8973ea5..fef51bb 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -303,8 +303,25 @@ texto sigue ahí. Verificada en QEMU. - `CELDA_TASKBAR_ANCHO` baja de 150 a 130 px para que las ocho pestañas quepan holgadas con el lanzador y el reloj. -Líneas abiertas posteriores: reciclado de las ranuras de ventana cerradas; -audio con varias voces (PCM) más allá del tono único de la bocina. +## Fase 18 — red: virtio-net y el primer hola al exterior (completada) + +renaser hablaba solo. Esta fase abre una boca y una oreja al exterior con +virtio-net, reutilizando el `KernelHal` del disco y el mapeador MMIO. Al +arrancar, el kernel envía un ARP request al gateway de QEMU; lee la +respuesta y la registra. Verificada con captura pcap. + +- Driver `drivers/red`: monta `VirtIONet`, + expone `enviar(frame)` y `drenar_rx(callback)`. Sin pila TCP/IP — solo + ethernet crudo —; la composición de paquetes se hace en el llamante. +- `componer_arp_request`: helper para construir el ARP request inicial. +- `interrupts::registrar_irq_red` + handler de IDT, gemelo del de disco. +- Tarea `tarea_red` en el reactor: envía el ARP al arrancar, drena RX en + cada fotograma y vuelca cada paquete a COM1. +- QEMU args ganan `-netdev user,id=net0 -device virtio-net-pci`. + +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). ## Principios que persisten entre fases diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs index 8cb309c..b91762d 100644 --- a/renaser/boot/src/main.rs +++ b/renaser/boot/src/main.rs @@ -327,7 +327,13 @@ fn lanzar_qemu(imagen: &Path, ovmf: &str) -> Result<(), String> { .arg("--no-reboot") // El disco de objetos, como dispositivo virtio-blk sobre el bus PCI. .arg("-drive").arg(format!("format=raw,file={NOMBRE_DISCO},if=none,id=drv0")) - .arg("-device").arg("virtio-blk-pci,drive=drv0"); + .arg("-device").arg("virtio-blk-pci,drive=drv0") + // FASE 18 :: la tarjeta de red — `user mode networking` de QEMU, un + // NAT virtual hacia el host. Sin opciones extra: gateway en 10.0.2.2, + // DHCP/DNS en 10.0.2.3, el invitado en 10.0.2.15. El kernel envia un + // ARP request al gateway en cuanto arranca como prueba de vida. + .arg("-netdev").arg("user,id=net0") + .arg("-device").arg("virtio-net-pci,netdev=net0"); // Cualquier argumento extra tras `--` se reenvia a QEMU intacto. // Ejemplo: `cargo run -p boot -- -display none -d int`. diff --git a/renaser/kernel/src/drivers/mod.rs b/renaser/kernel/src/drivers/mod.rs index 25a8258..faa26dd 100644 --- a/renaser/kernel/src/drivers/mod.rs +++ b/renaser/kernel/src/drivers/mod.rs @@ -13,9 +13,12 @@ // (Fase 12). // * `raton` — el raton PS/2: el dispositivo auxiliar del 8042 + IRQ12, // paquetes de 3 bytes (Fase 13). +// * `red` — la tarjeta virtio-net sobre PCI: ethernet crudo, +// primer ARP al gateway de QEMU (Fase 18). // ============================================================================= pub mod altavoz; pub mod disco; pub mod pci; pub mod raton; +pub mod red; diff --git a/renaser/kernel/src/drivers/red.rs b/renaser/kernel/src/drivers/red.rs new file mode 100644 index 0000000..9291b56 --- /dev/null +++ b/renaser/kernel/src/drivers/red.rs @@ -0,0 +1,242 @@ +// ============================================================================= +// renaser :: kernel/src/drivers/red.rs — Fase 18 :: virtio-net +// ----------------------------------------------------------------------------- +// El kernel deja de hablar solo consigo mismo. Con el mismo patron del disco +// —enumerar PCI, montar el transporte de virtio, ceder a `virtio-drivers` el +// diálogo de bajo nivel— renaser abre una boca y una oreja al exterior: una +// tarjeta de red virtio. +// +// En esta primera version el kernel envia un ARP request al gateway de +// QEMU (10.0.2.2) en cuanto arranca, y registra por COM1 cada paquete que +// recibe. No hay pila TCP/IP — solo ethernet crudo. El proximo paso natural +// seria una capa de capacidades `sys_net_*` para que los apps tambien +// hablen, pero esa es otra fase. +// ============================================================================= + +use core::sync::atomic::{AtomicU64, AtomicU8, Ordering}; + +use spin::{Mutex, Once}; +use virtio_drivers::device::net::VirtIONet; +use virtio_drivers::transport::pci::bus::{Command, DeviceFunction, PciRoot}; +use virtio_drivers::transport::pci::PciTransport; +use x86_64::instructions::interrupts; + +use super::disco::KernelHal; +use super::pci::CamPuertos; + +/// Vendor ID de VirtIO; Device IDs de un dispositivo de red (legacy + modern). +const VENDOR_VIRTIO: u16 = 0x1AF4; +const VIRTIO_NET_IDS: [u16; 2] = [0x1000, 0x1041]; + +/// Tamaño maximo de paquete que reservamos por bufer (MTU 1500 + algo de holgura +/// para cabeceras virtio y futuras VLAN). +const PAQUETE_MAX: usize = 1600; + +/// Profundidad de las colas RX y TX. 16 es pequeño pero suficiente para el +/// trafico de un demo. +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. +pub const ETHER_TYPE_RENASER: u16 = 0x88B5; +/// EtherType de ARP. +pub const ETHER_TYPE_ARP: u16 = 0x0806; + +/// Direccion fisica de la tarjeta de red, en seis bytes MAC. +pub type Mac = [u8; 6]; + +/// IP de la maquina renaser, en QEMU user-mode networking (10.0.2.0/24). +pub const IP_RENASER: [u8; 4] = [10, 0, 2, 15]; +/// IP del gateway que QEMU expone hacia el host. +pub const IP_GATEWAY: [u8; 4] = [10, 0, 2, 2]; + +/// La tarjeta de red, ya montada. Envuelve a `VirtIONet` para que pueda vivir +/// en un `static`. +struct Tarjeta(VirtIONet); + +// SEGURIDAD: `Tarjeta` encierra punteros crudos a las colas virtio y al MMIO +// del dispositivo. renaser es de un solo nucleo y todo acceso a la tarjeta se +// serializa tras el `Mutex` global. Los accesos cooperativos se hacen con las +// interrupciones acalladas para que la IRQ del dispositivo jamas las dispute. +unsafe impl Send for Tarjeta {} + +/// La tarjeta global. Se monta una sola vez, en `montar`. +static TARJETA: Once> = Once::new(); + +/// La direccion MAC que el dispositivo nos asigno, cacheada para consulta. +static MAC: Once = Once::new(); + +/// La linea de IRQ asignada al dispositivo por el firmware. +static IRQ_RED: AtomicU8 = AtomicU8::new(0); + +/// Cuenta de paquetes recibidos desde el arranque. +static PAQUETES_RX: AtomicU64 = AtomicU64::new(0); +/// Cuenta de paquetes transmitidos desde el arranque. +static PAQUETES_TX: AtomicU64 = AtomicU64::new(0); + +// ============================================================================= +// Montaje +// ============================================================================= + +/// Enumera el bus PCI, localiza el virtio-net, monta su transporte moderno y +/// lo deja tras el `Mutex` global. Descubre su linea de IRQ y la enruta. +/// Devuelve la MAC que el dispositivo nos confiere. Toda falla se devuelve. +pub fn montar() -> Result { + let mut raiz = PciRoot::new(CamPuertos); + + // 1. Localizar el primer virtio-net en el bus. + let mut hallado: Option = None; + 'busqueda: for bus in 0..=255u8 { + for (device_function, info) in raiz.enumerate_bus(bus) { + if info.vendor_id == VENDOR_VIRTIO && VIRTIO_NET_IDS.contains(&info.device_id) { + hallado = Some(device_function); + break 'busqueda; + } + } + } + let device_function = hallado.ok_or("virtio-net no hallado en el bus PCI")?; + + // 2. Habilitar E/S, MMIO y BUS-MASTER en la configuracion PCI. + raiz.set_command( + device_function, + Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER, + ); + + // 3. Montar el transporte PCI moderno y el dispositivo de red. + let transporte = PciTransport::new::(&mut raiz, device_function) + .map_err(|_| "no se pudo montar el transporte PCI de virtio-net")?; + let mut nic = + VirtIONet::::new(transporte, PAQUETE_MAX) + .map_err(|_| "no se pudo inicializar el dispositivo virtio-net")?; + + let mac = nic.mac_address(); + nic.enable_interrupts(); + + TARJETA.call_once(|| Mutex::new(Tarjeta(nic))); + MAC.call_once(|| mac); + + // 4. Descubrir la linea de IRQ y enrutarla. + let irq = super::pci::linea_irq(device_function); + if (2..=15).contains(&irq) { + crate::interrupts::registrar_irq_red(irq); + crate::pic::desenmascarar(irq); + IRQ_RED.store(irq, Ordering::SeqCst); + } + + Ok(mac) +} + +// ============================================================================= +// IRQ +// ============================================================================= + +/// Punto de entrada DESDE el manejador de IRQ de la red. Acknowledge en el +/// dispositivo —para que la linea baje— y se sale. +pub fn atender_irq() { + if let Some(tarjeta) = TARJETA.get() { + // SEGURIDAD: en contexto de IRQ las interrupciones ya estan acalladas; + // tomar el cerrojo aqui no puede interbloquear con las tareas, que + // siempre lo toman con `interrupts::without_interrupts`. + let _ = tarjeta.lock().0.ack_interrupt(); + } +} + +/// La linea de IRQ del dispositivo, si el firmware enruto una util. +pub fn irq() -> Option { + let v = IRQ_RED.load(Ordering::SeqCst); + if v == 0 { + None + } else { + Some(v) + } +} + +// ============================================================================= +// Consulta y E/S — la interfaz para las tareas cooperativas +// ============================================================================= + +/// La MAC del dispositivo. `None` si la tarjeta aun no se ha montado. +#[allow(dead_code)] +pub fn mac() -> Option { + MAC.get().copied() +} + +/// Numero de paquetes recibidos desde el arranque. +#[allow(dead_code)] +pub fn paquetes_rx() -> u64 { + PAQUETES_RX.load(Ordering::Relaxed) +} + +/// Numero de paquetes transmitidos desde el arranque. +#[allow(dead_code)] +pub fn paquetes_tx() -> u64 { + PAQUETES_TX.load(Ordering::Relaxed) +} + +/// Envia un frame Ethernet crudo (cabecera + payload, sin CRC — el dispositivo +/// se la añade). El llamante construye el frame entero. +pub fn enviar(frame: &[u8]) -> Result<(), &'static str> { + let tarjeta = TARJETA.get().ok_or("red no montada")?; + interrupts::without_interrupts(|| { + let mut tarjeta = tarjeta.lock(); + let mut tx = tarjeta.0.new_tx_buffer(frame.len()); + tx.packet_mut().copy_from_slice(frame); + tarjeta.0.send(tx).map_err(|_| "envio fallido")?; + PAQUETES_TX.fetch_add(1, Ordering::Relaxed); + Ok(()) + }) +} + +/// 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. +pub fn drenar_rx(mut callback: F) { + let Some(tarjeta) = TARJETA.get() else { + return; + }; + interrupts::without_interrupts(|| { + let mut tarjeta = tarjeta.lock(); + loop { + if !tarjeta.0.can_recv() { + break; + } + let rx = match tarjeta.0.receive() { + Ok(r) => r, + Err(_) => break, + }; + callback(rx.packet()); + let _ = tarjeta.0.recycle_rx_buffer(rx); + PAQUETES_RX.fetch_add(1, Ordering::Relaxed); + } + }); +} + +// ============================================================================= +// Composicion de un ARP request — el primer paquete que renaser saluda +// ============================================================================= + +/// Compone un frame Ethernet con una peticion ARP que pregunta por la MAC del +/// host `objetivo_ip`. El gateway de QEMU lo responde — su replica entra por +/// la cola RX y se registra en COM1 desde la tarea cooperativa de la red. +pub fn componer_arp_request( + nuestro_mac: Mac, + nuestro_ip: [u8; 4], + objetivo_ip: [u8; 4], +) -> [u8; 42] { + let mut frame = [0u8; 42]; + // Cabecera Ethernet. + frame[0..6].copy_from_slice(&[0xff; 6]); // destino: broadcast + frame[6..12].copy_from_slice(&nuestro_mac); + frame[12..14].copy_from_slice(ÐER_TYPE_ARP.to_be_bytes()); + // Payload ARP (28 bytes). + frame[14..16].copy_from_slice(&1u16.to_be_bytes()); // HW type: Ethernet + frame[16..18].copy_from_slice(&0x0800u16.to_be_bytes()); // proto: IPv4 + frame[18] = 6; // HW len + frame[19] = 4; // proto len + frame[20..22].copy_from_slice(&1u16.to_be_bytes()); // opcode: REQUEST + frame[22..28].copy_from_slice(&nuestro_mac); // sender MAC + frame[28..32].copy_from_slice(&nuestro_ip); // sender IP + // bytes 32..38: target MAC, se quedan a cero + frame[38..42].copy_from_slice(&objetivo_ip); // target IP + frame +} diff --git a/renaser/kernel/src/interrupts.rs b/renaser/kernel/src/interrupts.rs index 47f436b..92d40b1 100644 --- a/renaser/kernel/src/interrupts.rs +++ b/renaser/kernel/src/interrupts.rs @@ -26,6 +26,9 @@ static IDT: CeldaSync = /// legitima del disco vive en el vector 0 (reservado a las excepciones). static VECTOR_DISCO: AtomicU8 = AtomicU8::new(0); +/// Vector de la IDT asignado a la IRQ de la red (Fase 18). Mismo patron. +static VECTOR_RED: AtomicU8 = AtomicU8::new(0); + /// Construye y activa la Interrupt Descriptor Table. /// /// Debe invocarse una sola vez, durante el arranque, DESPUES de [`gdt::init`]. @@ -78,6 +81,17 @@ pub fn registrar_irq_disco(irq: u8) { idt[vector].set_handler_fn(irq_disco); } +/// Registra el manejador de la IRQ de la red virtio-net en la IDT (Fase 18). +/// Gemelo de [`registrar_irq_disco`]: las mismas condiciones de arranque +/// secuencial garantizan la mutacion segura. +pub fn registrar_irq_red(irq: u8) { + let vector = pic::vector_irq(irq); + VECTOR_RED.store(vector, Ordering::SeqCst); + // SEGURIDAD: ver `registrar_irq_disco`. + let idt: &'static mut InterruptDescriptorTable = unsafe { &mut *IDT.puntero() }; + idt[vector].set_handler_fn(irq_red); +} + // ============================================================================= // REFLEJOS DE EXCEPCION — las rutinas a las que la CPU salta ante cada fallo // ============================================================================= @@ -159,3 +173,11 @@ extern "x86-interrupt" fn irq_disco(_marco: InterruptStackFrame) { // PCI es de nivel — anunciar el fin sin haber bajado la linea la reavivaria. pic::fin_de_interrupcion(VECTOR_DISCO.load(Ordering::SeqCst)); } + +/// IRQ de la red — virtio-net (Fase 18). Llego un paquete (o un envio termino): +/// acknowledge en el dispositivo —que baja la linea— y EOI al PIC. Las tareas +/// cooperativas drenan despues la cola RX y consumen los paquetes. +extern "x86-interrupt" fn irq_red(_marco: InterruptStackFrame) { + crate::drivers::red::atender_irq(); + pic::fin_de_interrupcion(VECTOR_RED.load(Ordering::SeqCst)); +} diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs index b7eb9e3..f612bb4 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -174,6 +174,54 @@ 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. +async fn tarea_red(mac: drivers::red::Mac) { + // Dejar un par de fotogramas para que la cola RX se estabilice. + for _ in 0..10 { + async_system::reloj::EsperaFrame::nueva().await; + } + // Componer y enviar el ARP request: «¿quien tiene 10.0.2.2?». + let frame = drivers::red::componer_arp_request( + mac, + drivers::red::IP_RENASER, + drivers::red::IP_GATEWAY, + ); + match drivers::red::enviar(&frame) { + Ok(()) => { + let _ = writeln!( + baliza::Serie, + "red :: ARP REQUEST enviado :: ¿quien tiene 10.0.2.2?" + ); + } + Err(motivo) => { + 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, + ); + }); + } +} + /// FASE 6.2 — la prueba viva de la E/S asincrona. Esta tarea del reactor lee el /// sector 0 del disco SIN bloquear: cede la CPU mientras el disco trabaja —las /// apps siguen pintando entre tanto— y la IRQ del disco la reanuda cuando el @@ -511,6 +559,25 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { drivers::raton::init(ancho_lienzo, alto_lienzo); traza("raton :: listo"); + // --- 6.7. FASE 18 :: montar la tarjeta virtio-net. Si el firmware no + // enruta una linea de IRQ util o no hay dispositivo, el resto + // del arranque sigue — la red NO es critica. + let mac_red = drivers::red::montar(); + match mac_red { + Ok(mac) => { + let _ = writeln!( + baliza::Serie, + "red :: virtio-net :: MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} :: IRQ {:?}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + drivers::red::irq(), + ); + } + Err(motivo) => { + let _ = writeln!(baliza::Serie, "red :: virtio-net :: {motivo}"); + } + } + traza("red :: listo"); + // --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL // GRAFO. El kernel ya no empotra los modulos WASM: lee el // Manifiesto de Genesis que `boot` sembro en la imagen de disco e @@ -530,6 +597,11 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // disco de forma ASINCRONA: la demostracion de que la IRQ del disco // conduce la E/S sin detener a las aplicaciones visuales. ejecutor.spawn(tarea_sonda_disco()); + // FASE 18 :: si la tarjeta de red se monto, una tarea le envia un ARP + // request al gateway y registra por COM1 los paquetes entrantes. + if let Ok(mac) = mac_red { + ejecutor.spawn(tarea_red(mac)); + } // FASE 15 :: la voz del sistema da los buenos dias con un acorde de Do // mayor. La tarea del compositor lo hara sonar nota a nota una vez que // el reactor arranque y las interrupciones empiecen a llegar.