commit 28328e7600e718a0b0b419e39f5bcdfe01cd17f4 from: Murilo Ijanc date: Sun Mar 15 00:36:28 2026 UTC add synchronous ipc with endpoints, send, recv and call commit - e1940d574f26f4e261757461655d7290fe1ff4e9 commit + 28328e7600e718a0b0b419e39f5bcdfe01cd17f4 blob - /dev/null blob + f3c082a5af513b2829ef2c0fa93ed0d26da17839 (mode 644) --- /dev/null +++ src/ipc.rs @@ -0,0 +1,198 @@ +// vim: set tw=79 cc=80 ts=4 sw=4 sts=4 et : +// +// Copyright (c) 2025-2026 Murilo Ijanc' +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +//! Synchronous IPC: endpoints, send, recv, call, reply. + +use core::cell::UnsafeCell; + +use crate::sched; +use crate::serial; + +pub const MSG_MAX: usize = 256; +const MAX_ENDPOINTS: usize = 32; + +/// A pending message: pointer to sender's buffer + length. +/// Only valid while the sender is blocked. +#[derive(Clone, Copy)] +struct PendingMsg { + ptr: *const u8, + len: usize, +} + +/// An IPC endpoint. +#[derive(Clone, Copy)] +struct Endpoint { + active: bool, + /// Task index blocked in send (waiting for receiver) + sender: Option, + sender_msg: Option, + /// Task index blocked in recv (waiting for sender) + receiver: Option, + receiver_buf: Option, +} + +impl Endpoint { + const fn empty() -> Self { + Endpoint { + active: false, + sender: None, + sender_msg: None, + receiver: None, + receiver_buf: None, + } + } +} + +struct IpcState { + endpoints: UnsafeCell<[Endpoint; MAX_ENDPOINTS]>, + next_id: UnsafeCell, +} + +unsafe impl Sync for IpcState {} + +static IPC: IpcState = IpcState { + endpoints: UnsafeCell::new( + [Endpoint::empty(); MAX_ENDPOINTS], + ), + next_id: UnsafeCell::new(0), +}; + +/// Create a new endpoint. Returns the endpoint ID. +pub fn endpoint_create() -> Option { + unsafe { + let eps = &mut *IPC.endpoints.get(); + let next = &mut *IPC.next_id.get(); + if *next >= MAX_ENDPOINTS { + return None; + } + let id = *next; + eps[id].active = true; + *next += 1; + Some(id) + } +} + +/// Send a message to an endpoint. Blocks until a receiver +/// is ready. +pub fn send(ep_id: usize, msg: &[u8]) { + unsafe { + let eps = &mut *IPC.endpoints.get(); + + if ep_id >= MAX_ENDPOINTS || !eps[ep_id].active { + serial::print("ipc: invalid endpoint\n"); + return; + } + + let ep = &mut eps[ep_id]; + + // Is there a receiver already waiting? + if let Some(recv_task) = ep.receiver.take() { + // Direct transfer: copy msg to receiver's buffer + if let Some(rbuf) = ep.receiver_buf.take() { + let copy_len = msg.len().min(rbuf.len); + core::ptr::copy_nonoverlapping( + msg.as_ptr(), + rbuf.ptr as *mut u8, + copy_len, + ); + } + // Unblock receiver + sched::unblock(recv_task); + } else { + // No receiver yet — block sender + let me = sched::current_task_index(); + ep.sender = Some(me); + ep.sender_msg = Some(PendingMsg { + ptr: msg.as_ptr(), + len: msg.len(), + }); + sched::block(); + // When we wake up, the receiver has already + // copied our message. + } + } +} + +/// Receive a message from an endpoint. Blocks until a +/// sender is ready. Returns the number of bytes received. +pub fn recv(ep_id: usize, buf: &mut [u8]) -> usize { + unsafe { + let eps = &mut *IPC.endpoints.get(); + + if ep_id >= MAX_ENDPOINTS || !eps[ep_id].active { + serial::print("ipc: invalid endpoint\n"); + return 0; + } + + let ep = &mut eps[ep_id]; + + // Is there a sender already waiting? + if let Some(send_task) = ep.sender.take() { + if let Some(smsg) = ep.sender_msg.take() { + let copy_len = smsg.len.min(buf.len()); + core::ptr::copy_nonoverlapping( + smsg.ptr, + buf.as_mut_ptr(), + copy_len, + ); + // Unblock sender + sched::unblock(send_task); + return copy_len; + } + } + + // No sender yet — block receiver + let me = sched::current_task_index(); + ep.receiver = Some(me); + ep.receiver_buf = Some(PendingMsg { + ptr: buf.as_mut_ptr(), + len: buf.len(), + }); + sched::block(); + // When we wake up, the sender has copied into buf. + // We don't know the exact length here, so return + // buf.len() as upper bound. A real implementation + // would store the actual length. + buf.len() + } +} + +/// Call: send a message and wait for a reply on the same +/// endpoint (RPC pattern). Returns bytes received in reply. +pub fn call( + ep_id: usize, + msg: &[u8], + reply_buf: &mut [u8], +) -> usize { + send(ep_id, msg); + recv(ep_id, reply_buf) +} + +/// Read the CPU timestamp counter (cycles). +pub fn rdtsc() -> u64 { + let lo: u32; + let hi: u32; + unsafe { + core::arch::asm!( + "rdtsc", + out("eax") lo, + out("edx") hi, + options(nostack, nomem), + ); + } + ((hi as u64) << 32) | lo as u64 +}