blob: 3ffa040e15a49364468ec0682606d3ede68cbcbc [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 kernel::SyscallArgs;
use kernel::syscall::handle_syscall;
use pw_cast::CastInto as _;
use pw_status::{Error, Result};
use crate::exceptions::TrapFrame;
pub struct RiscVSyscallArgs<'a> {
frame: &'a TrapFrame,
cur_index: usize,
}
impl<'a> RiscVSyscallArgs<'a> {
fn new(frame: &'a TrapFrame) -> Self {
Self {
frame,
cur_index: 0,
}
}
}
impl<'a> SyscallArgs<'a> for RiscVSyscallArgs<'a> {
fn next_usize(&mut self) -> Result<usize> {
let value = self.frame.a(self.cur_index)?;
self.cur_index += 1;
Ok(value)
}
#[inline(always)]
fn next_u64(&mut self) -> Result<u64> {
// Note: This follows the PSABI calling convention[1] which differs from
// the outdated (but still returned in search results) ABI from the ISA[2].
// The PSABI is what both gcc and llvm based toolchains implement.
//
// 1: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/main/riscv-cc.adoc#integer-calling-convention
// 2: https://riscv.org/wp-content/uploads/2024/12/riscv-calling.pdf
let low: u64 = self.next_usize()?.cast_into();
let high: u64 = self.next_usize()?.cast_into();
Ok(low | high << 32)
}
}
// Pulls arguments out of the trap frame and calls the arch-independent syscall
// handler.
pub fn handle_ecall(frame: &mut TrapFrame) {
// Explicitly truncate the syscall ID to 16 bits per syscall ABI.
#[expect(clippy::cast_possible_truncation)]
let id = frame.t0 as u16;
let args = RiscVSyscallArgs::new(frame);
let ret_val = handle_syscall(super::Arch, id, args);
frame.a0 = ret_val.value[0];
frame.a1 = ret_val.value[1];
// Check for termination before returning to userspace.
kernel::interrupt_controller::handle_thread_termination(super::Arch, true);
// ECALL exceptions do not "retire the instruction" requiring the advancing
// of the PC past the ECALL instruction. ECALLs are encoded as 4 byte
// instructions.
//
// Use a wrapping add, as section 1.4 of the RISC-V unprivileged spec states:
// "...memory address computations done by the hardware ignore overflow
// and instead wrap around modulo 2^XLEN"
frame.epc = frame.epc.wrapping_add(4);
}
impl TrapFrame {
pub fn a(&self, index: usize) -> Result<usize> {
if index > 7 {
return Err(Error::InvalidArgument);
}
// Pointer math is used here instead of a match statement as it results
// in significantly smaller code.
//
// SAFETY: Index is range checked above and the a* fields are in consecutive
// positions in the `TrapFrame` struct.
let usize0 = &raw const self.a0;
Ok(unsafe { *usize0.byte_add(index * 4) })
}
}