Skip to main content

strat9_kernel/ipc/
transport.rs

1//! IPC transport layer: traits, dispatch enum, and manager.
2//!
3//! This module defines the three-level transport hierarchy:
4//! - **N1 (TypeSafe)**: kernel-internal mailbox, same address space, ~3-10 cycles.
5//! - **N2 (LockFree)**: shared-memory SPSC ring with futex notification, ~400-4000 cycles.
6//! - **N3 (Mmu)**: thread migration with PCID-preserving CR3 switch (research track).
7//!
8//! The [`TransportManager`] selects the appropriate level per silo-pair at
9//! connection creation time using a configurable decision matrix.
10
11use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
12
13use alloc::{collections::BTreeMap, sync::Arc};
14
15use crate::{silo::SiloId, sync::SpinLock};
16
17use super::{
18    lockfree_ring::{LockFreeRing, RingError, RingSlot},
19    mailbox::IntrusiveMailbox,
20};
21use alloc::vec::Vec;
22// ---------------------------------------------------------------------------
23// Transport level enumeration
24// ---------------------------------------------------------------------------
25
26/// Isolation level of an IPC transport.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
28#[repr(u8)]
29pub enum TransportLevel {
30    /// Rust type-based isolation : kernel-internal, same address space.
31    TypeSafe = 1,
32    /// Shared-memory lock-free SPSC ring with futex notification.
33    LockFree = 2,
34    /// MMU-based thread migration with PCID preservation (research).
35    Mmu = 3,
36}
37
38// ---------------------------------------------------------------------------
39// IpcTransport trait hierarchy
40// ---------------------------------------------------------------------------
41
42/// Describes the capabilities of an IPC transport.
43#[derive(Debug, Clone, Copy)]
44pub struct TransportCapabilities {
45    /// Maximum message size in bytes.
46    pub max_message_size: usize,
47    /// Whether the transport supports blocking send/recv.
48    pub blocking: bool,
49    /// Whether the transport supports zero-copy (DMA into buffers).
50    pub zero_copy: bool,
51    /// Approximate round-trip cost in CPU cycles (for scheduler hints).
52    pub estimated_cost_cycles: u32,
53}
54
55/// Core trait for all IPC transports.
56pub trait IpcTransport: Send + Sync {
57    /// The isolation level of this transport.
58    fn level(&self) -> TransportLevel;
59    /// Static capabilities of this transport.
60    fn capabilities(&self) -> TransportCapabilities;
61    /// Human-readable name for debugging / profiling.
62    fn name(&self) -> &'static str;
63}
64
65/// Producer side : sends messages into the transport.
66pub trait IpcProducer: IpcTransport {
67    /// Send a message, blocking if the buffer is full.
68    fn send(&self, msg: &[u8]) -> Result<(), IpcError>;
69    /// Send without blocking.
70    fn try_send(&self, msg: &[u8]) -> Result<(), IpcError>;
71}
72
73/// Consumer side : receives messages from the transport.
74pub trait IpcConsumer: IpcTransport {
75    /// Receive a message, blocking if the buffer is empty.
76    fn recv(&self, buf: &mut [u8]) -> Result<usize, IpcError>;
77    /// Receive without blocking.
78    fn try_recv(&self, buf: &mut [u8]) -> Result<Option<usize>, IpcError>;
79}
80
81/// Notification side : futex-based wakeup for blocking transports.
82pub trait IpcNotification: IpcTransport {
83    /// Notify the consumer that data is available.
84    fn notify_consumer(&self);
85    /// Notify the producer that space is available.
86    fn notify_producer(&self);
87    /// Block until a notification arrives.
88    fn wait_notification(&self) -> Result<(), IpcError>;
89}
90
91// ---------------------------------------------------------------------------
92// IpcError
93// ---------------------------------------------------------------------------
94
95/// Errors from IPC transport operations.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum IpcError {
98    /// Transport buffer is full.
99    WouldBlock,
100    /// Transport has been closed or disconnected.
101    Disconnected,
102    /// Message exceeds transport capacity.
103    MessageTooLarge,
104    /// Provided buffer is too small for pending message.
105    BufferTooSmall,
106    /// No transport exists for the given pair.
107    TransportNotFound,
108    /// Insufficient permissions.
109    PermissionDenied,
110    /// Generic transport failure.
111    TransportFailed,
112}
113
114// ---------------------------------------------------------------------------
115// TransportEndpoint : sum-type dispatch (no vtable)
116// ---------------------------------------------------------------------------
117
118/// A concrete IPC transport endpoint, dispatched via enum (no `dyn` vtable).
119#[derive(Debug, Clone)]
120pub enum TransportEndpoint {
121    /// N1 kernel-internal mailbox.
122    Mailbox(Arc<IntrusiveMailbox>),
123    /// N2 lock-free SPSC ring.
124    LockFree(Arc<LockFreeRing>),
125}
126
127impl IpcTransport for TransportEndpoint {
128    fn level(&self) -> TransportLevel {
129        match self {
130            Self::Mailbox(_) => TransportLevel::TypeSafe,
131            Self::LockFree(_) => TransportLevel::LockFree,
132        }
133    }
134
135    fn capabilities(&self) -> TransportCapabilities {
136        match self {
137            Self::Mailbox(m) => m.capabilities(),
138            Self::LockFree(r) => r.capabilities(),
139        }
140    }
141
142    fn name(&self) -> &'static str {
143        match self {
144            Self::Mailbox(_) => "mailbox",
145            Self::LockFree(_) => "lockfree",
146        }
147    }
148}
149
150impl IpcProducer for TransportEndpoint {
151    fn send(&self, msg: &[u8]) -> Result<(), IpcError> {
152        match self {
153            Self::Mailbox(m) => m.send(msg),
154            Self::LockFree(r) => r.send(msg),
155        }
156    }
157
158    fn try_send(&self, msg: &[u8]) -> Result<(), IpcError> {
159        match self {
160            Self::Mailbox(m) => m.try_send(msg),
161            Self::LockFree(r) => r.try_send(msg),
162        }
163    }
164}
165
166impl IpcConsumer for TransportEndpoint {
167    fn recv(&self, buf: &mut [u8]) -> Result<usize, IpcError> {
168        match self {
169            Self::Mailbox(m) => m.recv(buf),
170            Self::LockFree(r) => r.recv(buf),
171        }
172    }
173
174    fn try_recv(&self, buf: &mut [u8]) -> Result<Option<usize>, IpcError> {
175        match self {
176            Self::Mailbox(m) => m.try_recv(buf),
177            Self::LockFree(r) => r.try_recv(buf),
178        }
179    }
180}
181
182// LockFreeRing implements IpcTransport, IpcProducer, IpcConsumer
183impl IpcTransport for LockFreeRing {
184    fn level(&self) -> TransportLevel {
185        TransportLevel::LockFree
186    }
187
188    fn capabilities(&self) -> TransportCapabilities {
189        TransportCapabilities {
190            max_message_size: RingSlot::SLOT_SIZE,
191            blocking: true,
192            zero_copy: true,
193            estimated_cost_cycles: 400,
194        }
195    }
196
197    fn name(&self) -> &'static str {
198        "lockfree"
199    }
200}
201
202impl IpcProducer for LockFreeRing {
203    fn send(&self, msg: &[u8]) -> Result<(), IpcError> {
204        self.write(msg).map_err(ring_to_ipc_error)?;
205        self.notify_consumer_raw();
206        Ok(())
207    }
208
209    fn try_send(&self, msg: &[u8]) -> Result<(), IpcError> {
210        self.write(msg).map_err(ring_to_ipc_error)?;
211        self.notify_consumer_raw();
212        Ok(())
213    }
214}
215
216impl IpcConsumer for LockFreeRing {
217    fn recv(&self, buf: &mut [u8]) -> Result<usize, IpcError> {
218        self.read(buf).map_err(ring_to_ipc_error)
219    }
220
221    fn try_recv(&self, buf: &mut [u8]) -> Result<Option<usize>, IpcError> {
222        self.try_read(buf).map_err(ring_to_ipc_error)
223    }
224}
225
226fn ring_to_ipc_error(e: RingError) -> IpcError {
227    match e {
228        RingError::Full => IpcError::WouldBlock,
229        RingError::Empty => IpcError::WouldBlock,
230        RingError::MessageTooLarge => IpcError::MessageTooLarge,
231        RingError::BufferTooSmall => IpcError::BufferTooSmall,
232        _ => IpcError::TransportFailed,
233    }
234}
235
236// ---------------------------------------------------------------------------
237// TransportId / TransportCreateResult
238// ---------------------------------------------------------------------------
239
240/// Unique identifier for a transport connection.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
242pub struct TransportId(u64);
243
244impl TransportId {
245    fn new() -> Self {
246        static NEXT: AtomicU64 = AtomicU64::new(1);
247        TransportId(NEXT.fetch_add(1, Ordering::Relaxed))
248    }
249
250    /// Create a TransportId from a raw u64.
251    pub fn from_u64(raw: u64) -> Self {
252        TransportId(raw)
253    }
254
255    /// Get the raw u64 value.
256    pub fn as_u64(self) -> u64 {
257        self.0
258    }
259}
260
261/// Result of a successful transport creation.
262#[derive(Debug, Clone)]
263pub struct TransportCreateResult {
264    pub id: TransportId,
265    pub local: TransportEndpoint,
266    pub remote: TransportEndpoint,
267    pub level: TransportLevel,
268}
269
270// ---------------------------------------------------------------------------
271// TransportConfig
272// ---------------------------------------------------------------------------
273
274/// User-supplied configuration for transport creation.
275#[derive(Debug, Clone)]
276pub struct TransportConfig {
277    /// Minimum acceptable isolation level.
278    pub min_level: TransportLevel,
279    /// Requested ring capacity in slots (N2 only).
280    pub ring_capacity: Option<u32>,
281    /// Requested slot size in bytes (N2 only).
282    pub slot_size: Option<usize>,
283}
284
285// ---------------------------------------------------------------------------
286// Decision matrix
287// ---------------------------------------------------------------------------
288
289/// Policy entry for a single cell in the decision matrix.
290#[derive(Debug, Clone)]
291struct TransportPolicyEntry {
292    level: TransportLevel,
293    ring_capacity: u32,
294}
295
296/// Default decision matrix for Tier × Tier transport selection.
297const DECISION_MATRIX: [[TransportPolicyEntry; 3]; 3] = [
298    // src = Critical
299    [
300        TransportPolicyEntry {
301            level: TransportLevel::TypeSafe,
302            ring_capacity: 0,
303        },
304        TransportPolicyEntry {
305            level: TransportLevel::TypeSafe,
306            ring_capacity: 0,
307        },
308        TransportPolicyEntry {
309            level: TransportLevel::LockFree,
310            ring_capacity: 256,
311        },
312    ],
313    // src = System
314    [
315        TransportPolicyEntry {
316            level: TransportLevel::TypeSafe,
317            ring_capacity: 0,
318        },
319        TransportPolicyEntry {
320            level: TransportLevel::LockFree,
321            ring_capacity: 256,
322        },
323        TransportPolicyEntry {
324            level: TransportLevel::LockFree,
325            ring_capacity: 256,
326        },
327    ],
328    // src = User
329    [
330        TransportPolicyEntry {
331            level: TransportLevel::LockFree,
332            ring_capacity: 256,
333        },
334        TransportPolicyEntry {
335            level: TransportLevel::LockFree,
336            ring_capacity: 256,
337        },
338        TransportPolicyEntry {
339            level: TransportLevel::LockFree,
340            ring_capacity: 256,
341        },
342    ],
343];
344
345/// Per-transport performance counters.
346#[derive(Debug, Clone)]
347pub struct TransportStats {
348    /// Total messages sent.
349    pub sent: u64,
350    /// Total messages received.
351    pub received: u64,
352    /// Total errors.
353    pub errors: u64,
354    /// Current transport level.
355    pub level: TransportLevel,
356}
357
358impl TransportStats {
359    const fn new(level: TransportLevel) -> Self {
360        TransportStats {
361            sent: 0,
362            received: 0,
363            errors: 0,
364            level,
365        }
366    }
367}
368
369// ---------------------------------------------------------------------------
370// TransportEndpoint helpers for polling
371// ---------------------------------------------------------------------------
372
373impl TransportEndpoint {
374    /// Whether the transport has data to read.
375    pub fn has_data(&self) -> bool {
376        match self {
377            Self::Mailbox(m) => !m.is_empty(),
378            Self::LockFree(r) => r.has_data(),
379        }
380    }
381
382    /// Whether the transport has space to write.
383    pub fn has_space(&self) -> bool {
384        match self {
385            Self::Mailbox(_) => true, // Mailbox allocates on push, always nominally writable
386            Self::LockFree(r) => r.has_space(),
387        }
388    }
389}
390
391// ---------------------------------------------------------------------------
392// TransportManager
393// ---------------------------------------------------------------------------
394
395/// Central transport manager : selects and creates IPC transports per silo pair.
396///
397/// Uses a static decision matrix (tier × tier) with optional dynamic
398/// overrides.  A small FIFO cache avoids redundant creation for frequently
399/// used pairs.
400pub struct TransportManager {
401    /// Static decision matrix: [src_tier][dst_tier] -> policy.
402    decision_matrix: [[TransportPolicyEntry; 3]; 3],
403    /// Dynamic overrides (set by silo admin).
404    policy_overrides: SpinLock<BTreeMap<(u32, u32), TransportPolicyEntry>>,
405    /// Active transport registry.
406    active: SpinLock<BTreeMap<TransportId, TransportCreateResult>>,
407    /// Simple FIFO transport cache (no_std, no allocation).
408    cache: SpinLock<TransportCache>,
409    /// Per-transport performance statistics.
410    pub stats: SpinLock<TransportStats>,
411}
412
413impl TransportManager {
414    /// Create a new manager with the default decision matrix.
415    pub const fn new() -> Self {
416        TransportManager {
417            decision_matrix: DECISION_MATRIX,
418            policy_overrides: SpinLock::new(BTreeMap::new()),
419            active: SpinLock::new(BTreeMap::new()),
420            cache: SpinLock::new(TransportCache::new()),
421            stats: SpinLock::new(TransportStats::new(TransportLevel::LockFree)),
422        }
423    }
424
425    /// Establish a transport between `src` and `dst`.
426    ///
427    /// Selection order:
428    /// 1. Cache hit (fast path for repeated pairs).
429    /// 2. Dynamic override (admin-configured policy).
430    /// 3. Static decision matrix (safe default).
431    pub fn establish(
432        &self,
433        src: SiloId,
434        dst: SiloId,
435        config: TransportConfig,
436    ) -> Result<TransportCreateResult, IpcError> {
437        let pair = (src.sid, dst.sid);
438
439        // 1. Cache lookup
440        {
441            let mut cache = self.cache.lock();
442            if let Some(cached) = cache.get(pair) {
443                if cached.level as u8 >= config.min_level as u8 {
444                    return Ok(cached.clone());
445                }
446            }
447        }
448
449        // 2. Dynamic overrides
450        {
451            let overrides = self.policy_overrides.lock();
452            if let Some(entry) = overrides.get(&pair) {
453                return self.create(pair, entry.level, entry.ring_capacity);
454            }
455        }
456
457        // 3. Static matrix
458        let entry = &self.decision_matrix[src.tier as usize][dst.tier as usize];
459        let level = if entry.level < config.min_level {
460            config.min_level
461        } else {
462            entry.level
463        };
464        self.create(
465            pair,
466            level,
467            config.ring_capacity.unwrap_or(entry.ring_capacity),
468        )
469    }
470
471    /// Create a transport at the given level.
472    fn create(
473        &self,
474        _pair: (u32, u32),
475        level: TransportLevel,
476        capacity: u32,
477    ) -> Result<TransportCreateResult, IpcError> {
478        let id = TransportId::new();
479
480        let (local, remote) = match level {
481            TransportLevel::TypeSafe => {
482                let mb = IntrusiveMailbox::new();
483                let arc = Arc::new(mb);
484                (
485                    TransportEndpoint::Mailbox(arc.clone()),
486                    TransportEndpoint::Mailbox(arc),
487                )
488            }
489            TransportLevel::LockFree => {
490                let ring = LockFreeRing::new(capacity.max(4), 2048)
491                    .map_err(|_| IpcError::TransportFailed)?;
492                (
493                    TransportEndpoint::LockFree(ring.clone()),
494                    TransportEndpoint::LockFree(ring),
495                )
496            }
497            TransportLevel::Mmu => {
498                // N3 is research track : fall back to N2 LockFree
499                let ring = LockFreeRing::new(capacity.max(4), 2048)
500                    .map_err(|_| IpcError::TransportFailed)?;
501                (
502                    TransportEndpoint::LockFree(ring.clone()),
503                    TransportEndpoint::LockFree(ring),
504                )
505            }
506        };
507
508        let result = TransportCreateResult {
509            id,
510            local,
511            remote: remote.clone(),
512            level,
513        };
514
515        // Increment stats counters
516        // Increment stats counters
517        self.stats.lock().sent += 1;
518
519        {
520            let mut cache = self.cache.lock();
521            cache.put(_pair, result.clone());
522        }
523        {
524            let mut active = self.active.lock();
525            active.insert(id, result.clone());
526        }
527
528        Ok(result)
529    }
530
531    /// Look up a transport endpoint by ID.
532    pub fn get_endpoint(&self, id: TransportId) -> Option<TransportEndpoint> {
533        let active = self.active.lock();
534        active.get(&id).map(|r| r.local.clone())
535    }
536
537    /// Remove a transport from the registry (called when all handles close).
538    pub fn close(&self, id: TransportId) -> Result<(), IpcError> {
539        let mut active = self.active.lock();
540        active.remove(&id).ok_or(IpcError::TransportNotFound)?;
541        Ok(())
542    }
543
544    /// Override the transport policy for a specific silo pair.
545    pub fn set_policy(&self, src: u32, dst: u32, level: TransportLevel, capacity: u32) {
546        let mut overrides = self.policy_overrides.lock();
547        overrides.insert(
548            (src, dst),
549            TransportPolicyEntry {
550                level,
551                ring_capacity: capacity,
552            },
553        );
554    }
555}
556
557// ---------------------------------------------------------------------------
558// TransportCache : no_std FIFO cache
559// ---------------------------------------------------------------------------
560
561const CACHE_SIZE: usize = 128;
562
563struct TransportCache {
564    entries: [((u32, u32), Option<TransportCreateResult>); CACHE_SIZE],
565    next: usize,
566}
567
568impl TransportCache {
569    const fn new() -> Self {
570        const NONE: ((u32, u32), Option<TransportCreateResult>) = ((0, 0), None);
571        TransportCache {
572            entries: [NONE; CACHE_SIZE],
573            next: 0,
574        }
575    }
576
577    fn get(&mut self, key: (u32, u32)) -> Option<&TransportCreateResult> {
578        self.entries
579            .iter()
580            .find_map(|(k, v)| if *k == key { v.as_ref() } else { None })
581    }
582
583    fn put(&mut self, key: (u32, u32), value: TransportCreateResult) {
584        let idx = self.next;
585        self.entries[idx] = (key, Some(value));
586        self.next = (self.next + 1) % CACHE_SIZE;
587    }
588}
589
590// ---------------------------------------------------------------------------
591// TypedLockFreeRing : typestate pattern for SPSC role safety
592// ---------------------------------------------------------------------------
593
594use core::marker::PhantomData;
595
596/// Producer role marker: only this side can call `write()`.
597pub struct Producer;
598
599/// Consumer role marker: can only call `read()`.
600pub struct Consumer;
601
602/// SPSC ring with compile-time role enforcement.
603///
604/// `TypedLockFreeRing<Producer>` has both `write()` and `read()`.
605/// `TypedLockFreeRing<Consumer>` has only `read()`.
606/// The compiler rejects any misuse at compile time.
607pub struct TypedLockFreeRing<Role> {
608    inner: Arc<LockFreeRing>,
609    _role: PhantomData<Role>,
610}
611
612impl TypedLockFreeRing<Producer> {
613    /// Write into the ring (producer only).
614    pub fn write(&self, data: &[u8]) -> Result<(), RingError> {
615        self.inner.write(data)
616    }
617}
618
619impl<T> TypedLockFreeRing<T> {
620    /// Read from the ring (both roles).
621    pub fn read(&self, buf: &mut [u8]) -> Result<usize, RingError> {
622        self.inner.read(buf)
623    }
624}
625
626/// Create a typed SPSC pair returning separate producer and consumer ends.
627pub fn create_spsc_pair(
628    cap: u32,
629    slot_size: usize,
630) -> Result<(TypedLockFreeRing<Producer>, TypedLockFreeRing<Consumer>), RingError> {
631    let ring = LockFreeRing::new(cap, slot_size)?;
632    Ok((
633        TypedLockFreeRing {
634            inner: ring.clone(),
635            _role: PhantomData,
636        },
637        TypedLockFreeRing {
638            inner: ring,
639            _role: PhantomData,
640        },
641    ))
642}
643
644// NicDataPlane : multi-queue NIC data-path
645// ---------------------------------------------------------------------------
646
647/// Shared NIC ←→ scheduler routing table.
648///
649/// Updated by the scheduler on each context switch; read **only** by the NIC
650/// driver via a read-only (PTE_RO) mapping in Ring 3.  The NIC must never
651/// write this table : doing so would corrupt scheduler state.
652#[repr(C)]
653pub struct NicRoutingTable {
654    /// Number of valid entries.
655    pub count: AtomicU32,
656    _pad: [u8; 60],
657    /// Routing entries, one per silo.
658    pub entries: [RoutingEntry; MAX_ROUTES],
659}
660
661/// A single entry in the NIC routing table.
662#[repr(C, align(64))]
663pub struct RoutingEntry {
664    pub silo_id: u64,
665    pub core_id: u32,
666    pub state: AtomicU32,
667    pub rpc_count: AtomicU64,
668    pub generation: AtomicU64,
669}
670
671/// Maximum number of routing entries.
672pub const MAX_ROUTES: usize = 64;
673
674impl Default for NicRoutingTable {
675    fn default() -> Self {
676        NicRoutingTable {
677            count: AtomicU32::new(0),
678            _pad: [0u8; 60],
679            entries: unsafe { core::mem::zeroed() },
680        }
681    }
682}
683
684/// Per-queue ring pair for multi-queue NIC RSS.
685pub struct RingPair {
686    pub rx: Arc<LockFreeRing>,
687    pub tx: Arc<LockFreeRing>,
688}
689
690/// Multi-queue NIC data plane using independent SPSC rings.
691///
692/// Each RSS queue gets its own `RingPair`; the NIC driver writes into the
693/// RX ring of the current queue, and the networking silo (strate-net) polls
694/// all RX rings in round-robin.
695pub struct NicDataPlane {
696    /// One ring pair per RSS queue.
697    pub queues: Vec<RingPair>,
698    /// Shared routing table (read-only for Ring 3).
699    pub routing: NicRoutingTable,
700}