//! TurkOS EL0 syscall ABI v1: x7=version, x8=number, x0..x5=arguments. //! The sole return register is x0; negative errno values use two's complement. pub const VERSION: u64 = 1; pub const ABI_QUERY: u64 = 0; pub const ECHO_SIX: u64 = 1; pub const EXIT: u64 = 2; pub const READ_USER: u64 = 3; pub const WRITE_USER: u64 = 4; pub const SERIAL_WRITE: u64 = 5; pub const YIELD: u64 = 6; pub const CLOCK_READ: u64 = 7; pub const HANDLE_OPEN: u64 = 8; pub const HANDLE_QUERY: u64 = 9; pub const HANDLE_CLOSE: u64 = 10; pub const MMIO_MAP: u64 = 11; pub const IPC_SEND: u64 = 12; pub const IPC_INSPECT: u64 = 13; pub const HANDLE_TRANSFER: u64 = 14; pub const IPC_RECEIVE: u64 = 15; pub const WAIT_CHILD: u64 = 16; pub const EPERM: i64 = -1; pub const ENOSYS: i64 = -38; pub const EINVAL: i64 = -22; pub const EBADF: i64 = -9; pub const EMFILE: i64 = -24; pub const ECHILD: i64 = -10; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Call { pub version: u64, pub number: u64, pub args: [u64; 6], } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Outcome { Return(i64), Exit(u64), Yield, ClockRead, HandleOpen(u64), HandleQuery(u64), HandleClose(u64), } pub fn dispatch(call: Call) -> Outcome { if call.version != VERSION { return Outcome::Return(EINVAL); } match call.number { ABI_QUERY => Outcome::Return(VERSION as i64), ECHO_SIX => Outcome::Return( call.args .iter() .fold(0u64, |sum, arg| sum.wrapping_add(*arg)) as i64, ), EXIT => Outcome::Exit(call.args[0]), YIELD => Outcome::Yield, CLOCK_READ => Outcome::ClockRead, HANDLE_OPEN => Outcome::HandleOpen(call.args[0]), HANDLE_QUERY => Outcome::HandleQuery(call.args[0]), HANDLE_CLOSE => Outcome::HandleClose(call.args[0]), // No EL0 device capability or user MMIO mapping exists. The argument // is an opaque device ID, never a physical address or returned VA. MMIO_MAP => Outcome::Return(EPERM), _ => Outcome::Return(ENOSYS), } } #[cfg(test)] mod tests { use super::*; #[test] fn version_and_number_table() { let call = Call { version: VERSION, number: ABI_QUERY, args: [0; 6], }; assert_eq!(dispatch(call), Outcome::Return(1)); assert_eq!( dispatch(Call { version: 0, ..call }), Outcome::Return(EINVAL) ); assert_eq!( dispatch(Call { number: 0xffff, ..call }), Outcome::Return(ENOSYS) ); assert_eq!( dispatch(Call { version: 2, number: EXIT, ..call }), Outcome::Return(EINVAL) ); assert_eq!( dispatch(Call { version: 2, number: CLOCK_READ, ..call }), Outcome::Return(EINVAL) ); } #[test] fn six_args_and_signed_return() { let call = Call { version: VERSION, number: ECHO_SIX, args: [1, 2, 3, 4, 5, 6], }; assert_eq!(dispatch(call), Outcome::Return(21)); assert_eq!(ENOSYS as u64, 0xffff_ffff_ffff_ffda); assert_eq!(EINVAL as u64, 0xffff_ffff_ffff_ffea); assert_eq!( dispatch(Call { number: EXIT, ..call }), Outcome::Exit(1) ); assert_eq!( dispatch(Call { number: YIELD, ..call }), Outcome::Yield ); assert_eq!( dispatch(Call { number: CLOCK_READ, ..call }), Outcome::ClockRead ); assert_eq!( dispatch(Call { number: HANDLE_OPEN, ..call }), Outcome::HandleOpen(1) ); assert_eq!( dispatch(Call { number: HANDLE_QUERY, ..call }), Outcome::HandleQuery(1) ); assert_eq!( dispatch(Call { number: HANDLE_CLOSE, ..call }), Outcome::HandleClose(1) ); } #[test] fn mmio_mapping_request_denies_all_unprivileged_device_ids() { for device in [0, 1, 2, 0xffff, 0x0900_0000] { let call = Call { version: VERSION, number: MMIO_MAP, args: [device, 0, 0, 0, 0, 0], }; assert_eq!(dispatch(call), Outcome::Return(EPERM)); assert_eq!( dispatch(Call { version: 2, ..call }), Outcome::Return(EINVAL) ); } } }