From bcba94555717fe5921c48a29887f155e0beaafd2 Mon Sep 17 00:00:00 2001 From: Markus Maiwald Date: Tue, 30 Dec 2025 23:39:51 +0100 Subject: [PATCH] wip(rumpk): Phase 3.5 Live Wire - 95% Complete (TX Wire Issue) - Implemented ping_ion.zig: Sovereign ARP/ICMP Responder - Fixed VirtIO header offset (10-byte skip) - Fixed packed struct size issues (hardcoded 14/28/20 byte headers) - Full data path working: RX -> NPL Parse -> TX Push -> Kernel Drain -> VirtIO Queue - Remaining: VirtIO TX packets not reaching wire (needs tcpdump debugging) - ARP Reply crafted correctly, ICMP Echo Reply crafted correctly - VirtIO notify called, but packets not observed by host --- core/kernel.nim | 42 +++++++++++++++----- hal/virtio_net.zig | 55 ++++++++++++++++---------- npl/ping_ion.zig | 98 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 151 insertions(+), 44 deletions(-) diff --git a/core/kernel.nim b/core/kernel.nim index a638511..342de72 100644 --- a/core/kernel.nim +++ b/core/kernel.nim @@ -83,9 +83,14 @@ proc rumpk_yield_internal() {.cdecl, exportc.} = return elif load == 0: # IDLE MODE (Phase 3): No pending commands. - # In a purely cooperative system, we don't WFI here to avoid hanging - # without a timer IRQ. The Watchdog will manage the sleep. - discard + # We must enable interrupts to receive packets! + asm "csrsi sstatus, 2" + # We can just yield here, interrupts will fire and preempt us (if we had preemption) + # or fire and return to here. + # But if we just loop, we burn CPU. + # Ideally: WFI. + # For now: Just enable interrupts so ISR can fire. + # asm "wfi" # Optional: Save power. # Normal Round Robin logic if current_fiber == addr fiber_ion: @@ -100,17 +105,15 @@ proc rumpk_yield_internal() {.cdecl, exportc.} = proc fiber_yield*() {.exportc, cdecl.} = rumpk_yield_internal() + # Utility moved up + # Channel API (The Valve) - Wrappers for ION # Channel API is imported from ion.nim - - - - # HAL/NPL Entry points proc rumpk_halt() {.importc, cdecl, noreturn.} proc hal_io_init() {.importc, cdecl.} @@ -120,6 +123,7 @@ proc launch_subject() {.importc, cdecl.} # Hardware Ingress (Zig -> Nim) proc ion_ingress*(id: uint16, len: uint16) {.exportc, cdecl.} = ## Intercept raw hardware packet and push to Sovereign RX Channel + # kprint("[Kernel] Ingress ID: "); kprint_int(int(id)); kprint(" Len: "); kprint_int(int(len)); kprintln("") let data = ion_get_virt(id) var pkt = IonPacket(data: cast[ptr UncheckedArray[byte]](data), len: len, id: id) chan_rx.send(pkt) @@ -144,6 +148,11 @@ proc subject_fiber_entry() {.cdecl.} = # Include Watchdog Logic (Access to Kernel Globals) include watchdog +# HAL Driver API +proc virtio_net_poll() {.importc, cdecl.} +proc virtio_net_send(data: pointer, len: csize_t) {.importc, cdecl.} +proc ion_free_raw(id: uint16) {.importc, cdecl.} + proc ion_fiber_entry() {.cdecl.} = kprint("[ION] Fiber 1 Reporting for Duty.") if ion_paused: kprintln(" (PAUSED)") else: kprintln("") @@ -152,6 +161,9 @@ proc ion_fiber_entry() {.cdecl.} = var cmd: CmdPacket while true: + # 0. Poll Hardware (The Heartbeat) + virtio_net_poll() + # 1. Process Commands (Drain the ring!) while chan_cmd.recv(cmd): if cmd.kind == uint32(CMD_ION_STOP): @@ -165,9 +177,14 @@ proc ion_fiber_entry() {.cdecl.} = # 2. Process Data (if not paused, Drain the ring!) if not ion_paused: while chan_tx.recv(pkt): - # High speed telemetry logic - var alert = IonPacket(id: 777, len: 42) - chan_event.send(alert) + # Transmit to Hardware + kprint("[ION] TX from chan_tx, len=") + # kprint_int(int(pkt.len)) + kprintln("") + virtio_net_send(pkt.data, csize_t(pkt.len)) + # Zero-Copy Ingest means we own the buffer now. + # Since virtio_net_send copies (for now), we must free the original slab. + ion_free_raw(pkt.id) fiber_yield() @@ -234,6 +251,11 @@ proc kmain() {.exportc, cdecl.} = # 4. WATCHDOG FIBER (The Immune System) init_fiber(addr fiber_watchdog, watchdog_loop, addr stack_watchdog[0], sizeof(stack_watchdog)) + # [FIX] GLOBAL INTERRUPT ENABLE + # Open the ear before we enter the loop. + kprintln("[Kernel] Enabling Supervisor Interrupts (SIE)...") + asm "csrsi sstatus, 2" + kprintln("[Kernel] All Systems Go. Entering Autonomous Loop.") # Handover to Scheduler (The Heartbeat) diff --git a/hal/virtio_net.zig b/hal/virtio_net.zig index 18b71ab..cdef332 100644 --- a/hal/virtio_net.zig +++ b/hal/virtio_net.zig @@ -1,3 +1,4 @@ +// MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) // Rumpk Layer 0: VirtIO-Net Driver (Sovereign Edition) // - Uses VirtioTransport for PCI Capability Traversal // - Supports both Legacy (I/O & Memory) and Modern VirtIO @@ -26,17 +27,17 @@ var poll_count: u32 = 0; export fn virtio_net_poll() void { poll_count += 1; - // Periodic debug: show queue state - if (poll_count == 1 or (poll_count % 1000000 == 0)) { - if (global_driver) |*d| { - if (d.rx_queue) |_| { - asm volatile ("fence" ::: .{ .memory = true }); - uart.print("[VirtIO] Poll #"); - uart.print_hex(poll_count); - uart.print("\n"); - } - } - } + // Periodic debug: show queue state (SILENCED FOR PRODUCTION) + // if (poll_count == 1 or (poll_count % 1000000 == 0)) { + // if (global_driver) |*d| { + // if (d.rx_queue) |_| { + // asm volatile ("fence" ::: .{ .memory = true }); + // uart.print("[VirtIO] Poll #"); + // uart.print_hex(poll_count); + // uart.print("\n"); + // } + // } + // } if (global_driver) |*d| { if (d.rx_queue) |q| { @@ -61,6 +62,9 @@ export fn virtio_net_poll() void { } export fn virtio_net_send(data: [*]const u8, len: usize) void { + uart.print("[VirtIO] virtio_net_send called, len="); + uart.print_hex(len); + uart.print("\n"); if (global_driver) |*d| { d.send_packet(data, len); } @@ -274,25 +278,31 @@ pub const VirtioNetDriver = struct { var replenished: bool = false; while (q.index != hw_idx) { - uart.print("[VirtIO RX] Processing Packet...\n"); + // uart.print("[VirtIO RX] Processing Packet...\n"); const elem = used_ring[q.index % q.num]; const desc_idx = elem.id; const slab_id = q.ids[desc_idx]; const len = elem.len; - uart.print(" Desc: "); - uart.print_hex(@intCast(desc_idx)); - uart.print(" Len: "); - uart.print_hex(len); - uart.print(" Slab: "); - uart.print_hex(slab_id); - uart.print("\n"); + // uart.print(" Desc: "); + // uart.print_hex(@intCast(desc_idx)); + // uart.print(" Len: "); + // uart.print_hex(len); + // uart.print(" Slab: "); + // uart.print_hex(slab_id); + // uart.print("\n"); const header_len: u32 = 10; if (len > header_len) { - // Call ION - ion_ingress(slab_id, @intCast(len)); + // Call ION - Pass only the Ethernet Frame (Skip VirtIO Header) + // ion_ingress receives slab_id which contains full buffer. + // We need to tell it the offset. + // Hack: Pass `len - header_len` as the actual Ethernet frame length. + // The NPL must then offset into the buffer by 10 to get to Ethernet. + // OR: We adjust here. Let's adjust here by storing offset. + // Simplest: Pass len directly, NPL will skip first 10 bytes. + ion_ingress(slab_id, @intCast(len - header_len)); } else { uart.print(" [Warn] Packet too short/empty\n"); ion_free_raw(slab_id); @@ -406,6 +416,9 @@ pub const VirtioNetDriver = struct { asm volatile ("fence" ::: .{ .memory = true }); self.transport.notify(1); + uart.print("[VirtIO TX] Queued & Notified Len="); + uart.print_hex(header_len + copy_len); + uart.print("\n"); } const Virtqueue = struct { diff --git a/npl/ping_ion.zig b/npl/ping_ion.zig index e9e50cb..6738de4 100644 --- a/npl/ping_ion.zig +++ b/npl/ping_ion.zig @@ -1,8 +1,8 @@ -const std = @import("std"); - // MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) // RUMPK NPL // LIVE WIRE (ARP/ICMP RESPONDER) +const std = @import("std"); + // 1. The SysTable Contract (Must match Kernel!) const ION_BASE = 0x83000000; @@ -179,6 +179,12 @@ export fn main() c_int { const sys = get_systable(); print("[LIVE] Waiting for Packets (ARP/ICMP)...\n"); + // Verify SysTable Magic + if (sys.magic != 0x4E585553) { + print("[LIVE] PANIC: SysTable Magic Mismatch!\n"); + return 1; + } + const rx_ring = sys.s_rx; const tx_ring = sys.s_tx; @@ -192,6 +198,10 @@ export fn main() c_int { // We have a packet! const pkt = rx_ring.data[tail & rx_ring.mask]; + print("[LIVE] RX Packet Len: "); + print_u64(pkt.len); + print("\n"); + // Process it handle_packet(pkt, tx_ring); @@ -210,14 +220,20 @@ export fn main() c_int { } fn handle_packet(pkt: IonPacket, tx_ring: *RingBufferPacket) void { - const data_ptr: [*]u8 = @ptrFromInt(pkt.data); - const data: []u8 = data_ptr[0..pkt.len]; + // Skip VirtIO NET Header (10 bytes) + const VIRTIO_HEADER_LEN = 10; + const data_ptr: [*]u8 = @ptrFromInt(pkt.data + VIRTIO_HEADER_LEN); + const data: []u8 = data_ptr[0..pkt.len]; // len is already adjusted by HAL if (data.len < @sizeOf(EthHeader)) return; const eth = @as(*align(1) EthHeader, @ptrCast(data.ptr)); const eth_type = ntohs(eth.type); + print("[LIVE] EthType: 0x"); + print_hex(eth_type); + print("\n"); + if (eth_type == 0x0806) { // ARP handle_arp(data, eth, tx_ring, pkt); } else if (eth_type == 0x0800) { // IPv4 @@ -226,14 +242,39 @@ fn handle_packet(pkt: IonPacket, tx_ring: *RingBufferPacket) void { } fn handle_arp(data: []u8, eth: *align(1) EthHeader, tx_ring: *RingBufferPacket, pkt: IonPacket) void { - if (data.len < @sizeOf(EthHeader) + @sizeOf(ArpHeader)) return; + // Ethernet Header = 14 bytes, ARP Header = 28 bytes, Total = 42 bytes + const ETH_HLEN: usize = 14; + const ARP_HLEN: usize = 28; + const required_len = ETH_HLEN + ARP_HLEN; - const arp = @as(*align(1) ArpHeader, @ptrCast(data.ptr + @sizeOf(EthHeader))); + print("[LIVE] ARP Check: data.len="); + print_u64(data.len); + print(" required="); + print_u64(required_len); + print("\n"); + + if (data.len < required_len) return; + + const arp = @as(*align(1) ArpHeader, @ptrCast(data.ptr + ETH_HLEN)); // Check if Request (1) and Target IP Matches + // We unroll the check manually or use bytes + const opcode_val = ntohs(arp.opcode); + print("[LIVE] ARP Opcode: "); + print_u64(opcode_val); + print(" Target: "); + print_u64(arp.tip0); + print("."); + print_u64(arp.tip1); + print("."); + print_u64(arp.tip2); + print("."); + print_u64(arp.tip3); + print("\n"); + // Just check manually - if (ntohs(arp.opcode) == 1 and + if (opcode_val == 1 and arp.tip0 == MY_IP[0] and arp.tip1 == MY_IP[1] and arp.tip2 == MY_IP[2] and arp.tip3 == MY_IP[3]) { @@ -285,15 +326,39 @@ fn handle_arp(data: []u8, eth: *align(1) EthHeader, tx_ring: *RingBufferPacket, arp.sip2 = MY_IP[2]; arp.sip3 = MY_IP[3]; - // 3. Send - send_packet(tx_ring, pkt); + // 3. Send - Create TX packet pointing to Ethernet frame (not VirtIO header) + // pkt.data points to slab start (VirtIO header at offset 0) + // data.ptr points to Ethernet frame (offset 10) + // len should be the Ethernet frame size (42 for ARP) + const tx_pkt = IonPacket{ + .data = @intFromPtr(data.ptr), // Points to Ethernet frame + .phys = 0, + .len = 42, // Ethernet frame length for ARP + .id = pkt.id, + }; + send_packet(tx_ring, tx_pkt); } } fn handle_ipv4(data: []u8, eth: *align(1) EthHeader, tx_ring: *RingBufferPacket, pkt: IonPacket) void { - if (data.len < @sizeOf(EthHeader) + @sizeOf(IpHeader)) return; + const ETH_HLEN: usize = 14; + const IP_HLEN: usize = 20; - const ip = @as(*align(1) IpHeader, @ptrCast(data.ptr + @sizeOf(EthHeader))); + if (data.len < ETH_HLEN + IP_HLEN) return; + + const ip = @as(*align(1) IpHeader, @ptrCast(data.ptr + ETH_HLEN)); + + print("[LIVE] IP Dst: "); + print_u64(ip.dst0); + print("."); + print_u64(ip.dst1); + print("."); + print_u64(ip.dst2); + print("."); + print_u64(ip.dst3); + print(" Proto: "); + print_u64(ip.proto); + print("\n"); // Check if destined for us if (ip.dst0 != MY_IP[0] or ip.dst1 != MY_IP[1] or @@ -301,14 +366,18 @@ fn handle_ipv4(data: []u8, eth: *align(1) EthHeader, tx_ring: *RingBufferPacket, if (ip.proto == 1) { // ICMP const ip_header_len = (ip.ver_ihl & 0x0F) * 4; - const icmp_offset = @sizeOf(EthHeader) + ip_header_len; + const icmp_offset = ETH_HLEN + ip_header_len; if (data.len < icmp_offset + @sizeOf(IcmpHeader)) return; const icmp = @as(*align(1) IcmpHeader, @ptrCast(data.ptr + icmp_offset)); + print("[LIVE] ICMP Type: "); + print_u64(icmp.type); + print("\n"); + if (icmp.type == 8) { // Echo Request - // print("[LIVE] Ping! Ponging...\n"); + print("[LIVE] Ping! Ponging...\n"); // 1. Eth Header eth.dst0 = eth.src0; @@ -373,6 +442,9 @@ fn send_packet(tx_ring: *RingBufferPacket, pkt: IonPacket) void { if (next != tail) { tx_ring.data[head & mask] = pkt; @atomicStore(u32, &tx_ring.head, next, .release); + print("[LIVE] TX Pushed! Len="); + print_u64(pkt.len); + print("\n"); } else { print("[LIVE] TX Full! Dropping.\n"); }