Skip to main content

strat9_kernel/ipc/
reply.rs

1//! IPC call/reply support : both synchronous (blocking) and async (ring-based).
2//!
3//! # Design  concept
4//!
5//! -  `ReplyTarget::Sync` is analogous to an seL4 endpoint call+reply
6//! -  `ReplyTarget::AsyncRing` combines both: the ring *is* the completion port
7//! -   Send/Receive/Reply : exactly our sync `wait_for_reply`/`deliver_reply`
8//! -   The CQE `user_data` correlates the reply to the original submission
9
10use super::message::IpcMessage;
11use crate::{
12    async_io::{complete::push_completion_for_ring, ring::find_ring},
13    memory::UserSliceWrite,
14    process::TaskId,
15    sync::{SpinLock, WaitQueue},
16};
17use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
18
19// ===========================================================================
20// ReplyTarget : where to route the reply
21// ===========================================================================
22
23/// Describes how to deliver a reply once the server responds.
24enum ReplyTarget {
25    Sync {
26        msg: Option<IpcMessage>,
27        waitq: Arc<WaitQueue>,
28    },
29
30    AsyncRing {
31        ring_id: u64,
32        user_data: u64,
33        reply_buf: u64,
34    },
35}
36
37struct ReplySlot {
38    target: ReplyTarget,
39
40    waiting_on: Option<TaskId>,
41}
42
43struct ReplyRegistry {
44    slots: BTreeMap<TaskId, ReplySlot>,
45}
46
47impl ReplyRegistry {
48    const fn new() -> Self {
49        ReplyRegistry {
50            slots: BTreeMap::new(),
51        }
52    }
53}
54
55static REPLIES: SpinLock<ReplyRegistry> = SpinLock::new(ReplyRegistry::new());
56
57// ===========================================================================
58// Helpers
59// ===========================================================================
60
61fn epipe_reply() -> IpcMessage {
62    let mut err = IpcMessage::new(0x80);
63    let epipe: u32 = 32;
64    err.payload[0..4].copy_from_slice(&epipe.to_le_bytes());
65    err
66}
67
68// ===========================================================================
69// Public API
70// ===========================================================================
71
72/// Block the current task waiting for a reply message (synchronous call path).
73///
74/// The caller (via `SYS_IPC_CALL`) blocks on a `WaitQueue` until the server
75/// calls `deliver_reply`.  Returns the reply message; returns an EPIPE error
76/// if the slot was removed while waiting (server died).
77pub fn wait_for_reply(task_id: TaskId, waiting_on: TaskId) -> IpcMessage {
78    let waitq = {
79        let mut registry = REPLIES.lock();
80        let slot = registry.slots.entry(task_id).or_insert_with(|| ReplySlot {
81            target: ReplyTarget::Sync {
82                msg: None,
83                waitq: Arc::new(WaitQueue::new()),
84            },
85            waiting_on: Some(waiting_on),
86        });
87        slot.waiting_on = Some(waiting_on); // Should never happen : a task cannot be both sync-waiting
88                                            // and have an async ring pending on the same slot.
89        match &slot.target {
90            ReplyTarget::Sync { waitq, .. } => waitq.clone(),
91            ReplyTarget::AsyncRing { .. } => {
92                return epipe_reply();
93            }
94        }
95    };
96
97    let msg = waitq.wait_until(|| {
98        let mut registry = REPLIES.lock();
99        match registry.slots.get_mut(&task_id) {
100            Some(ReplySlot {
101                target: ReplyTarget::Sync { msg, .. },
102                ..
103            }) => msg.take(),
104            _ => Some(epipe_reply()),
105        }
106    });
107
108    let mut registry = REPLIES.lock();
109    registry.slots.remove(&task_id);
110    msg
111}
112
113/// Register a pending ring-based `IpcCall` for `caller`.
114///
115/// When the server calls `deliver_reply(caller, msg)`, the kernel will:
116///   - Copy `msg` into the caller's buffer at `reply_buf`.
117///   - Push a CQE to the caller's ring with the given `user_data`.
118///
119
120pub fn register_ring_call(
121    caller: TaskId,
122    waiting_on: TaskId,
123    ring_id: u64,
124    user_data: u64,
125    reply_buf: u64,
126) {
127    let mut registry = REPLIES.lock();
128    registry.slots.insert(
129        caller,
130        ReplySlot {
131            target: ReplyTarget::AsyncRing {
132                ring_id,
133                user_data,
134                reply_buf,
135            },
136            waiting_on: Some(waiting_on),
137        },
138    );
139}
140
141pub fn cancel_replies_waiting_on(dead_task: TaskId) {
142    let mut actions = alloc::vec::Vec::new();
143
144    {
145        let mut registry = REPLIES.lock();
146        let to_cancel: Vec<TaskId> = registry
147            .slots
148            .iter()
149            .filter(|(_, slot)| slot.waiting_on == Some(dead_task))
150            .map(|(id, _)| *id)
151            .collect();
152
153        for id in to_cancel {
154            let slot = registry.slots.remove(&id);
155            if let Some(slot) = slot {
156                actions.push((id, slot.target));
157            }
158        }
159    }
160
161    for (_id, target) in actions {
162        match target {
163            ReplyTarget::Sync { waitq, .. } => {
164                // Slot removed from registry; the blocked task's
165                // wait_until closure will return epipe_reply() when it
166                // can't find its slot.
167                waitq.wake_all();
168            }
169            ReplyTarget::AsyncRing {
170                ring_id, user_data, ..
171            } => {
172                push_completion_for_ring_by_id(ring_id, user_data, -32, 0);
173            }
174        }
175    }
176}
177
178/// Deliver a reply message to the given task.
179
180pub fn deliver_reply(target: TaskId, msg: IpcMessage) -> Result<(), ()> {
181    let mut registry = REPLIES.lock();
182
183    // Fast path: look up the slot without inserting a new one.
184    let slot = registry.slots.get_mut(&target).ok_or(())?;
185
186    match &mut slot.target {
187        ReplyTarget::Sync {
188            msg: slot_msg,
189            waitq,
190        } => {
191            slot_msg.replace(msg);
192            let wq = waitq.clone();
193            drop(registry);
194            wq.wake_one();
195            Ok(())
196        }
197        ReplyTarget::AsyncRing {
198            ring_id,
199            user_data,
200            reply_buf,
201        } => {
202            let ring_id = *ring_id;
203            let user_data = *user_data;
204            let reply_buf = *reply_buf;
205
206            registry.slots.remove(&target);
207
208            let msg_size = core::mem::size_of::<IpcMessage>();
209            let mut raw = [0u8; core::mem::size_of::<IpcMessage>()];
210            crate::ipc::message::ipc_message_to_raw(&msg, &mut raw);
211            if let Ok(user) = UserSliceWrite::new(reply_buf, msg_size) {
212                let _ = user.copy_from(&raw);
213            }
214
215            drop(registry);
216
217            // Push a CQE to the caller's async ring.
218            push_completion_for_ring_by_id(ring_id, user_data, 0, 0);
219            Ok(())
220        }
221    }
222}
223
224/// Thin wrapper : resolve `ring_id` to `&Ring` and then push CQE.
225fn push_completion_for_ring_by_id(ring_id: u64, user_data: u64, result: i32, flags: u32) {
226    if let Some(ring) = find_ring(ring_id) {
227        push_completion_for_ring(&ring, user_data, result, flags);
228    }
229}