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
This commit is contained in:
Markus Maiwald 2025-12-30 23:39:51 +01:00
parent 0aa8febe46
commit bcba945557
3 changed files with 151 additions and 44 deletions

View File

@ -83,9 +83,14 @@ proc rumpk_yield_internal() {.cdecl, exportc.} =
return return
elif load == 0: elif load == 0:
# IDLE MODE (Phase 3): No pending commands. # IDLE MODE (Phase 3): No pending commands.
# In a purely cooperative system, we don't WFI here to avoid hanging # We must enable interrupts to receive packets!
# without a timer IRQ. The Watchdog will manage the sleep. asm "csrsi sstatus, 2"
discard # 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 # Normal Round Robin logic
if current_fiber == addr fiber_ion: if current_fiber == addr fiber_ion:
@ -100,17 +105,15 @@ proc rumpk_yield_internal() {.cdecl, exportc.} =
proc fiber_yield*() {.exportc, cdecl.} = proc fiber_yield*() {.exportc, cdecl.} =
rumpk_yield_internal() rumpk_yield_internal()
# Utility moved up # Utility moved up
# Channel API (The Valve) - Wrappers for ION # Channel API (The Valve) - Wrappers for ION
# Channel API is imported from ion.nim # Channel API is imported from ion.nim
# HAL/NPL Entry points # HAL/NPL Entry points
proc rumpk_halt() {.importc, cdecl, noreturn.} proc rumpk_halt() {.importc, cdecl, noreturn.}
proc hal_io_init() {.importc, cdecl.} proc hal_io_init() {.importc, cdecl.}
@ -120,6 +123,7 @@ proc launch_subject() {.importc, cdecl.}
# Hardware Ingress (Zig -> Nim) # Hardware Ingress (Zig -> Nim)
proc ion_ingress*(id: uint16, len: uint16) {.exportc, cdecl.} = proc ion_ingress*(id: uint16, len: uint16) {.exportc, cdecl.} =
## Intercept raw hardware packet and push to Sovereign RX Channel ## 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) let data = ion_get_virt(id)
var pkt = IonPacket(data: cast[ptr UncheckedArray[byte]](data), len: len, id: id) var pkt = IonPacket(data: cast[ptr UncheckedArray[byte]](data), len: len, id: id)
chan_rx.send(pkt) chan_rx.send(pkt)
@ -144,6 +148,11 @@ proc subject_fiber_entry() {.cdecl.} =
# Include Watchdog Logic (Access to Kernel Globals) # Include Watchdog Logic (Access to Kernel Globals)
include watchdog 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.} = proc ion_fiber_entry() {.cdecl.} =
kprint("[ION] Fiber 1 Reporting for Duty.") kprint("[ION] Fiber 1 Reporting for Duty.")
if ion_paused: kprintln(" (PAUSED)") else: kprintln("") if ion_paused: kprintln(" (PAUSED)") else: kprintln("")
@ -152,6 +161,9 @@ proc ion_fiber_entry() {.cdecl.} =
var cmd: CmdPacket var cmd: CmdPacket
while true: while true:
# 0. Poll Hardware (The Heartbeat)
virtio_net_poll()
# 1. Process Commands (Drain the ring!) # 1. Process Commands (Drain the ring!)
while chan_cmd.recv(cmd): while chan_cmd.recv(cmd):
if cmd.kind == uint32(CMD_ION_STOP): 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!) # 2. Process Data (if not paused, Drain the ring!)
if not ion_paused: if not ion_paused:
while chan_tx.recv(pkt): while chan_tx.recv(pkt):
# High speed telemetry logic # Transmit to Hardware
var alert = IonPacket(id: 777, len: 42) kprint("[ION] TX from chan_tx, len=")
chan_event.send(alert) # 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() fiber_yield()
@ -234,6 +251,11 @@ proc kmain() {.exportc, cdecl.} =
# 4. WATCHDOG FIBER (The Immune System) # 4. WATCHDOG FIBER (The Immune System)
init_fiber(addr fiber_watchdog, watchdog_loop, addr stack_watchdog[0], sizeof(stack_watchdog)) 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.") kprintln("[Kernel] All Systems Go. Entering Autonomous Loop.")
# Handover to Scheduler (The Heartbeat) # Handover to Scheduler (The Heartbeat)

View File

@ -1,3 +1,4 @@
// MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
// Rumpk Layer 0: VirtIO-Net Driver (Sovereign Edition) // Rumpk Layer 0: VirtIO-Net Driver (Sovereign Edition)
// - Uses VirtioTransport for PCI Capability Traversal // - Uses VirtioTransport for PCI Capability Traversal
// - Supports both Legacy (I/O & Memory) and Modern VirtIO // - Supports both Legacy (I/O & Memory) and Modern VirtIO
@ -26,17 +27,17 @@ var poll_count: u32 = 0;
export fn virtio_net_poll() void { export fn virtio_net_poll() void {
poll_count += 1; poll_count += 1;
// Periodic debug: show queue state // Periodic debug: show queue state (SILENCED FOR PRODUCTION)
if (poll_count == 1 or (poll_count % 1000000 == 0)) { // if (poll_count == 1 or (poll_count % 1000000 == 0)) {
if (global_driver) |*d| { // if (global_driver) |*d| {
if (d.rx_queue) |_| { // if (d.rx_queue) |_| {
asm volatile ("fence" ::: .{ .memory = true }); // asm volatile ("fence" ::: .{ .memory = true });
uart.print("[VirtIO] Poll #"); // uart.print("[VirtIO] Poll #");
uart.print_hex(poll_count); // uart.print_hex(poll_count);
uart.print("\n"); // uart.print("\n");
} // }
} // }
} // }
if (global_driver) |*d| { if (global_driver) |*d| {
if (d.rx_queue) |q| { 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 { 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| { if (global_driver) |*d| {
d.send_packet(data, len); d.send_packet(data, len);
} }
@ -274,25 +278,31 @@ pub const VirtioNetDriver = struct {
var replenished: bool = false; var replenished: bool = false;
while (q.index != hw_idx) { 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 elem = used_ring[q.index % q.num];
const desc_idx = elem.id; const desc_idx = elem.id;
const slab_id = q.ids[desc_idx]; const slab_id = q.ids[desc_idx];
const len = elem.len; const len = elem.len;
uart.print(" Desc: "); // uart.print(" Desc: ");
uart.print_hex(@intCast(desc_idx)); // uart.print_hex(@intCast(desc_idx));
uart.print(" Len: "); // uart.print(" Len: ");
uart.print_hex(len); // uart.print_hex(len);
uart.print(" Slab: "); // uart.print(" Slab: ");
uart.print_hex(slab_id); // uart.print_hex(slab_id);
uart.print("\n"); // uart.print("\n");
const header_len: u32 = 10; const header_len: u32 = 10;
if (len > header_len) { if (len > header_len) {
// Call ION // Call ION - Pass only the Ethernet Frame (Skip VirtIO Header)
ion_ingress(slab_id, @intCast(len)); // 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 { } else {
uart.print(" [Warn] Packet too short/empty\n"); uart.print(" [Warn] Packet too short/empty\n");
ion_free_raw(slab_id); ion_free_raw(slab_id);
@ -406,6 +416,9 @@ pub const VirtioNetDriver = struct {
asm volatile ("fence" ::: .{ .memory = true }); asm volatile ("fence" ::: .{ .memory = true });
self.transport.notify(1); self.transport.notify(1);
uart.print("[VirtIO TX] Queued & Notified Len=");
uart.print_hex(header_len + copy_len);
uart.print("\n");
} }
const Virtqueue = struct { const Virtqueue = struct {

View File

@ -1,8 +1,8 @@
const std = @import("std");
// MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) // MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
// RUMPK NPL // LIVE WIRE (ARP/ICMP RESPONDER) // RUMPK NPL // LIVE WIRE (ARP/ICMP RESPONDER)
const std = @import("std");
// 1. The SysTable Contract (Must match Kernel!) // 1. The SysTable Contract (Must match Kernel!)
const ION_BASE = 0x83000000; const ION_BASE = 0x83000000;
@ -179,6 +179,12 @@ export fn main() c_int {
const sys = get_systable(); const sys = get_systable();
print("[LIVE] Waiting for Packets (ARP/ICMP)...\n"); 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 rx_ring = sys.s_rx;
const tx_ring = sys.s_tx; const tx_ring = sys.s_tx;
@ -192,6 +198,10 @@ export fn main() c_int {
// We have a packet! // We have a packet!
const pkt = rx_ring.data[tail & rx_ring.mask]; const pkt = rx_ring.data[tail & rx_ring.mask];
print("[LIVE] RX Packet Len: ");
print_u64(pkt.len);
print("\n");
// Process it // Process it
handle_packet(pkt, tx_ring); handle_packet(pkt, tx_ring);
@ -210,14 +220,20 @@ export fn main() c_int {
} }
fn handle_packet(pkt: IonPacket, tx_ring: *RingBufferPacket) void { fn handle_packet(pkt: IonPacket, tx_ring: *RingBufferPacket) void {
const data_ptr: [*]u8 = @ptrFromInt(pkt.data); // Skip VirtIO NET Header (10 bytes)
const data: []u8 = data_ptr[0..pkt.len]; 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; if (data.len < @sizeOf(EthHeader)) return;
const eth = @as(*align(1) EthHeader, @ptrCast(data.ptr)); const eth = @as(*align(1) EthHeader, @ptrCast(data.ptr));
const eth_type = ntohs(eth.type); const eth_type = ntohs(eth.type);
print("[LIVE] EthType: 0x");
print_hex(eth_type);
print("\n");
if (eth_type == 0x0806) { // ARP if (eth_type == 0x0806) { // ARP
handle_arp(data, eth, tx_ring, pkt); handle_arp(data, eth, tx_ring, pkt);
} else if (eth_type == 0x0800) { // IPv4 } 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 { 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 // Check if Request (1) and Target IP Matches
// We unroll the check manually or use bytes // 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 // 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.tip0 == MY_IP[0] and arp.tip1 == MY_IP[1] and
arp.tip2 == MY_IP[2] and arp.tip3 == MY_IP[3]) 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.sip2 = MY_IP[2];
arp.sip3 = MY_IP[3]; arp.sip3 = MY_IP[3];
// 3. Send // 3. Send - Create TX packet pointing to Ethernet frame (not VirtIO header)
send_packet(tx_ring, pkt); // 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 { 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 // Check if destined for us
if (ip.dst0 != MY_IP[0] or ip.dst1 != MY_IP[1] or 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 if (ip.proto == 1) { // ICMP
const ip_header_len = (ip.ver_ihl & 0x0F) * 4; 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; if (data.len < icmp_offset + @sizeOf(IcmpHeader)) return;
const icmp = @as(*align(1) IcmpHeader, @ptrCast(data.ptr + icmp_offset)); 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 if (icmp.type == 8) { // Echo Request
// print("[LIVE] Ping! Ponging...\n"); print("[LIVE] Ping! Ponging...\n");
// 1. Eth Header // 1. Eth Header
eth.dst0 = eth.src0; eth.dst0 = eth.src0;
@ -373,6 +442,9 @@ fn send_packet(tx_ring: *RingBufferPacket, pkt: IonPacket) void {
if (next != tail) { if (next != tail) {
tx_ring.data[head & mask] = pkt; tx_ring.data[head & mask] = pkt;
@atomicStore(u32, &tx_ring.head, next, .release); @atomicStore(u32, &tx_ring.head, next, .release);
print("[LIVE] TX Pushed! Len=");
print_u64(pkt.len);
print("\n");
} else { } else {
print("[LIVE] TX Full! Dropping.\n"); print("[LIVE] TX Full! Dropping.\n");
} }