#![no_std] //! Typed user-side calls for the separate v2 runtime ABI. use core::{num::NonZeroU64, ptr::NonNull}; use turkos_os_syscall_abi::syscall_abi::{AbiResult, DecodeError, Errno, ABI_VERSION, CALL_VERSION}; use turkos_os_user_runtime_abi::{ALLOC, EXIT, FREE, VERSION, decode}; pub trait Transport { fn call(&mut self, version: u64, number: u64, arguments: [u64; 6]) -> u64; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Kernel(Errno), Wire(DecodeError), InvalidReturn, UnexpectedExitReturn, } pub struct Allocation { address: NonZeroU64, bytes: usize, alignment: usize, } impl Allocation { pub const fn address(&self) -> u64 { self.address.get() } pub const fn bytes(&self) -> usize { self.bytes } pub const fn alignment(&self) -> usize { self.alignment } /// The kernel owns the mapping. The caller must ensure no aliases access /// it during or after a successful free. pub unsafe fn as_mut_ptr(&mut self) -> NonNull { unsafe { NonNull::new_unchecked(self.address.get() as *mut u8) } } } pub struct Client { transport: T } impl Client { pub const fn new(transport: T) -> Self { Self { transport } } pub fn into_transport(self) -> T { self.transport } fn result(&mut self, version: u64, number: u64, arguments: [u64; 6]) -> Result { match AbiResult::decode(self.transport.call(version, number, arguments)) .map_err(Error::Wire)? { AbiResult::Ok(value) => Ok(value.value()), AbiResult::Err(error) => Err(Error::Kernel(error)), } } pub fn version(&mut self) -> Result { self.result(ABI_VERSION, CALL_VERSION, [0; 6]) } pub fn alloc(&mut self, bytes: usize, alignment: usize) -> Result { let arguments = [bytes as u64, alignment as u64, 0, 0, 0, 0]; decode(VERSION, ALLOC, arguments).map_err(Error::Kernel)?; let address = self.result(VERSION, ALLOC, arguments)?; let address = NonZeroU64::new(address).ok_or(Error::InvalidReturn)?; if address.get() % alignment as u64 != 0 { return Err(Error::InvalidReturn); } Ok(Allocation {address, bytes, alignment}) } /// A failed free returns its allocation so the caller retains ownership. pub fn free(&mut self, allocation: Allocation) -> Result<(), (Error, Allocation)> { let address = allocation.address.get(); match self.result(VERSION, FREE, [address, 0, 0, 0, 0, 0]) { Ok(0) => Ok(()), Ok(_) => Err((Error::InvalidReturn, allocation)), Err(error) => Err((error, allocation)), } } /// Exit is expected to retire the current process and never return. pub fn exit(&mut self, code: i32) -> Error { let _ = self.transport.call(VERSION, EXIT, [code as i64 as u64, 0, 0, 0, 0, 0]); Error::UnexpectedExitReturn } } #[cfg(all(target_arch = "aarch64", target_os = "none"))] pub struct Native; #[cfg(all(target_arch = "aarch64", target_os = "none"))] impl Transport for Native { #[inline(always)] fn call(&mut self, version: u64, number: u64, arguments: [u64; 6]) -> u64 { let mut result = arguments[0]; unsafe { core::arch::asm!("svc #0", inout("x0") result, in("x1") arguments[1], in("x2") arguments[2], in("x3") arguments[3], in("x4") arguments[4], in("x5") arguments[5], in("x7") version, in("x8") number, options(nostack)); } result } }