feat(renaser): Fase 18 — red: virtio-net y el primer hola al exterior

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<KernelHal, PciTransport, 16>`,
  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 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-23 04:06:23 +00:00
parent 60553bec44
commit bdd088b89e
9 changed files with 420 additions and 6 deletions
+37
View File
@@ -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 texto justo donde quedó. El `almacen` reporta 24 objetos en el grafo
(frente a 9 antes de escribir) y `raiz presente`: cada `guardar` anexó (frente a 9 antes de escribir) y `raiz presente`: cada `guardar` anexó
una versión al log direccionado por contenido. 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<KernelHal, PciTransport, 16>`,
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.
+4 -3
View File
@@ -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 arrancar, repique al lanzar o cerrar, bajo al desalojar, con prioridad
sobre `sys_tono`— la Fase 16 COMPLETA —la barra viva: botón «+» 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 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 segundo— la Fase 17 COMPLETA —`bitacora`, editor de texto que persiste entre
entre arranques en el grafo de objetos (tipografía 8×8 embebida)—. arranques en el grafo de objetos (tipografía 8×8 embebida)— y la Fase 18
Todo verificado en QEMU. Ver `ROADMAP.md`. 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 ## Flujo de trabajo
+14
View File
@@ -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 intacta. Apaga, enciende, sigue escribiendo. La casa no olvida lo que se
le confía. 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.* *El diario continúa. La próxima página la escribirá la próxima jornada.*
+19 -2
View File
@@ -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 - `CELDA_TASKBAR_ANCHO` baja de 150 a 130 px para que las ocho pestañas
quepan holgadas con el lanzador y el reloj. quepan holgadas con el lanzador y el reloj.
Líneas abiertas posteriores: reciclado de las ranuras de ventana cerradas; ## Fase 18 — red: virtio-net y el primer hola al exterior (completada)
audio con varias voces (PCM) más allá del tono único de la bocina.
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<KernelHal, PciTransport, 16>`,
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 ## Principios que persisten entre fases
+7 -1
View File
@@ -327,7 +327,13 @@ fn lanzar_qemu(imagen: &Path, ovmf: &str) -> Result<(), String> {
.arg("--no-reboot") .arg("--no-reboot")
// El disco de objetos, como dispositivo virtio-blk sobre el bus PCI. // 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("-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. // Cualquier argumento extra tras `--` se reenvia a QEMU intacto.
// Ejemplo: `cargo run -p boot -- -display none -d int`. // Ejemplo: `cargo run -p boot -- -display none -d int`.
+3
View File
@@ -13,9 +13,12 @@
// (Fase 12). // (Fase 12).
// * `raton` — el raton PS/2: el dispositivo auxiliar del 8042 + IRQ12, // * `raton` — el raton PS/2: el dispositivo auxiliar del 8042 + IRQ12,
// paquetes de 3 bytes (Fase 13). // 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 altavoz;
pub mod disco; pub mod disco;
pub mod pci; pub mod pci;
pub mod raton; pub mod raton;
pub mod red;
+242
View File
@@ -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<KernelHal, PciTransport, PROFUNDIDAD_COLA>);
// 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<Mutex<Tarjeta>> = Once::new();
/// La direccion MAC que el dispositivo nos asigno, cacheada para consulta.
static MAC: Once<Mac> = 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<Mac, &'static str> {
let mut raiz = PciRoot::new(CamPuertos);
// 1. Localizar el primer virtio-net en el bus.
let mut hallado: Option<DeviceFunction> = 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::<KernelHal, _>(&mut raiz, device_function)
.map_err(|_| "no se pudo montar el transporte PCI de virtio-net")?;
let mut nic =
VirtIONet::<KernelHal, _, PROFUNDIDAD_COLA>::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<u8> {
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> {
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<F: FnMut(&[u8])>(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(&ETHER_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
}
+22
View File
@@ -26,6 +26,9 @@ static IDT: CeldaSync<InterruptDescriptorTable> =
/// legitima del disco vive en el vector 0 (reservado a las excepciones). /// legitima del disco vive en el vector 0 (reservado a las excepciones).
static VECTOR_DISCO: AtomicU8 = AtomicU8::new(0); 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. /// Construye y activa la Interrupt Descriptor Table.
/// ///
/// Debe invocarse una sola vez, durante el arranque, DESPUES de [`gdt::init`]. /// 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); 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 // 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. // PCI es de nivel — anunciar el fin sin haber bajado la linea la reavivaria.
pic::fin_de_interrupcion(VECTOR_DISCO.load(Ordering::SeqCst)); 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));
}
+72
View File
@@ -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 /// 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 /// 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 /// 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); drivers::raton::init(ancho_lienzo, alto_lienzo);
traza("raton :: listo"); 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 // --- 7. FASE 7 :: levantar el reactor y poblar el userspace DESDE EL
// GRAFO. El kernel ya no empotra los modulos WASM: lee el // GRAFO. El kernel ya no empotra los modulos WASM: lee el
// Manifiesto de Genesis que `boot` sembro en la imagen de disco e // 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 // disco de forma ASINCRONA: la demostracion de que la IRQ del disco
// conduce la E/S sin detener a las aplicaciones visuales. // conduce la E/S sin detener a las aplicaciones visuales.
ejecutor.spawn(tarea_sonda_disco()); 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 // 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 // mayor. La tarea del compositor lo hara sonar nota a nota una vez que
// el reactor arranque y las interrupciones empiecen a llegar. // el reactor arranque y las interrupciones empiecen a llegar.