xous-core/kernel/src/syscall.rs
2024-06-13 15:56:37 +08:00

1128 lines
50 KiB
Rust

// SPDX-FileCopyrightText: 2020 Sean Cross <sean@xobs.io>
// SPDX-License-Identifier: Apache-2.0
use core::sync::atomic::{AtomicU8, AtomicUsize, Ordering::Relaxed};
use xous_kernel::*;
use crate::arch;
use crate::arch::process::Process as ArchProcess;
use crate::irq::{interrupt_claim, interrupt_free};
use crate::mem::{MemoryManager, PAGE_SIZE};
use crate::server::{SenderID, WaitingMessage};
use crate::services::SystemServices;
#[cfg(feature = "swap")]
use crate::swap::{Swap, SwapAbi};
/* Quoth Xobs:
The idea behind SWITCHTO_CALLER was that you'd have a process act as a scheduler,
where it would know all of its children processes. It would call SwitchTo(pid, tid)
on its children, which would call Yield or WaitEvent as necessary that would then
cause execution to return to the parent process.
If the timer hit, it would call ReturnToParent() which would also return to the caller.
Currently (as of Mar 2021) this functionality isn't being used, it's just returning
back to the kernel, e.g. (PID,TID) = (1,1)
*/
/// This is the PID/TID of the last person that called SwitchTo
static mut SWITCHTO_CALLER: Option<(PID, TID)> = None;
/// When a process is switched to, take note of the original PID and TID.
/// That way we know whether to give the process its full quantum when
/// messages are Returned. If a process Returns messages while it's in its
/// own quantum, then don't immediately transfer control to the Client.
/// However, for processes that are running on Borrowed Quantum (i.e. when
/// another process sent them a message and they're immediately responding,)
/// return control to the Client.
static ORIGINAL_PID: AtomicU8 = AtomicU8::new(2);
static ORIGINAL_TID: AtomicUsize = AtomicUsize::new(2);
#[derive(PartialEq)]
enum ExecutionType {
Blocking,
NonBlocking,
}
#[cfg(baremetal)]
pub fn reset_switchto_caller() { unsafe { SWITCHTO_CALLER = None }; }
fn retry_syscall(pid: PID, tid: TID) -> SysCallResult {
if cfg!(baremetal) {
arch::process::Process::with_current_mut(|p| p.retry_instruction(tid))?;
do_yield(pid, tid)
} else {
Ok(xous_kernel::Result::RetryCall)
}
}
fn do_yield(_pid: PID, tid: TID) -> SysCallResult {
// If we're not running on bare metal, treat this as a no-op.
if !cfg!(baremetal) {
return Ok(xous_kernel::Result::Ok);
}
let (parent_pid, parent_ctx) =
unsafe { SWITCHTO_CALLER.take().expect("yielded when no parent context was present") };
//println!("\n\r ***YIELD CALLED***");
SystemServices::with_mut(|ss| {
// TODO: Advance thread
let result = ss
.activate_process_thread(tid, parent_pid, parent_ctx, true)
.map(|_| Ok(xous_kernel::Result::ResumeProcess))
.unwrap_or(Err(xous_kernel::Error::ProcessNotFound));
ss.set_last_thread(PID::new(ORIGINAL_PID.load(Relaxed)).unwrap(), ORIGINAL_TID.load(Relaxed)).ok();
result
})
}
fn send_message(pid: PID, tid: TID, cid: CID, message: Message) -> SysCallResult {
SystemServices::with_mut(|ss| {
let sidx = ss.sidx_from_cid(cid).ok_or(xous_kernel::Error::ServerNotFound)?;
let server_pid = ss.server_from_sidx(sidx).expect("server couldn't be located").pid;
// Remember the address the message came from, in case we need to
// return it after the borrow is through.
let client_address = match &message {
Message::Scalar(_) | Message::BlockingScalar(_) => None,
Message::Move(msg) | Message::MutableBorrow(msg) | Message::Borrow(msg) => {
MemoryAddress::new(msg.buf.as_ptr() as _)
}
};
// Translate memory messages from the client process to the server
// process. Additionally, determine whether the call is blocking. If
// so, switch to the server context right away.
let blocking = message.is_blocking();
let message = match message {
Message::Scalar(_) | Message::BlockingScalar(_) => message,
Message::Move(msg) => {
let new_virt = ss.send_memory(
msg.buf.as_mut_ptr() as *mut usize,
server_pid,
core::ptr::null_mut(),
msg.buf.len(),
)?;
Message::Move(MemoryMessage {
id: msg.id,
buf: unsafe { MemoryRange::new(new_virt as usize, msg.buf.len()) }?,
offset: msg.offset,
valid: msg.valid,
})
}
Message::MutableBorrow(msg) => {
let new_virt = ss.lend_memory(
msg.buf.as_mut_ptr() as *mut usize,
server_pid,
core::ptr::null_mut(),
msg.buf.len(),
true,
)?;
Message::MutableBorrow(MemoryMessage {
id: msg.id,
buf: unsafe { MemoryRange::new(new_virt as usize, msg.buf.len()) }?,
offset: msg.offset,
valid: msg.valid,
})
}
Message::Borrow(msg) => {
let new_virt = ss.lend_memory(
msg.buf.as_mut_ptr() as *mut usize,
server_pid,
core::ptr::null_mut(),
msg.buf.len(),
false,
)?;
// println!(
// "Lending {} bytes from {:08x} in PID {} to {:08x} in PID {}",
// msg.buf.len(),
// msg.buf.as_mut_ptr() as usize,
// pid,
// new_virt as usize,
// server_pid,
// );
Message::Borrow(MemoryMessage {
id: msg.id,
buf: unsafe { MemoryRange::new(new_virt as usize, msg.buf.len()) }?,
offset: msg.offset,
valid: msg.valid,
})
}
};
// If the server has an available thread to receive the message,
// transfer it right away.
let server = ss.server_from_sidx_mut(sidx).expect("server couldn't be located");
if let Some(server_tid) = server.take_available_thread() {
// klog!(
// "there are threads available in PID {} to handle this message -- marking as Ready",
// server_pid
// );
let sender_idx = if message.is_blocking() {
ss.remember_server_message(sidx, pid, tid, &message, client_address).map_err(|e| {
klog!("error remembering server message: {:?}", e);
ss.server_from_sidx_mut(sidx)
.expect("server couldn't be located")
.return_available_thread(server_tid);
e
})?
} else {
0
};
let sender = SenderID::new(sidx, sender_idx, Some(pid));
klog!("server connection data: sidx: {}, idx: {}, server pid: {}", sidx, sender_idx, server_pid);
let envelope = MessageEnvelope { sender: sender.into(), body: message };
// Mark the server's context as "Ready". If this fails, return the context
// to the blocking list.
#[cfg(baremetal)]
ss.ready_thread(server_pid, server_tid).map_err(|e| {
ss.server_from_sidx_mut(sidx)
.expect("server couldn't be located")
.return_available_thread(server_tid);
e
})?;
let runnable = ss.runnable(server_pid, Some(server_tid)).expect("server doesn't exist");
// --- NOTE: Returning this value //
return if blocking && cfg!(baremetal) {
if !runnable {
// If it's not runnable (e.g. it's being debugged), switch to the parent.
let (ppid, ptid) = unsafe { SWITCHTO_CALLER.take().unwrap() };
klog!(
"Activating Server parent process (server is blocked) and switching away from Client"
);
ss.set_thread_result(
server_pid,
server_tid,
xous_kernel::Result::MessageEnvelope(envelope),
)
.expect("couldn't set result for server thread");
let result = ss
.activate_process_thread(tid, ppid, ptid, false)
.map(|_| Ok(xous_kernel::Result::ResumeProcess))
.unwrap_or(Err(xous_kernel::Error::ProcessNotFound));
// Keep track of which process owned the quantum. This ensures that the next
// thread in sequence gets to run when this process is activated again.
ss.set_last_thread(
PID::new(ORIGINAL_PID.load(Relaxed)).unwrap(),
ORIGINAL_TID.load(Relaxed),
)
.ok();
result
} else {
// Switch to the server, since it's in a state to be run.
klog!("Activating Server context and switching away from Client");
ss.activate_process_thread(tid, server_pid, server_tid, false)
.map(|_| Ok(xous_kernel::Result::MessageEnvelope(envelope)))
.unwrap_or(Err(xous_kernel::Error::ProcessNotFound))
}
} else if blocking && !cfg!(baremetal) {
klog!("Blocking client, since it sent a blocking message");
ss.unschedule_thread(pid, tid)?;
ss.switch_to_thread(server_pid, Some(server_tid))?;
ss.set_thread_result(server_pid, server_tid, xous_kernel::Result::MessageEnvelope(envelope))
.map(|_| xous_kernel::Result::BlockedProcess)
} else if cfg!(baremetal) {
klog!(
"Setting the return value of the Server ({}:{}) to {:?} and returning to Client",
server_pid,
server_tid,
envelope
);
ss.set_thread_result(server_pid, server_tid, xous_kernel::Result::MessageEnvelope(envelope))
.map(|_| xous_kernel::Result::Ok)
} else {
klog!("setting the return value of the Server to {:?} and returning to Client", envelope);
// "Switch to" the server PID when not running on bare metal. This ensures
// that it's "Running".
ss.switch_to_thread(server_pid, Some(server_tid))?;
ss.set_thread_result(server_pid, server_tid, xous_kernel::Result::MessageEnvelope(envelope))
.map(|_| xous_kernel::Result::Ok)
};
}
klog!("no threads available in PID {} to handle this message, so queueing", server_pid);
// Add this message to the queue. If the queue is full, this
// returns an error.
let _queue_idx = ss.queue_server_message(sidx, pid, tid, message, client_address)?;
klog!("queued into index {:x}", _queue_idx);
// Park this context if it's blocking. This is roughly
// equivalent to a "Yield".
if blocking {
if cfg!(baremetal) {
// println!("Returning to parent");
let process = ss.get_process(pid).expect("Can't get current process");
let ppid = process.ppid;
unsafe { SWITCHTO_CALLER = None };
let result = ss
.activate_process_thread(tid, ppid, 0, false)
.map(|_| Ok(xous_kernel::Result::ResumeProcess))
.unwrap_or(Err(xous_kernel::Error::ProcessNotFound));
ss.set_last_thread(PID::new(ORIGINAL_PID.load(Relaxed)).unwrap(), ORIGINAL_TID.load(Relaxed))
.ok();
result
} else {
ss.unschedule_thread(pid, tid)?;
Ok(xous_kernel::Result::BlockedProcess)
}
} else {
// println!("Returning to Client with Ok result");
Ok(xous_kernel::Result::Ok)
}
})
}
fn return_memory(
server_pid: PID,
server_tid: TID,
in_irq: bool,
sender: MessageSender,
buf: MemoryRange,
offset: Option<MemorySize>,
valid: Option<MemorySize>,
) -> SysCallResult {
SystemServices::with_mut(|ss| {
let sender = SenderID::from(sender);
let server = ss.server_from_sidx_mut(sender.sidx).ok_or(xous_kernel::Error::ServerNotFound)?;
if server.pid != server_pid {
return Err(xous_kernel::Error::ServerNotFound);
}
let result = server.take_waiting_message(sender.idx, Some(&buf))?;
klog!("waiting message was: {:?}", result);
let (client_pid, client_tid, _server_addr, client_addr, len) = match result {
WaitingMessage::BorrowedMemory(client_pid, client_ctx, server_addr, client_addr, len) => {
(client_pid, client_ctx, server_addr, client_addr, len)
}
WaitingMessage::MovedMemory => {
return Ok(xous_kernel::Result::Ok);
}
WaitingMessage::ForgetMemory(range) => {
return MemoryManager::with_mut(|mm| {
let mut result = Ok(xous_kernel::Result::Ok);
let virt = range.as_ptr() as usize;
let size = range.len();
if cfg!(baremetal) && virt & 0xfff != 0 {
klog!("VIRT NOT DIVISIBLE BY 4: {:08x}", virt);
return Err(xous_kernel::Error::BadAlignment);
}
for addr in (virt..(virt + size)).step_by(PAGE_SIZE) {
if let Err(e) = mm.unmap_page(addr as *mut usize) {
if result.is_ok() {
result = Err(e);
}
}
}
result
});
}
WaitingMessage::ScalarMessage(_pid, _tid) => {
klog!("WARNING: Tried to wait on a message that was a scalar");
return Err(xous_kernel::Error::DoubleFree);
}
WaitingMessage::None => {
klog!("WARNING: Tried to wait on a message that didn't exist -- return memory");
return Err(xous_kernel::Error::DoubleFree);
}
};
// println!(
// "KERNEL({}): Returning {} bytes from {:08x} in PID {} to {:08x} in PID {} in context {}",
// pid,
// len,
// _server_addr.get(),
// pid,
// client_addr.get(),
// client_pid,
// client_tid
// );
#[cfg(baremetal)]
let src_virt = _server_addr.get() as _;
#[cfg(not(baremetal))]
let src_virt = buf.as_ptr() as _;
let return_value = xous_kernel::Result::MemoryReturned(offset, valid);
// Return the memory to the calling process
ss.return_memory(src_virt, client_pid, client_tid, client_addr.get() as _, len.get())?;
if cfg!(baremetal) {
ss.ready_thread(client_pid, client_tid)?;
}
// Return to the server if any of the following are true:
//
// 1. We're running in hosted mode -- hosted mode runs all threads simultaneously anyway
// 2. We're in an interrupt -- interrupts cannot cross process boundaries
// 3. The client isn't runnable -- it may be being debugged
// 4. We're in the quantum assigned to the server -- this prevents pipeline blockages
if !cfg!(baremetal)
|| in_irq
|| !ss.runnable(client_pid, Some(client_tid))?
|| (ORIGINAL_PID.load(Relaxed) == server_pid.get() && ORIGINAL_TID.load(Relaxed) == client_tid)
{
// In a hosted environment, `switch_to_thread()` doesn't continue
// execution from the new thread. Instead it continues in the old
// thread. Therefore, we need to instruct the client to resume, and
// return to the server.
#[cfg(not(baremetal))]
ss.switch_to_thread(client_pid, Some(client_tid))?;
// In a baremetal environment, the opposite is true -- we instruct
// the server to resume and return to the client.
ss.set_thread_result(client_pid, client_tid, return_value)?;
Ok(xous_kernel::Result::Ok)
} else {
// Switch away from the server, but leave it as Runnable
if cfg!(baremetal) {
ss.unschedule_thread(server_pid, server_tid)?;
ss.ready_thread(server_pid, server_tid)?
}
ss.set_thread_result(server_pid, server_tid, xous_kernel::Result::Ok)?;
// Switch to the client
ss.switch_to_thread(client_pid, Some(client_tid))?;
Ok(return_value)
}
})
}
fn return_result(
server_pid: PID,
server_tid: TID,
in_irq: bool,
sender: MessageSender,
return_value: xous_kernel::Result,
) -> SysCallResult {
SystemServices::with_mut(|ss| {
let sender = SenderID::from(sender);
let server = ss.server_from_sidx_mut(sender.sidx).ok_or(xous_kernel::Error::ServerNotFound)?;
if server.pid != server_pid {
return Err(xous_kernel::Error::ServerNotFound);
}
let result = server.take_waiting_message(sender.idx, None)?;
let (client_pid, client_tid) = match result {
WaitingMessage::ScalarMessage(pid, tid) => (pid, tid),
WaitingMessage::ForgetMemory(_) => {
klog!("WARNING: Tried to wait on a scalar message that was actually forgettingmemory");
return Err(xous_kernel::Error::DoubleFree);
}
WaitingMessage::BorrowedMemory(_, _, _, _, _) => {
klog!("WARNING: Tried to wait on a scalar message that was actually borrowed memory");
return Err(xous_kernel::Error::DoubleFree);
}
WaitingMessage::MovedMemory => {
klog!("WARNING: Tried to wait on a scalar message that was actually moved memory");
return Err(xous_kernel::Error::DoubleFree);
}
WaitingMessage::None => {
klog!(
"WARNING ({}:{}): Tried to wait on a message that didn't exist (irq? {}) -- return {:?}",
server_pid.get(),
server_tid,
if in_irq { "yes" } else { "no" },
result
);
return Err(xous_kernel::Error::DoubleFree);
}
};
if cfg!(baremetal) {
ss.ready_thread(client_pid, client_tid)?;
}
// Return to the server if any of the following are true:
//
// 1. We're running in hosted mode -- hosted mode runs all threads simultaneously anyway
// 2. We're in an interrupt -- interrupts cannot cross process boundaries
// 3. The client isn't runnable -- it may be being debugged
// 4. We're in the quantum assigned to the server -- this prevents pipeline blockages
if !cfg!(baremetal)
|| in_irq
|| !ss.runnable(client_pid, Some(client_tid))?
|| (ORIGINAL_PID.load(Relaxed) == server_pid.get() && ORIGINAL_TID.load(Relaxed) == client_tid)
{
// In a hosted environment, `switch_to_thread()` doesn't continue
// execution from the new thread. Instead it continues in the old
// thread. Therefore, we need to instruct the client to resume, and
// return to the server.
// In a baremetal environment, the opposite is true -- we instruct
// the server to resume and return to the client.
ss.set_thread_result(client_pid, client_tid, return_value)?;
Ok(xous_kernel::Result::Ok)
} else {
if cfg!(baremetal) {
ss.unschedule_thread(server_pid, server_tid)?;
ss.ready_thread(server_pid, server_tid)?
}
// Switch away from the server, but leave it as Runnable
ss.set_thread_result(server_pid, server_tid, xous_kernel::Result::Ok)?;
// Switch to the client
ss.switch_to_thread(client_pid, Some(client_tid))?;
Ok(return_value)
}
})
}
fn reply_and_receive_next(
server_pid: PID,
server_tid: TID,
in_irq: bool,
sender: MessageSender,
arg0: usize,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
scalar_type: usize,
) -> SysCallResult {
let sender = SenderID::from(sender);
SystemServices::with_mut(|ss| {
struct MessageResponse {
pid: PID,
tid: TID,
result: xous_kernel::Result,
}
let (result, next_message) = {
let server = ss.server_from_sidx_mut(sender.sidx).ok_or(xous_kernel::Error::ServerNotFound)?;
if server.pid != server_pid {
println!(
"WARNING: PIDs don't match! The server is from PID {}, but our PID is {}",
server.pid, server_pid
);
return Err(xous_kernel::Error::ServerNotFound);
}
let waiting_message = server.take_waiting_message(sender.idx, None)?;
let next_message = server.take_next_message(sender.sidx);
// If there is no message, park the server thread. We do this here because
// we cannot hold the `Server` object while we also modify process state.
if next_message.is_none() {
server.park_thread(server_tid);
}
(waiting_message, next_message)
};
// TODO: Have errors turn into calls to `ReceiveMessage`
let response = match result {
WaitingMessage::ScalarMessage(pid, tid) => {
let result = match scalar_type {
1 => xous_kernel::Result::Scalar1(arg1),
2 => xous_kernel::Result::Scalar2(arg1, arg2),
_ => xous_kernel::Result::Scalar5(arg0, arg1, arg2, arg3, arg4),
};
MessageResponse { pid, tid, result }
}
WaitingMessage::ForgetMemory(_) => {
klog!("WARNING: Tried to wait on a scalar message that was actually forgetting memory");
return Err(xous_kernel::Error::DoubleFree);
}
WaitingMessage::BorrowedMemory(pid, tid, _server_addr, client_addr, len) => {
#[cfg(baremetal)]
let src_virt = _server_addr.get() as _;
#[cfg(not(baremetal))]
let src_virt = arg1 as _;
// Return the memory to the calling process
ss.return_memory(src_virt, pid, tid, client_addr.get() as _, len.get())?;
MessageResponse {
pid,
tid,
result: xous_kernel::Result::MemoryReturned(MemorySize::new(arg3), MemorySize::new(arg4)),
}
}
WaitingMessage::MovedMemory => {
klog!("WARNING: Tried to wait on a scalar message that was actually moved memory");
return Err(xous_kernel::Error::DoubleFree);
}
WaitingMessage::None => {
klog!("WARNING: Tried to wait on a message that didn't exist -- receive and return scalar");
return Err(xous_kernel::Error::DoubleFree);
}
};
let client_pid = response.pid;
let client_tid = response.tid;
if cfg!(baremetal) {
ss.ready_thread(client_pid, client_tid)?;
}
// If there is a pending message, fetch it and schedule the thread to run
if let Some(msg) = next_message {
if !cfg!(baremetal)
|| in_irq
|| !ss.runnable(client_pid, Some(client_tid))?
|| (ORIGINAL_PID.load(Relaxed) == server_pid.get()
&& ORIGINAL_TID.load(Relaxed) == client_tid)
{
// Switch to the client and return the result
ss.set_thread_result(response.pid, response.tid, response.result)?;
// Return the new message envelope to the server
Ok(xous_kernel::Result::MessageEnvelope(msg))
} else {
if cfg!(baremetal) {
ss.unschedule_thread(server_pid, server_tid)?;
ss.ready_thread(server_pid, server_tid)?
}
// When the server is resumed, it will receive this as a return value.
ss.set_thread_result(server_pid, server_tid, xous_kernel::Result::MessageEnvelope(msg))?;
// Switch to the client
ss.switch_to_thread(response.pid, Some(response.tid))?;
Ok(response.result)
}
} else {
// For baremetal targets, switch away from this process.
if cfg!(baremetal) {
// Set the thread result for the client
ss.set_thread_result(response.pid, response.tid, response.result)?;
// Activate the client thread and switch to it
ss.activate_process_thread(server_tid, response.pid, response.tid, false)
.map(|_| Ok(xous_kernel::Result::ResumeProcess))
.unwrap_or(Err(xous_kernel::Error::ProcessNotFound))
}
// For hosted targets, simply return `BlockedProcess` indicating we'll make
// a callback to their socket at a later time.
else {
ss.unschedule_thread(server_pid, server_tid)?;
// Switch to the client and return the result
ss.switch_to_thread(response.pid, Some(response.tid))?;
ss.set_thread_result(response.pid, response.tid, response.result)?;
// Indicate that the server should block its process
Ok(xous_kernel::Result::BlockedProcess)
}
}
})
}
fn receive_message(pid: PID, tid: TID, sid: SID, blocking: ExecutionType) -> SysCallResult {
SystemServices::with_mut(|ss| {
assert!(ss.thread_is_running(pid, tid), "current thread is not running");
// See if there is a pending message. If so, return immediately.
let sidx = ss.sidx_from_sid(sid, pid).ok_or(xous_kernel::Error::ServerNotFound)?;
let server = ss.server_from_sidx_mut(sidx).ok_or(xous_kernel::Error::ServerNotFound)?;
// server.print_queue();
// Ensure the server is for this PID
if server.pid != pid {
return Err(xous_kernel::Error::ServerNotFound);
}
// If there is a pending message, return it immediately.
if let Some(msg) = server.take_next_message(sidx) {
klog!("waiting messages found -- returning {:x?}", msg);
return Ok(xous_kernel::Result::MessageEnvelope(msg));
}
if blocking == ExecutionType::NonBlocking {
klog!("nonblocking message -- returning None");
return Ok(xous_kernel::Result::None);
}
// There is no pending message, so return control to the parent
// process and mark ourselves as awaiting an event. When a message
// arrives, our return value will already be set to the
// MessageEnvelope of the incoming message.
klog!("did not have any waiting messages -- parking thread {}", tid);
server.park_thread(tid);
// For baremetal targets, switch away from this process.
if cfg!(baremetal) {
unsafe { SWITCHTO_CALLER = None };
let ppid = ss.get_process(pid).expect("Can't get current process").ppid;
// TODO: Advance thread
let result = ss
.activate_process_thread(tid, ppid, 0, false)
.map(|_| Ok(xous_kernel::Result::ResumeProcess))
.unwrap_or(Err(xous_kernel::Error::ProcessNotFound));
ss.set_last_thread(PID::new(ORIGINAL_PID.load(Relaxed)).unwrap(), ORIGINAL_TID.load(Relaxed))
.ok();
result
}
// For hosted targets, simply return `BlockedProcess` indicating we'll make
// a callback to their socket at a later time.
else {
ss.unschedule_thread(pid, tid).map(|_| xous_kernel::Result::BlockedProcess)
}
})
}
pub fn handle(pid: PID, tid: TID, in_irq: bool, call: SysCall) -> SysCallResult {
klog!("KERNEL({}:{}): Syscall {:x?}, in_irq={}", pid, tid, call, in_irq);
// let call_string = format!("{:x?}", call);
// let start_time = std::time::Instant::now();
#[allow(clippy::let_and_return)]
let result = if in_irq && !call.can_call_from_interrupt() {
klog!("[!] Called {:?} that's cannot be called from the interrupt handler!", call);
Err(xous_kernel::Error::InvalidSyscall)
} else {
handle_inner(pid, tid, in_irq, call)
};
// println!("KERNEL [{:2}:{:2}] Syscall took {:7} usec: {}", pid, tid, start_time.elapsed().as_micros(),
// call_string);
klog!(
" -> ({}:{}) {:x?}",
crate::arch::current_pid(),
crate::arch::process::Process::current().current_tid(),
result
);
result
}
pub fn handle_inner(pid: PID, tid: TID, in_irq: bool, call: SysCall) -> SysCallResult {
match call {
SysCall::MapMemory(phys, virt, size, req_flags) => {
MemoryManager::with_mut(|mm| {
let phys_ptr = phys.map(|x| x.get() as *mut u8).unwrap_or(core::ptr::null_mut());
let virt_ptr = virt.map(|x| x.get() as *mut u8).unwrap_or(core::ptr::null_mut());
// Don't let the address exceed the user area (unless it's PID 1)
if pid.get() != 1 && virt.map(|x| x.get() >= arch::mem::USER_AREA_END).unwrap_or(false) {
klog!("Exceeded user area");
return Err(xous_kernel::Error::BadAddress);
// Don't allow mapping non-page values
} else if size.get() & (PAGE_SIZE - 1) != 0 {
// println!("map: bad alignment of size {:08x}", size);
return Err(xous_kernel::Error::BadAlignment);
}
// println!(
// "Mapping {:08x} -> {:08x} ({} bytes, flags: {:?})",
// phys_ptr as u32, virt_ptr as u32, size, req_flags
// );
let range =
mm.map_range(phys_ptr, virt_ptr, size.get(), pid, req_flags, MemoryType::Default)?;
if !phys_ptr.is_null() {
if mm.is_main_memory(phys_ptr) {
let range_start = range.as_mut_ptr() as *mut usize;
let range_end = range_start.wrapping_add(range.len() / core::mem::size_of::<usize>());
unsafe {
crate::mem::bzero(range_start, range_end);
};
}
for offset in
(range.as_ptr() as usize..(range.as_ptr() as usize + range.len())).step_by(PAGE_SIZE)
{
// println!("Handing page to user");
crate::arch::mem::hand_page_to_user(offset as *mut u8)
.expect("couldn't hand page to user");
}
}
Ok(xous_kernel::Result::MemoryRange(range))
})
}
SysCall::UnmapMemory(range) => MemoryManager::with_mut(|mm| {
let mut result = Ok(xous_kernel::Result::Ok);
let virt = range.as_ptr() as usize;
let size = range.len();
if cfg!(baremetal) && virt & 0xfff != 0 {
return Err(xous_kernel::Error::BadAlignment);
}
for addr in (virt..(virt + size)).step_by(PAGE_SIZE) {
if let Err(e) = mm.unmap_page(addr as *mut usize) {
if result.is_ok() {
result = Err(e);
}
}
}
result
}),
SysCall::IncreaseHeap(delta, flags) => {
if delta & 0xfff != 0 {
return Err(xous_kernel::Error::BadAlignment);
}
// Special case for a delta of 0 -- just return the current heap size
if delta == 0 {
let (start, length) = ArchProcess::with_inner_mut(|process_inner| {
(process_inner.mem_heap_base, process_inner.mem_heap_size)
});
return Ok(xous_kernel::Result::MemoryRange(unsafe {
MemoryRange::new(
start,
// 0-length MemoryRanges are disallowed -- so return 4096 as the minimum in any case,
// even though it's a lie
if length == 0 { 4096 } else { length },
)
.unwrap()
}));
}
let start = {
ArchProcess::with_inner_mut(|process_inner| {
if process_inner.mem_heap_size + delta > process_inner.mem_heap_max {
return Err(xous_kernel::Error::OutOfMemory);
}
let start = process_inner.mem_heap_base + process_inner.mem_heap_size;
process_inner.mem_heap_size += delta;
Ok(start as *mut u8)
})?
};
// Mark the new pages as "reserved"
MemoryManager::with_mut(|mm| {
Ok(xous_kernel::Result::MemoryRange(mm.reserve_range(start, delta, flags)?))
})
}
SysCall::DecreaseHeap(delta) => {
if delta & 0xfff != 0 {
return Err(xous_kernel::Error::BadAlignment);
}
let (start, length, end) = ArchProcess::with_inner_mut(|process_inner| {
// Don't allow decreasing the heap beyond the current allocation
if delta > process_inner.mem_heap_size {
return Err(xous_kernel::Error::OutOfMemory);
}
let end = process_inner.mem_heap_base + process_inner.mem_heap_size;
process_inner.mem_heap_size -= delta;
Ok((process_inner.mem_heap_base, process_inner.mem_heap_size, end))
})?;
// Unmap the pages from the heap
MemoryManager::with_mut(|mm| {
for page in ((end - delta)..end).step_by(crate::arch::mem::PAGE_SIZE) {
mm.unmap_page(page as *mut usize).expect("unable to unmap page");
}
});
// Return the new size of the heap
Ok(xous_kernel::Result::MemoryRange(unsafe { MemoryRange::new(start, length).unwrap() }))
}
SysCall::SwitchTo(new_pid, new_tid) => SystemServices::with_mut(|ss| {
unsafe {
assert!(
SWITCHTO_CALLER.is_none(),
"SWITCHTO_CALLER was {:?} and not None, indicating SwitchTo was called twice",
SWITCHTO_CALLER,
);
SWITCHTO_CALLER = Some((pid, tid));
}
// println!(
// "Activating process thread {} in pid {} coming from pid {} thread {}",
// new_context, new_pid, pid, tid
// );
let new_tid = ss.activate_process_thread(tid, new_pid, new_tid, true)?;
ORIGINAL_PID.store(new_pid.get(), Relaxed);
ORIGINAL_TID.store(new_tid, Relaxed);
Ok(xous_kernel::Result::ResumeProcess)
}),
SysCall::ClaimInterrupt(no, callback, arg) => {
interrupt_claim(no, pid as definitions::PID, callback, arg).map(|_| xous_kernel::Result::Ok)
}
SysCall::FreeInterrupt(no) => {
interrupt_free(no, pid as definitions::PID).map(|_| xous_kernel::Result::Ok)
}
SysCall::Yield => do_yield(pid, tid),
SysCall::ReturnToParent(_pid, _cpuid) => {
unsafe {
if let Some((parent_pid, parent_ctx)) = SWITCHTO_CALLER.take() {
crate::arch::irq::set_isr_return_pair(parent_pid, parent_ctx)
}
};
Ok(xous_kernel::Result::ResumeProcess)
}
SysCall::ReceiveMessage(sid) => receive_message(pid, tid, sid, ExecutionType::Blocking),
SysCall::TryReceiveMessage(sid) => receive_message(pid, tid, sid, ExecutionType::NonBlocking),
SysCall::WaitEvent => SystemServices::with_mut(|ss| {
let process = ss.get_process(pid).expect("Can't get current process");
let ppid = process.ppid;
unsafe { SWITCHTO_CALLER = None };
// TODO: Advance thread
if cfg!(baremetal) {
let result = ss
.activate_process_thread(tid, ppid, 0, false)
.map(|_| Ok(xous_kernel::Result::ResumeProcess))
.unwrap_or(Err(xous_kernel::Error::ProcessNotFound));
ss.set_last_thread(PID::new(ORIGINAL_PID.load(Relaxed)).unwrap(), ORIGINAL_TID.load(Relaxed))
.ok();
result
} else {
Ok(xous_kernel::Result::Ok)
}
}),
SysCall::CreateThread(thread_init) => SystemServices::with_mut(|ss| {
ss.create_thread(pid, thread_init).map(|new_tid| {
// Set the return value of the existing thread to be the new thread ID
if cfg!(baremetal) {
// Immediately switch to the new thread
ss.switch_to_thread(pid, Some(new_tid)).expect("couldn't activate new thread");
ss.set_thread_result(pid, tid, xous_kernel::Result::ThreadID(new_tid))
.expect("couldn't set new thread ID");
// Return `ResumeProcess` since we're switching threads
xous_kernel::Result::ResumeProcess
} else {
xous_kernel::Result::ThreadID(new_tid)
}
})
}),
SysCall::CreateProcess(process_init) => SystemServices::with_mut(|ss| {
ss.create_process(process_init).map(xous_kernel::Result::NewProcess)
}),
SysCall::CreateServerWithAddress(name) => SystemServices::with_mut(|ss| {
ss.create_server_with_address(pid, name, true)
.map(|(sid, cid)| xous_kernel::Result::NewServerID(sid, cid))
}),
SysCall::CreateServer => SystemServices::with_mut(|ss| {
ss.create_server(pid, true).map(|(sid, cid)| xous_kernel::Result::NewServerID(sid, cid))
}),
SysCall::CreateServerId => {
SystemServices::with_mut(|ss| ss.create_server_id().map(xous_kernel::Result::ServerID))
}
SysCall::TryConnect(sid) => {
SystemServices::with_mut(|ss| ss.connect_to_server(sid).map(xous_kernel::Result::ConnectionID))
}
SysCall::ReturnMemory(sender, buf, offset, valid) => {
return_memory(pid, tid, in_irq, sender, buf, offset, valid)
}
SysCall::ReturnScalar1(sender, arg) => {
return_result(pid, tid, in_irq, sender, xous_kernel::Result::Scalar1(arg))
}
SysCall::ReturnScalar2(sender, arg1, arg2) => {
return_result(pid, tid, in_irq, sender, xous_kernel::Result::Scalar2(arg1, arg2))
}
SysCall::ReturnScalar5(sender, arg1, arg2, arg3, arg4, arg5) => return_result(
pid,
tid,
in_irq,
sender,
xous_kernel::Result::Scalar5(arg1, arg2, arg3, arg4, arg5),
),
SysCall::ReplyAndReceiveNext(sender, a0, a1, a2, a3, a4, scalar_type) => {
reply_and_receive_next(pid, tid, in_irq, sender, a0, a1, a2, a3, a4, scalar_type)
}
SysCall::TrySendMessage(cid, message) => send_message(pid, tid, cid, message),
SysCall::TerminateProcess(_ret) => SystemServices::with_mut(|ss| {
ss.unschedule_thread(pid, tid)?;
ss.terminate_process(pid)?;
// Clear out `SWITCHTO_CALLER` since we're resuming the parent process.
unsafe { SWITCHTO_CALLER = None };
Ok(xous_kernel::Result::ResumeProcess)
}),
SysCall::Shutdown => SystemServices::with_mut(|ss| ss.shutdown().map(|_| xous_kernel::Result::Ok)),
SysCall::GetProcessId => Ok(xous_kernel::Result::ProcessID(pid)),
SysCall::GetThreadId => Ok(xous_kernel::Result::ThreadID(tid)),
SysCall::Connect(sid) => {
let result = SystemServices::with_mut(|ss| {
ss.connect_to_server(sid).map(xous_kernel::Result::ConnectionID)
});
match result {
Ok(o) => Ok(o),
Err(xous_kernel::Error::ServerNotFound) => retry_syscall(pid, tid),
Err(e) => Err(e),
}
}
SysCall::ConnectForProcess(pid, sid) => {
let result = SystemServices::with_mut(|ss| {
ss.connect_process_to_server(pid, sid).map(xous_kernel::Result::ConnectionID)
});
match result {
Ok(o) => Ok(o),
Err(xous_kernel::Error::ServerNotFound) => retry_syscall(pid, tid),
Err(e) => Err(e),
}
}
SysCall::SendMessage(cid, message) => {
let result = send_message(pid, tid, cid, message);
match result {
Ok(o) => Ok(o),
Err(xous_kernel::Error::ServerQueueFull) => retry_syscall(pid, tid),
Err(e) => Err(e),
}
}
SysCall::Disconnect(cid) => {
SystemServices::with_mut(|ss| ss.disconnect_from_server(cid).and(Ok(xous_kernel::Result::Ok)))
}
SysCall::DestroyServer(sid) => {
SystemServices::with_mut(|ss| ss.destroy_server(pid, sid).and(Ok(xous_kernel::Result::Ok)))
}
SysCall::JoinThread(other_tid) => {
SystemServices::with_mut(|ss| ss.join_thread(pid, tid, other_tid)).map(|ret| {
// Successfully joining a thread causes this thread to sleep while the parent process
// is resumed. This is the same as a `Yield`
if ret == xous_kernel::Result::ResumeProcess {
unsafe { SWITCHTO_CALLER = None };
}
ret
})
}
SysCall::UpdateMemoryFlags(range, flags, pid) => {
// We do not yet support modifying flags for other processes.
if pid.is_some() {
return Err(xous_kernel::Error::ProcessNotChild);
}
MemoryManager::with_mut(|mm| mm.update_memory_flags(range, flags))?;
Ok(xous_kernel::Result::Ok)
}
SysCall::AdjustProcessLimit(index, current, new) => match index {
1 => arch::process::Process::with_inner_mut(|p| {
if p.mem_heap_max == current {
p.mem_heap_max = new;
}
Ok(xous_kernel::Result::Scalar2(index, p.mem_heap_max))
}),
2 => arch::process::Process::with_inner_mut(|p| {
if p.mem_heap_size == current && new < p.mem_heap_max {
p.mem_heap_size = new;
}
Ok(xous_kernel::Result::Scalar2(index, p.mem_heap_size))
}),
_ => Err(xous_kernel::Error::InvalidLimit),
},
#[cfg(feature = "v2p")]
SysCall::VirtToPhys(vaddr) => {
let phys_addr = crate::arch::mem::virt_to_phys(vaddr as usize);
match phys_addr {
Ok(pa) => Ok(xous_kernel::Result::Scalar1(pa)),
Err(_) => Err(xous_kernel::Error::BadAddress),
}
}
#[cfg(feature = "v2p")]
SysCall::VirtToPhysPid(pid, vaddr) => {
let phys_addr = crate::arch::mem::virt_to_phys_pid(pid, vaddr as usize);
match phys_addr {
Ok(pa) => Ok(xous_kernel::Result::Scalar1(pa)),
Err(_) => Err(xous_kernel::Error::BadAddress),
}
}
#[cfg(feature = "swap")]
SysCall::RegisterSwapper(s0, s1, s2, s3, handler, state) => {
Swap::with_mut(|swap| swap.register_handler(s0, s1, s2, s3, handler, state))
}
#[cfg(feature = "swap")]
SysCall::SwapOp(op, a1, a2, a3, a4, a5, a6) => {
#[cfg(feature = "debug-swap-verbose")]
println!("Swap ABI via syscall: {:?}", SwapAbi::from(op));
match SwapAbi::from(op) {
SwapAbi::ClearMemoryNow => {
if pid.get() != xous_kernel::SWAPPER_PID {
return Err(xous_kernel::Error::AccessDenied);
} else {
Swap::with_mut(|swap| {
swap.clearmem_stop_irq();
swap.hard_oom_syscall()
})
}
}
SwapAbi::GetFreePages => {
if pid.get() != xous_kernel::SWAPPER_PID {
return Err(xous_kernel::Error::AccessDenied);
}
Swap::with(|swap| swap.get_free_mem())
}
SwapAbi::StealPage => {
if pid.get() != xous_kernel::SWAPPER_PID {
return Err(xous_kernel::Error::AccessDenied);
}
let target_pid = a1 as u8;
let vaddr = a2;
match crate::arch::mem::evict_page_inner(
PID::new(target_pid as u8).expect("Invalid PID"),
vaddr,
) {
Ok(ptr) => Ok(xous_kernel::Result::Scalar5(ptr, 0, 0, 0, 0)),
Err(_e) => {
#[cfg(feature = "debug-swap")]
println!(
"steal_page rejecting request for pid{}/{:x}: {:?}",
target_pid, vaddr, _e
);
Err(xous_kernel::Error::BadAddress)
}
}
}
SwapAbi::ReleaseMemory => {
if pid.get() != xous_kernel::SWAPPER_PID {
return Err(xous_kernel::Error::AccessDenied);
}
let vaddr_to_release = a1;
let original_pid = a2 as u8;
MemoryManager::with_mut(|mm| {
let paddr = crate::arch::mem::virt_to_phys(vaddr_to_release).unwrap() as usize;
#[cfg(feature = "debug-swap-verbose")]
println!("ReleaseMemory - paddr {:x}", paddr);
// this call unmaps the virtual page from the page table
crate::arch::mem::unmap_page_inner(mm, vaddr_to_release)
.expect("couldn't unmap page");
// This call releases the physical page from the RPT - the pid has to match that of
// the original owner. This is the "pointy end" of the stick;
// after this call, the memory is now back into the free pool.
mm.release_page_swap(paddr as *mut usize, PID::new(original_pid).unwrap())
.expect("couldn't free page that was swapped out");
Ok(xous_kernel::Result::Ok)
})
}
SwapAbi::HardOom => {
// Hard OOM can be invoked from any PID, but, only by a kernel routine. Check that
// we're in supervisor mode!
if riscv::register::sstatus::read().spp() == riscv::register::sstatus::SPP::Supervisor {
// any process can invoke the hard OOM syscall, but only from within the kernel
Swap::with_mut(|swap| swap.hard_oom_syscall())
} else {
Err(xous_kernel::Error::AccessDenied)
}
}
SwapAbi::RetrievePage => {
if riscv::register::sstatus::read().spp() == riscv::register::sstatus::SPP::Supervisor {
// any process can invoke RetrievePage, but only from within the kernel
let target_vaddr_in_pid = a1;
let paddr = a2;
Swap::with_mut(|swap| swap.retrieve_page_syscall(target_vaddr_in_pid, paddr))
} else {
Err(xous_kernel::Error::AccessDenied)
}
}
SwapAbi::Invalid => {
println!(
"Invalid SwapOp: {:x} {:x} {:x} {:x} {:x} {:x} {:x}",
op, a1, a2, a3, a4, a5, a6
);
Err(xous_kernel::Error::UnhandledSyscall)
}
}
}
#[cfg(feature = "raw-trng")]
SysCall::RawTrng(_a1, _a2, _a3, _a4, _a5, _a6, _a7) => {
// TODO: implement this platform call for other targets
// Right now, there is no get_raw_u32() for precursor/renode/hosted
Ok(xous_kernel::Result::Scalar5(
crate::platform::rand::get_raw_u32() as usize,
crate::platform::rand::get_raw_u32() as usize,
crate::platform::rand::get_raw_u32() as usize,
crate::platform::rand::get_raw_u32() as usize,
0,
))
}
/* https://github.com/betrusted-io/xous-core/issues/90
SysCall::SetExceptionHandler(pc, sp) => SystemServices::with_mut(|ss| {
ss.set_exception_handler(pid, pc, sp)
.and(Ok(xous_kernel::Result::Ok))
}),
*/
_ => Err(xous_kernel::Error::UnhandledSyscall),
}
}