Skip to main content

strat9_kernel/ipc/
test.rs

1//! IPC ping-pong test (Port-based) + MPMC SyncChannel test (IPC-02).
2//!
3//! ## Port test (Task A / Task B)
4//! Creates two kernel tasks that communicate via an IPC port:
5//! - Task A (sender): creates a port, stores its ID in a shared static,
6//!   then sends a message with msg_type=42.
7//! - Task B (receiver): spins until the port ID is published, then calls
8//!   `recv()` (which blocks if the message hasn't arrived yet), logs the
9//!   result, and verifies correctness.
10//!
11//! ## Channel test (IPC-02)
12//! Three kernel tasks share a `channel::<u64>(4)`:
13//! - Producer-1: sends 1, 2, 3 then drops its Sender endpoint.
14//! - Producer-2: yields once (so the consumer may block), then sends 4, 5.
15//! - Consumer: blocks on `recv()` until both producers disconnect,
16//!   verifies it received exactly 5 messages.
17
18use crate::{
19    ipc::{self, channel, IpcMessage, PortId},
20    process::{add_task, Task, TaskPriority},
21    sync::SpinLock,
22};
23use core::sync::atomic::{AtomicU64, Ordering};
24
25// ─────────────────────────────────────────────────────────────────────────────
26// Port ping-pong test
27// ─────────────────────────────────────────────────────────────────────────────
28
29/// Shared port ID between the two test tasks.
30/// 0 = not yet created.
31static TEST_PORT_ID: AtomicU64 = AtomicU64::new(0);
32
33/// Create and schedule the IPC test tasks.
34pub fn create_ipc_test_tasks() {
35    let task_a = Task::new_kernel_task(ipc_sender_main, "ipc-sender", TaskPriority::Normal)
36        .expect("Failed to create IPC sender task");
37    add_task(task_a);
38
39    let task_b = Task::new_kernel_task(ipc_receiver_main, "ipc-recv", TaskPriority::Normal)
40        .expect("Failed to create IPC receiver task");
41    add_task(task_b);
42}
43
44/// Sender task: creates a port, publishes ID, sends a test message, then exits.
45extern "C" fn ipc_sender_main() -> ! {
46    crate::serial_println!("[ipc-test] Task A (sender): starting");
47
48    // Get our task ID
49    let my_id = crate::process::current_task_id().unwrap();
50    crate::serial_println!("[ipc-test] Task A: creating port...");
51
52    let port_id = ipc::create_port(my_id);
53    crate::serial_println!("[ipc-test] Task A: port {} created", port_id);
54
55    // Publish the port ID so the receiver can find it
56    TEST_PORT_ID.store(port_id.as_u64(), Ordering::Release);
57
58    // Yield a couple of times to give the receiver a chance to call recv() first
59    // (this tests the blocking path — receiver blocks, then we wake it)
60    crate::process::yield_task();
61    crate::process::yield_task();
62
63    // Build a test message
64    let mut msg = IpcMessage::new(42); // msg_type = 42
65    msg.payload[0] = b'H';
66    msg.payload[1] = b'i';
67    msg.payload[2] = b'!';
68
69    crate::serial_println!("[ipc-test] Task A: sending message (type=42)...");
70    let port = ipc::get_port(port_id).expect("port should exist");
71    port.send(msg).expect("send should succeed");
72    crate::serial_println!("[ipc-test] Task A: message sent, exiting");
73
74    crate::process::scheduler::exit_current_task(0);
75}
76
77/// Receiver task: waits for port ID, calls recv (blocks if needed), logs result.
78extern "C" fn ipc_receiver_main() -> ! {
79    crate::serial_println!("[ipc-test] Task B (receiver): starting");
80
81    // Wait for the sender to publish the port ID
82    let port_id = loop {
83        let id = TEST_PORT_ID.load(Ordering::Acquire);
84        if id != 0 {
85            break PortId(id);
86        }
87        crate::process::yield_task();
88    };
89
90    crate::serial_println!("[ipc-test] Task B: found port {}, calling recv...", port_id);
91
92    let port = ipc::get_port(port_id).expect("port should exist");
93    let msg = port.recv().expect("recv should succeed");
94
95    let sender = msg.sender;
96    let msg_type = msg.msg_type;
97    crate::serial_println!(
98        "[ipc-test] Task B: received message (type={}, sender={})",
99        msg_type,
100        sender,
101    );
102
103    // Verify correctness
104    if msg_type == 42 && msg.payload[0] == b'H' && msg.payload[1] == b'i' && msg.payload[2] == b'!'
105    {
106        crate::serial_println!("[ipc-test] IPC ping-pong test PASSED");
107    } else {
108        crate::serial_println!("[ipc-test] IPC ping-pong test FAILED (unexpected data)");
109    }
110
111    crate::process::scheduler::exit_current_task(0);
112}
113
114// The PortId(u64) constructor is pub(crate) via the struct definition,
115// so this test module in the same crate can use it directly.
116
117// ─────────────────────────────────────────────────────────────────────────────
118// IPC-02: typed MPMC SyncChannel test
119// ─────────────────────────────────────────────────────────────────────────────
120//
121// Exercises:
122//   - channel() constructor (Sender<u64> + Receiver<u64> cloning → MPMC)
123//   - Blocking recv  (consumer blocks; producer-2 yields first to force it)
124//   - Disconnect detection (last Sender drop → Receiver sees Disconnected)
125//   - wait_until round-trip through the scheduler
126
127/// Endpoint slots: each task takes its endpoint out (Option::take) once.
128/// SpinLock<Option<T>>: Sync when T: Send — no private-field access needed.
129static CHAN_TX1: SpinLock<Option<channel::Sender<u64>>> = SpinLock::new(None);
130static CHAN_TX2: SpinLock<Option<channel::Sender<u64>>> = SpinLock::new(None);
131static CHAN_RX: SpinLock<Option<channel::Receiver<u64>>> = SpinLock::new(None);
132
133/// Schedule the three channel test tasks.
134pub fn create_channel_test_tasks() {
135    let (tx, rx) = channel::channel::<u64>(4);
136    let tx2 = tx.clone(); // MPMC: second producer on the same channel
137
138    *CHAN_TX1.lock() = Some(tx);
139    *CHAN_TX2.lock() = Some(tx2);
140    *CHAN_RX.lock() = Some(rx);
141
142    let producer1 = Task::new_kernel_task(chan_producer1_main, "chan-prod-1", TaskPriority::Normal)
143        .expect("chan-prod-1 alloc failed");
144    let producer2 = Task::new_kernel_task(chan_producer2_main, "chan-prod-2", TaskPriority::Normal)
145        .expect("chan-prod-2 alloc failed");
146    let consumer = Task::new_kernel_task(chan_consumer_main, "chan-consumer", TaskPriority::Normal)
147        .expect("chan-consumer alloc failed");
148
149    add_task(producer1);
150    add_task(producer2);
151    add_task(consumer);
152}
153
154/// Producer-1: sends 1, 2, 3 then drops Sender (decrements sender_count).
155extern "C" fn chan_producer1_main() -> ! {
156    crate::serial_println!("[chan-test] Producer-1: starting");
157    let tx = CHAN_TX1.lock().take().expect("CHAN_TX1 empty");
158
159    for v in [1u64, 2, 3] {
160        crate::serial_println!("[chan-test] Producer-1: sending {}", v);
161        tx.send(v).expect("prod1 send failed");
162    }
163
164    crate::serial_println!("[chan-test] Producer-1: done");
165    drop(tx); // explicit: sender_count--
166    crate::process::scheduler::exit_current_task(0);
167}
168
169/// Producer-2: yields first so the consumer blocks, then sends 4, 5.
170extern "C" fn chan_producer2_main() -> ! {
171    crate::serial_println!("[chan-test] Producer-2: starting");
172    let tx = CHAN_TX2.lock().take().expect("CHAN_TX2 empty");
173
174    crate::process::yield_task(); // ensure consumer reaches recv() first
175
176    for v in [4u64, 5] {
177        crate::serial_println!("[chan-test] Producer-2: sending {}", v);
178        tx.send(v).expect("prod2 send failed");
179    }
180
181    crate::serial_println!("[chan-test] Producer-2: done");
182    drop(tx); // last Sender → receiver wakes with Disconnected next call
183    crate::process::scheduler::exit_current_task(0);
184}
185
186/// Consumer: drains all messages; expects exactly 5, then Disconnected.
187extern "C" fn chan_consumer_main() -> ! {
188    crate::serial_println!("[chan-test] Consumer: starting");
189    let rx = CHAN_RX.lock().take().expect("CHAN_RX empty");
190
191    let mut received: u64 = 0;
192    loop {
193        match rx.recv() {
194            Ok(v) => {
195                crate::serial_println!("[chan-test] Consumer: got {}", v);
196                received += 1;
197            }
198            Err(channel::ChannelError::Disconnected) => {
199                crate::serial_println!(
200                    "[chan-test] Consumer: disconnected after {} msgs",
201                    received
202                );
203                break;
204            }
205            Err(e) => {
206                crate::serial_println!("[chan-test] Consumer: unexpected error {:?}", e);
207                break;
208            }
209        }
210    }
211
212    if received == 5 {
213        crate::serial_println!(
214            "[chan-test] SyncChannel MPMC test PASSED ({} msgs)",
215            received
216        );
217    } else {
218        crate::serial_println!(
219            "[chan-test] SyncChannel MPMC test FAILED (expected 5, got {})",
220            received
221        );
222    }
223
224    crate::process::scheduler::exit_current_task(0);
225}
226
227// ─────────────────────────────────────────────────────────────────────────────
228// IPC-04/05 tests: shared ring + POSIX semaphore
229// ─────────────────────────────────────────────────────────────────────────────
230
231static SEM_TEST_ID: AtomicU64 = AtomicU64::new(0);
232static SEM_TEST_RESULT: SpinLock<Option<bool>> = SpinLock::new(None);
233
234/// Creates ipc 04 05 test task.
235pub fn create_ipc_04_05_test_task() {
236    let task = Task::new_kernel_task(ipc_04_05_main, "ipc-04-05-test", TaskPriority::Normal)
237        .expect("Failed to create ipc-04-05-test task");
238    add_task(task);
239}
240
241/// Performs the ipc 04 05 main operation.
242extern "C" fn ipc_04_05_main() -> ! {
243    crate::serial_println!("[ipc-04-05] start");
244
245    let ring_id = match ipc::shared_ring::create_ring(8192) {
246        Ok(id) => id,
247        Err(e) => {
248            crate::serial_println!("[ipc-04-05] FAIL ring create: {:?}", e);
249            crate::process::scheduler::exit_current_task(1);
250        }
251    };
252
253    let ring = match ipc::shared_ring::get_ring(ring_id) {
254        Some(r) => r,
255        None => {
256            crate::serial_println!("[ipc-04-05] FAIL ring lookup");
257            crate::process::scheduler::exit_current_task(1);
258        }
259    };
260    let pages = ring.page_count();
261    let addrs = ring.frame_phys_addrs();
262    if pages < 2 || addrs.len() != pages {
263        crate::serial_println!(
264            "[ipc-04-05] FAIL ring layout pages={} addrs={}",
265            pages,
266            addrs.len()
267        );
268        let _ = ipc::shared_ring::destroy_ring(ring_id);
269        crate::process::scheduler::exit_current_task(1);
270    }
271
272    if ipc::shared_ring::destroy_ring(ring_id).is_err()
273        || ipc::shared_ring::get_ring(ring_id).is_some()
274    {
275        crate::serial_println!("[ipc-04-05] FAIL ring destroy");
276        crate::process::scheduler::exit_current_task(1);
277    }
278
279    let sem_id = match ipc::semaphore::create_semaphore(0) {
280        Ok(id) => id,
281        Err(e) => {
282            crate::serial_println!("[ipc-04-05] FAIL sem create: {:?}", e);
283            crate::process::scheduler::exit_current_task(1);
284        }
285    };
286    SEM_TEST_ID.store(sem_id.as_u64(), Ordering::Release);
287
288    let poster = Task::new_kernel_task(sem_poster_main, "ipc-sem-poster", TaskPriority::Normal)
289        .expect("Failed to create ipc-sem-poster task");
290    add_task(poster);
291
292    let sem = ipc::semaphore::get_semaphore(sem_id).expect("sem should exist");
293
294    if !matches!(
295        sem.try_wait(),
296        Err(ipc::semaphore::SemaphoreError::WouldBlock)
297    ) {
298        crate::serial_println!("[ipc-04-05] FAIL sem try_wait should block");
299        let _ = ipc::semaphore::destroy_semaphore(sem_id);
300        crate::process::scheduler::exit_current_task(1);
301    }
302
303    let wait_ok = sem.wait().is_ok();
304    *SEM_TEST_RESULT.lock() = Some(wait_ok);
305
306    if !wait_ok {
307        crate::serial_println!("[ipc-04-05] FAIL sem wait");
308        let _ = ipc::semaphore::destroy_semaphore(sem_id);
309        crate::process::scheduler::exit_current_task(1);
310    }
311
312    if ipc::semaphore::destroy_semaphore(sem_id).is_err() {
313        crate::serial_println!("[ipc-04-05] FAIL sem destroy");
314        crate::process::scheduler::exit_current_task(1);
315    }
316
317    crate::serial_println!("[ipc-04-05] PASSED");
318    crate::process::scheduler::exit_current_task(0);
319}
320
321/// Performs the sem poster main operation.
322extern "C" fn sem_poster_main() -> ! {
323    for _ in 0..4 {
324        crate::process::yield_task();
325    }
326    let sem_id = ipc::semaphore::SemId::from_u64(SEM_TEST_ID.load(Ordering::Acquire));
327    if let Some(sem) = ipc::semaphore::get_semaphore(sem_id) {
328        let _ = sem.post();
329    }
330    crate::process::scheduler::exit_current_task(0);
331}