blob: 46a2f4046c951702445038b366462cdaebc61bdd [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use foreign_box::ForeignRc;
use memory_config::MemoryRegionType;
use pw_cast::CastInto as _;
use pw_log::info;
use pw_status::{Error, Result};
use pw_time_core::{Clock, Instant};
use syscall_defs::{ExitStatus, Signals, SysCallId, SysCallReturnValue, WaitReturn};
use crate::Kernel;
use crate::interrupt_controller::InterruptController;
use crate::object::{KernelObject, SyscallBuffer};
const SYSCALL_DEBUG: bool = false;
/// An arch-specific collection of syscall arguments
///
/// Since architectures ABI, calling, and syscall conventions can differ, this
/// trait provides a common API for extracting syscall arguments. The API does
/// not provide any index based methods for retrieving arguments because, on some
/// architectures, the location of an argument can depend of the size of the
/// preceding arguments.
pub trait SyscallArgs<'a> {
/// Return the next `usize` argument.
fn next_usize(&mut self) -> Result<usize>;
/// Return the next `u64` argument.
fn next_u64(&mut self) -> Result<u64>;
/// Return the next `u32` argument.
#[inline(always)]
fn next_u32(&mut self) -> Result<u32> {
u32::try_from(self.next_usize()?).map_err(|_| Error::InvalidArgument)
}
/// Return the next `Instant` argument.
#[inline(always)]
fn next_instant<C: Clock>(&mut self) -> Result<Instant<C>> {
Ok(Instant::from_ticks(self.next_u64()?))
}
}
pub fn lookup_handle<K: Kernel>(
kernel: K,
handle: u32,
) -> Result<ForeignRc<K::AtomicUsize, dyn KernelObject<K>>> {
let Some(object) = kernel
.get_scheduler()
.lock(kernel)
.current_thread()
.get_object(kernel, handle)
else {
log_if::debug_if!(
SYSCALL_DEBUG,
"sycall: ObjectWait can't access handle {:#010x}",
handle as u32
);
return Err(Error::OutOfRange);
};
Ok(object)
}
fn handle_object_wait<'a, K: Kernel>(
kernel: K,
mut args: K::SyscallArgs<'a>,
) -> Result<WaitReturn> {
let handle = args.next_u32()?;
let signals = args.next_u32()?;
let deadline = args.next_instant()?;
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: handling object_wait {:#010x} {:#010x} {:#018x}",
handle as u32,
signals as u32,
deadline.ticks() as u64
);
let object = lookup_handle(kernel, handle)?;
let Some(signal_mask) = Signals::from_bits(signals) else {
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: ObjectWait invalid signal mask: {:#010x}",
signals as u32
);
return Err(Error::InvalidArgument);
};
let ret = object.object_wait(kernel, signal_mask, deadline);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: object_wait complete");
ret
}
fn handle_wait_group_add<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
let wait_group_handle = args.next_u32()?;
let object_handle = args.next_u32()?;
let signals = args.next_u32()?;
let user_data = args.next_usize()?;
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: handling wait_group_add {:#010x} {:#010x} {:#010x} {:#x}",
wait_group_handle as u32,
object_handle as u32,
signals as u32,
user_data as usize
);
let wait_group = lookup_handle(kernel, wait_group_handle)?;
let object = lookup_handle(kernel, object_handle)?;
let Some(signal_mask) = Signals::from_bits(signals) else {
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: WaitGroupAdd invalid signal mask: {:#010x}",
signals as u32
);
return Err(Error::InvalidArgument);
};
// Safety: `wait_group` is returned from lookup_handle() as a ForeignRc.
let ret = unsafe { wait_group.wait_group_add(kernel, &*object, signal_mask, user_data) };
log_if::debug_if!(SYSCALL_DEBUG, "syscall: wait_group_add complete");
ret
}
fn handle_wait_group_remove<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
let wait_group_handle = args.next_u32()?;
let object_handle = args.next_u32()?;
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: handling wait_group_remove {:#010x} {:#010x}",
wait_group_handle as u32,
object_handle as u32
);
let wait_group = lookup_handle(kernel, wait_group_handle)?;
let object = lookup_handle(kernel, object_handle)?;
let ret = wait_group.wait_group_remove(kernel, &*object);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: wait_group_remove complete");
ret
}
fn handle_channel_transact<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<u64> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling channel_transact");
let handle = args.next_u32()?;
let send_data_addr = args.next_usize()?;
let send_data_len = args.next_usize()?;
let recv_data_addr = args.next_usize()?;
let recv_data_len = args.next_usize()?;
let deadline: Instant<K::Clock> = args.next_instant()?;
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: handling channel_transact({:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x})",
handle as u32,
send_data_addr as usize,
send_data_len as usize,
recv_data_addr as usize,
recv_data_len as usize,
deadline.ticks() as u64,
);
let object = lookup_handle(kernel, handle)?;
let send_buffer = SyscallBuffer::new_in_current_process(
kernel,
MemoryRegionType::ReadOnlyData,
send_data_addr,
send_data_len.cast_signed(),
)?;
let recv_buffer = SyscallBuffer::new_in_current_process(
kernel,
MemoryRegionType::ReadWriteData,
recv_data_addr,
recv_data_len.cast_signed(),
)?;
let ret = object.channel_transact(kernel, send_buffer, recv_buffer, deadline);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: channel_transact complete");
ret.map(|v| v.cast_into())
}
fn handle_channel_async_transact<'a, K: Kernel>(
kernel: K,
mut args: K::SyscallArgs<'a>,
) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling channel_async_transact");
let handle = args.next_u32()?;
let send_data_addr = args.next_usize()?;
let send_data_len = args.next_usize()?;
let recv_data_addr = args.next_usize()?;
let recv_data_len = args.next_usize()?;
let object = lookup_handle(kernel, handle)?;
let send_buffer = SyscallBuffer::new_in_current_process(
kernel,
MemoryRegionType::ReadOnlyData,
send_data_addr,
send_data_len.cast_signed(),
)?;
let recv_buffer = SyscallBuffer::new_in_current_process(
kernel,
MemoryRegionType::ReadWriteData,
recv_data_addr,
recv_data_len.cast_signed(),
)?;
let ret = object.channel_async_transact(kernel, send_buffer, recv_buffer);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: channel_async_transact complete");
ret
}
fn handle_channel_async_transact_complete<'a, K: Kernel>(
kernel: K,
mut args: K::SyscallArgs<'a>,
) -> Result<u64> {
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: handling channel_async_transact_complete"
);
let handle = args.next_u32()?;
let object = lookup_handle(kernel, handle)?;
let ret = object.channel_async_transact_complete(kernel);
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: channel_async_transact_complete complete"
);
ret.map(|v| v.cast_into())
}
fn handle_channel_async_cancel<'a, K: Kernel>(
kernel: K,
mut args: K::SyscallArgs<'a>,
) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling channel_async_cancel");
let handle = args.next_u32()?;
let object = lookup_handle(kernel, handle)?;
let ret = object.channel_async_cancel(kernel);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: channel_async_cancel complete");
ret
}
fn handle_channel_read<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<u64> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling channel_read");
let handle = args.next_u32()?;
let offset = args.next_usize()?;
let buffer_addr = args.next_usize()?;
let buffer_len = args.next_usize()?;
let object = lookup_handle(kernel, handle)?;
let buffer = SyscallBuffer::new_in_current_process(
kernel,
MemoryRegionType::ReadWriteData,
buffer_addr,
buffer_len.cast_signed(),
)?;
let ret = object.channel_read(kernel, offset, buffer);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: channel_read complete");
ret.map(|v| v.cast_into())
}
fn handle_channel_respond<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling channel_respond");
let handle = args.next_u32()?;
let buffer_addr = args.next_usize()?;
let buffer_len = args.next_usize()?;
let object = lookup_handle(kernel, handle)?;
let buffer = SyscallBuffer::new_in_current_process(
kernel,
MemoryRegionType::ReadOnlyData,
buffer_addr,
buffer_len.cast_signed(),
)?;
let ret = object.channel_respond(kernel, buffer);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: channel_respond complete");
ret
}
fn handle_interrupt_ack<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling interrupt_ack");
let handle = args.next_u32()?;
let signals = args.next_u32()?;
let Some(signal_mask) = Signals::from_bits(signals) else {
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: InterruptAck invalid signal mask: {:#010x}",
signals as u32
);
return Err(Error::InvalidArgument);
};
let object = lookup_handle(kernel, handle)?;
let ret = object.interrupt_ack(kernel, signal_mask);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: interrupt_ack complete");
ret
}
fn handle_thread_start<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling thread_start");
let handle = args.next_u32()?;
let initial_pc = args.next_usize()?;
let initial_sp = args.next_usize()?;
let object = lookup_handle(kernel, handle)?;
object.thread_start(kernel, initial_pc, initial_sp)
}
fn handle_task_terminate<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling task_terminate");
let handle = args.next_u32()?;
let object = lookup_handle(kernel, handle)?;
object.task_terminate(kernel)
}
fn handle_task_join<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<ExitStatus> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling task_join");
let handle = args.next_u32()?;
let object = lookup_handle(kernel, handle)?;
object.task_join(kernel)
}
fn handle_thread_exit<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> ! {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling thread_exit");
// TODO: b/510812835 - infallible syscalls.
let exit_code = args.next_u32().unwrap_or(0);
crate::scheduler::exit_thread(kernel, syscall_defs::ExitStatus::Success(exit_code));
}
fn handle_process_exit<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> ! {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling process_exit");
// TODO: b/510812835 - infallible syscalls.
let exit_code = args.next_u32().unwrap_or(0);
let mut sched = kernel.get_scheduler().lock(kernel);
let current_process = sched.current_process_handle().clone();
sched.process_terminate(
kernel,
&current_process,
syscall_defs::ExitStatus::Success(exit_code),
);
// Drop the reference to the current process before calling exit_thread.
// Since exit_thread does not return, the reference would otherwise be leaked,
// preventing the process from being successfully joined (which requires ref_count == 1).
drop(current_process);
// Release the scheduler lock to allow preemption.
drop(sched);
// Now exit the current thread as well.
crate::scheduler::exit_thread(kernel, syscall_defs::ExitStatus::ProcessTerminated);
}
fn handle_process_start<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling process_start");
let handle = args.next_u32()?;
let object = lookup_handle(kernel, handle)?;
object.process_start(kernel)
}
fn handle_set_peer_user_signal<'a, K: Kernel>(
kernel: K,
mut args: K::SyscallArgs<'a>,
) -> Result<()> {
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: handling object_set_peer_user_signal"
);
let handle = args.next_u32()?;
let set = args.next_u32()? != 0;
let object = lookup_handle(kernel, handle)?;
let ret = object.object_set_peer_user_signal(kernel, set);
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: object_set_peer_user_signal complete"
);
ret
}
// TODO: Remove this syscall when logging is added.
fn handle_debug_putc<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<u64> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling debug_putc");
let arg = args.next_u32()?;
let c = char::from_u32(arg).ok_or(Error::InvalidArgument)?;
let sched = kernel.get_scheduler().lock(kernel);
info!("{}: {}", sched.current_thread().name as &str, c as char);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: debug_putc complete");
Ok(u64::from(arg))
}
// TODO: Consider adding an feature flagged PowerManager object and move this shutdown call to it.
fn handle_debug_shutdown<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<u64> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling debug_shutdown");
let exit_code = args.next_u32()?;
if exit_code != 0 {
// Allow `kernel` to be unused if `system_dump_on_failure` is not enabled.
let _ = kernel;
#[cfg(feature = "system_dump_on_failure")]
{
pw_log::info!(
"Kernel exiting with failure code {}. Dumping system state:",
exit_code as i32
);
kernel.get_scheduler().lock(kernel).dump(kernel);
}
}
crate::target::shutdown(exit_code);
}
fn handle_debug_log<'a, K: Kernel>(kernel: K, mut args: K::SyscallArgs<'a>) -> Result<()> {
let buffer_addr = args.next_usize()?;
let buffer_len = args.next_usize()?;
let buffer = SyscallBuffer::new_in_current_process(
kernel,
MemoryRegionType::ReadOnlyData,
buffer_addr,
buffer_len.cast_signed(),
)?;
let mut console = console::Console::new();
for byteslice in buffer.as_slices() {
console.write_all(byteslice)?;
}
Ok(())
}
fn handle_debug_trigger_interrupt<'a, K: Kernel>(
_kernel: K,
mut args: K::SyscallArgs<'a>,
) -> Result<()> {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: handling debug_trigger_interrupt");
let irq = args.next_u32()?;
K::InterruptController::trigger_interrupt(irq);
log_if::debug_if!(SYSCALL_DEBUG, "syscall: debug_trigger_interrupt complete");
Ok(())
}
fn handle_debug_clock_now<'a, K: Kernel>(kernel: K, mut _args: K::SyscallArgs<'a>) -> u64 {
kernel.now().ticks()
}
/// Handle a system call.
///
/// This is the architecture-independent entry point for system calls.
///
/// # Requirements
/// The architecture-specific code calling this function MUST check if the
/// current thread is terminating before returning to userspace (e.g., by
/// calling `kernel::interrupt_controller::handle_thread_termination`).
/// Since in-kernel thread termination is advisory, this ensures that
/// terminating threads actually exit instead of returning to userspace.
pub fn handle_syscall<'a, K: Kernel>(
kernel: K,
id: u16,
args: K::SyscallArgs<'a>,
) -> SysCallReturnValue {
log_if::debug_if!(SYSCALL_DEBUG, "syscall: {:#06x}", id as usize);
// Instead of having a architecture independent match here, an array of
// extern "C" function pointers could be kept and use the architecture's
// calling convention to directly call them.
//
// This allows [`arch::arm_cortex_m::in_interrupt_handler()`] to treat
// active SVCalls as not in interrupt context.
let id = match SysCallId::try_from(id) {
Ok(id) => id,
Err(_) => {
log_if::debug_if!(
SYSCALL_DEBUG,
"syscall: unknown syscall {}",
u32::from(id) as u32
);
return SysCallReturnValue::from(-(Error::InvalidArgument as isize) as i64);
}
};
let res = {
match id {
SysCallId::ObjectWait => handle_object_wait(kernel, args).into(),
SysCallId::WaitGroupAdd => handle_wait_group_add(kernel, args).into(),
SysCallId::WaitGroupRemove => handle_wait_group_remove(kernel, args).into(),
SysCallId::ChannelTransact => handle_channel_transact(kernel, args).into(),
SysCallId::ChannelAsyncTransact => handle_channel_async_transact(kernel, args).into(),
SysCallId::ChannelAsyncTransactComplete => {
handle_channel_async_transact_complete(kernel, args).into()
}
SysCallId::ChannelAsyncCancel => handle_channel_async_cancel(kernel, args).into(),
SysCallId::ChannelRead => handle_channel_read(kernel, args).into(),
SysCallId::ChannelRespond => handle_channel_respond(kernel, args).into(),
SysCallId::InterruptAck => handle_interrupt_ack(kernel, args).into(),
SysCallId::ThreadStart => handle_thread_start(kernel, args).into(),
SysCallId::TaskTerminate => handle_task_terminate(kernel, args).into(),
SysCallId::TaskJoin => handle_task_join(kernel, args).into(),
SysCallId::ThreadExit => handle_thread_exit(kernel, args),
SysCallId::ProcessStart => handle_process_start(kernel, args).into(),
SysCallId::ProcessExit => handle_process_exit(kernel, args),
SysCallId::RaisePeerUserSignal => handle_set_peer_user_signal(kernel, args).into(),
SysCallId::DebugPutc => handle_debug_putc(kernel, args).into(),
SysCallId::DebugShutdown => handle_debug_shutdown(kernel, args).into(),
SysCallId::DebugLog => handle_debug_log(kernel, args).into(),
SysCallId::DebugNop => 0u64.into(),
SysCallId::DebugTriggerInterrupt => handle_debug_trigger_interrupt(kernel, args).into(),
SysCallId::DebugClockNow => handle_debug_clock_now(kernel, args).into(),
SysCallId::SysCallLowerBound
| SysCallId::SysCallUpperBound
| SysCallId::DebugSysCallLowerBound
| SysCallId::DebugSysCallUpperBound => {
pw_assert::panic!("Marker variants are unreachable");
}
}
};
log_if::debug_if!(SYSCALL_DEBUG, "syscall: {:#06x} returning", id as usize);
res
}