morpheus_network/stack/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
//! smoltcp integration layer.
//!
//! This module provides the bridge between MorpheusX network device drivers
//! and the smoltcp TCP/IP stack.
//!
//! # Components
//!
//! - [`DeviceAdapter`] - Adapts `NetworkDevice` to smoltcp's `Device` trait
//! - [`NetInterface`] - Full IP stack with TCP sockets and DHCP
//!
//! # Usage
//!
//! ```ignore
//! use morpheus_network::stack::{NetInterface, NetConfig};
//! use morpheus_network::device::virtio::VirtioNetDevice;
//! use morpheus_network::device::hal::StaticHal;
//! use dma_pool::DmaPool;
//!
//! // Initialize DMA pool from caves in our PE image
//! unsafe { DmaPool::init_from_caves(image_base, image_end) };
//!
//! // Initialize HAL
//! StaticHal::init();
//!
//! // Create device
//! let device = VirtioNetDevice::<StaticHal, _>::new(transport)?;
//!
//! // Create interface with DHCP
//! let mut iface = NetInterface::new(device, NetConfig::dhcp());
//!
//! // Poll until IP configured
//! while !iface.has_ip() {
//!     iface.poll(get_time_ms());
//! }
//! ```

mod interface;

use crate::device::NetworkDevice;
use core::marker::PhantomData;
use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken};
use smoltcp::time::Instant;

pub use crate::device::pci::ecam_bases;
pub use interface::{NetConfig, NetInterface, NetState, MAX_TCP_SOCKETS};

const MTU: usize = 1536;

/// Thin adapter that exposes a `NetworkDevice` to smoltcp.
pub struct DeviceAdapter<D: NetworkDevice> {
    pub inner: D,
}

impl<D: NetworkDevice> DeviceAdapter<D> {
    pub fn new(inner: D) -> Self {
        Self { inner }
    }
}

impl<D: NetworkDevice> Device for DeviceAdapter<D> {
    type RxToken<'a>
        = AdapterRxToken<'a, D>
    where
        D: 'a;
    type TxToken<'a>
        = AdapterTxToken<'a, D>
    where
        D: 'a;

    fn capabilities(&self) -> DeviceCapabilities {
        let mut caps = DeviceCapabilities::default();
        caps.max_transmission_unit = MTU;
        caps.medium = Medium::Ethernet;
        caps
    }

    fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
        // Try to actually receive a packet first
        // Only return tokens if we have data ready
        let mut temp_buf = [0u8; MTU];
        match self.inner.receive(&mut temp_buf) {
            Ok(Some(len)) if len > 0 => {
                // Track received packets
                RX_PACKET_COUNTER.increment();

                // We have a packet! Create tokens with the data
                let device_ptr: *mut D = &mut self.inner;
                let mut token = AdapterRxToken {
                    device: device_ptr,
                    buffer: [0u8; MTU],
                    len,
                    _p: PhantomData,
                };
                token.buffer[..len].copy_from_slice(&temp_buf[..len]);
                Some((
                    token,
                    AdapterTxToken {
                        device: device_ptr,
                        _p: PhantomData,
                    },
                ))
            }
            _ => None, // No packet available
        }
    }

    fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
        if self.inner.can_transmit() {
            let device_ptr: *mut D = &mut self.inner;
            Some(AdapterTxToken {
                device: device_ptr,
                _p: PhantomData,
            })
        } else {
            None
        }
    }
}

pub struct AdapterRxToken<'a, D: NetworkDevice> {
    device: *mut D,
    buffer: [u8; MTU],
    len: usize,
    _p: PhantomData<&'a mut D>,
}

impl<'a, D: NetworkDevice> RxToken for AdapterRxToken<'a, D> {
    fn consume<R, F>(self, f: F) -> R
    where
        F: FnOnce(&mut [u8]) -> R,
    {
        // We already have the data in buffer from when receive() was called
        let mut buf = self.buffer;
        f(&mut buf[..self.len])
    }
}

pub struct AdapterTxToken<'a, D: NetworkDevice> {
    device: *mut D,
    _p: PhantomData<&'a mut D>,
}

impl<'a, D: NetworkDevice> TxToken for AdapterTxToken<'a, D> {
    fn consume<R, F>(self, len: usize, f: F) -> R
    where
        F: FnOnce(&mut [u8]) -> R,
    {
        let mut buffer = [0u8; MTU];
        let result = f(&mut buffer[..len]);

        // Track packets sent
        TX_PACKET_COUNTER.increment();

        // Check if this is a DHCP packet (UDP port 67/68)
        if len >= 42 {
            // Check if Ethernet + IP + UDP to port 67 (DHCP server)
            let ethertype = u16::from_be_bytes([buffer[12], buffer[13]]);
            if ethertype == 0x0800 {
                // IPv4
                let protocol = buffer[23]; // IP protocol field
                if protocol == 17 {
                    // UDP
                    let dst_port = u16::from_be_bytes([buffer[36], buffer[37]]);
                    if dst_port == 67 {
                        DHCP_DISCOVER_COUNTER.increment();
                    }
                }
            }
        }

        // Attempt transmit and capture error for debugging
        // We still have to return `result` regardless of TX success
        // because that's what smoltcp expects
        match unsafe { (*self.device).transmit(&buffer[..len]) } {
            Ok(()) => {
                // TX succeeded
            }
            Err(_e) => {
                // TX failed - log to static flag for debugging
                // In a real implementation, we'd have a proper error channel
                TX_ERROR_COUNTER.increment();
            }
        }

        result
    }
}

// ============================================================================
// Packet Statistics Ring Buffers
// ============================================================================

/// Ring buffer size for packet counters (must be power of 2 for efficient masking)
const PACKET_COUNTER_SIZE: usize = 256;

/// Ring buffer for packet statistics - tracks recent activity without overflow
struct PacketCounterRing {
    /// Write position (wraps automatically)
    head: core::sync::atomic::AtomicUsize,
    /// Total packets ever processed (uses wrapping arithmetic)
    total: core::sync::atomic::AtomicU32,
}

impl PacketCounterRing {
    const fn new() -> Self {
        Self {
            head: core::sync::atomic::AtomicUsize::new(0),
            total: core::sync::atomic::AtomicU32::new(0),
        }
    }

    /// Increment the counter (wraps safely)
    fn increment(&self) {
        // Advance head position (wraps at PACKET_COUNTER_SIZE)
        self.head
            .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
        // Increment total count (wraps at u32::MAX automatically)
        self.total
            .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
    }

    /// Get total count (wraps safely at u32::MAX)
    fn count(&self) -> u32 {
        self.total.load(core::sync::atomic::Ordering::Relaxed)
    }

    /// Reset the counter
    fn reset(&self) {
        self.head.store(0, core::sync::atomic::Ordering::Relaxed);
        self.total.store(0, core::sync::atomic::Ordering::Relaxed);
    }
}

// Global packet counter ring buffers
static TX_ERROR_COUNTER: PacketCounterRing = PacketCounterRing::new();
static TX_PACKET_COUNTER: PacketCounterRing = PacketCounterRing::new();
static DHCP_DISCOVER_COUNTER: PacketCounterRing = PacketCounterRing::new();
static RX_PACKET_COUNTER: PacketCounterRing = PacketCounterRing::new();

// Debug marker for tracing initialization steps
static DEBUG_INIT_STAGE: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);

// ============================================================================
// Debug Log Ring Buffer
// ============================================================================

/// Maximum length of a single debug message
const DEBUG_MSG_LEN: usize = 64;
/// Number of messages in the ring buffer
const DEBUG_RING_SIZE: usize = 16;

/// A single debug log entry
#[derive(Clone, Copy)]
pub struct DebugLogEntry {
    /// Message content (null-terminated or full)
    pub msg: [u8; DEBUG_MSG_LEN],
    /// Actual length of message
    pub len: usize,
    /// Stage number when this was logged
    pub stage: u32,
}

impl Default for DebugLogEntry {
    fn default() -> Self {
        Self {
            msg: [0u8; DEBUG_MSG_LEN],
            len: 0,
            stage: 0,
        }
    }
}

/// Ring buffer for debug logs
struct DebugRing {
    entries: [DebugLogEntry; DEBUG_RING_SIZE],
    write_idx: usize,
    read_idx: usize,
    count: usize,
}

impl DebugRing {
    const fn new() -> Self {
        Self {
            entries: [DebugLogEntry {
                msg: [0u8; DEBUG_MSG_LEN],
                len: 0,
                stage: 0,
            }; DEBUG_RING_SIZE],
            write_idx: 0,
            read_idx: 0,
            count: 0,
        }
    }
}

static mut DEBUG_RING: DebugRing = DebugRing::new();
static DEBUG_RING_LOCK: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);

/// Push a debug message to the ring buffer (AND write to serial)
pub fn debug_log(stage: u32, msg: &str) {
    // ALWAYS write to serial first - this is visible even if we hang
    #[cfg(target_arch = "x86_64")]
    crate::serial_stage(stage, msg);

    // Bounded try-lock: in single-threaded context, lock should never be held
    // If it is, we have a bug - don't spin forever, just skip the log
    if DEBUG_RING_LOCK.swap(true, core::sync::atomic::Ordering::Acquire) {
        // Lock held - impossible in single-threaded, skip to avoid hang
        return;
    }

    unsafe {
        let entry = &mut DEBUG_RING.entries[DEBUG_RING.write_idx];
        let bytes = msg.as_bytes();
        let copy_len = bytes.len().min(DEBUG_MSG_LEN);
        entry.msg[..copy_len].copy_from_slice(&bytes[..copy_len]);
        entry.len = copy_len;
        entry.stage = stage;

        DEBUG_RING.write_idx = (DEBUG_RING.write_idx + 1) % DEBUG_RING_SIZE;
        if DEBUG_RING.count < DEBUG_RING_SIZE {
            DEBUG_RING.count += 1;
        } else {
            // Overwrite oldest - advance read pointer
            DEBUG_RING.read_idx = (DEBUG_RING.read_idx + 1) % DEBUG_RING_SIZE;
        }
    }

    DEBUG_RING_LOCK.store(false, core::sync::atomic::Ordering::Release);
}

/// Pop the next debug message from the ring buffer (FIFO order)
/// Returns None if buffer is empty
pub fn debug_log_pop() -> Option<DebugLogEntry> {
    // Bounded try-lock: in single-threaded context, lock should never be held
    if DEBUG_RING_LOCK.swap(true, core::sync::atomic::Ordering::Acquire) {
        // Lock held - impossible in single-threaded, return None
        return None;
    }

    let result = unsafe {
        if DEBUG_RING.count == 0 {
            None
        } else {
            let entry = DEBUG_RING.entries[DEBUG_RING.read_idx];
            DEBUG_RING.read_idx = (DEBUG_RING.read_idx + 1) % DEBUG_RING_SIZE;
            DEBUG_RING.count -= 1;
            Some(entry)
        }
    };

    DEBUG_RING_LOCK.store(false, core::sync::atomic::Ordering::Release);
    result
}

/// Check if there are pending debug messages
pub fn debug_log_available() -> bool {
    unsafe { DEBUG_RING.count > 0 }
}

/// Clear all debug messages
pub fn debug_log_clear() {
    // Bounded try-lock: in single-threaded context, lock should never be held
    if DEBUG_RING_LOCK.swap(true, core::sync::atomic::Ordering::Acquire) {
        // Lock held - impossible in single-threaded, skip
        return;
    }

    unsafe {
        DEBUG_RING.write_idx = 0;
        DEBUG_RING.read_idx = 0;
        DEBUG_RING.count = 0;
    }

    DEBUG_RING_LOCK.store(false, core::sync::atomic::Ordering::Release);
}

/// Set debug init stage and log it
pub fn set_debug_stage(stage: u32) {
    DEBUG_INIT_STAGE.store(stage, core::sync::atomic::Ordering::Relaxed);

    // Also log to ring buffer with description
    let desc = match stage {
        10 => "entered NetInterface::new",
        11 => "got MAC address",
        12 => "created DeviceAdapter",
        13 => "created smoltcp Config",
        14 => "about to create Interface",
        15 => "Interface created",
        16 => "SocketSet created",
        17 => "about to create DNS socket",
        18 => "DNS socket added",
        19 => "creating DHCP socket",
        20 => "DHCP socket added",
        25 => "returning from new()",
        _ => "unknown",
    };
    debug_log(stage, desc);
}

/// Get current debug init stage.
pub fn debug_stage() -> u32 {
    DEBUG_INIT_STAGE.load(core::sync::atomic::Ordering::Relaxed)
}

/// Get the number of TX errors that have occurred.
pub fn tx_error_count() -> u32 {
    TX_ERROR_COUNTER.count()
}

/// Get the number of packets transmitted.
pub fn tx_packet_count() -> u32 {
    TX_PACKET_COUNTER.count()
}

/// Get the number of DHCP discover packets sent.
pub fn dhcp_discover_count() -> u32 {
    DHCP_DISCOVER_COUNTER.count()
}

/// Get the number of packets received.
pub fn rx_packet_count() -> u32 {
    RX_PACKET_COUNTER.count()
}