use crate::core::sched::{Budget, Consumed};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ExitReason(pub u16);
impl ExitReason {
pub const NONE: ExitReason = ExitReason(0);
pub const SYSCALL: ExitReason = ExitReason(1);
pub const BREAKPOINT: ExitReason = ExitReason(2);
pub const FAULT: ExitReason = ExitReason(3);
pub const HALT: ExitReason = ExitReason(4);
pub const MMIO: ExitReason = ExitReason(5);
pub const SHUTDOWN: ExitReason = ExitReason(6);
pub const INTERNAL: ExitReason = ExitReason(7);
#[must_use]
pub const fn is_maskable(self) -> bool {
self.0 >= 1 && self.0 < 32
}
#[must_use]
pub const fn name(self) -> Option<&'static str> {
match self.0 {
0 => Some("none"),
1 => Some("syscall"),
2 => Some("breakpoint"),
3 => Some("fault"),
4 => Some("halt"),
5 => Some("mmio"),
6 => Some("shutdown"),
7 => Some("internal"),
_ => None,
}
}
}
impl core::fmt::Display for ExitReason {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.name() {
Some(name) => f.write_str(name),
None => write!(f, "exit reason #{}", self.0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Access {
#[default]
None,
Read,
Write,
Execute,
}
impl Access {
#[must_use]
pub const fn is_write(self) -> bool {
matches!(self, Access::Write)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(transparent)]
pub struct ExitMask(u32);
impl ExitMask {
pub const NONE: ExitMask = ExitMask(0);
pub const USER: ExitMask = ExitMask::NONE
.with(ExitReason::SYSCALL)
.with(ExitReason::FAULT);
#[must_use]
pub const fn bits(self) -> u32 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u32) -> ExitMask {
ExitMask(bits)
}
#[must_use]
pub const fn with(self, reason: ExitReason) -> ExitMask {
if reason.is_maskable() {
ExitMask(self.0 | (1 << reason.0))
} else {
self
}
}
#[must_use]
pub const fn without(self, reason: ExitReason) -> ExitMask {
if reason.is_maskable() {
ExitMask(self.0 & !(1 << reason.0))
} else {
self
}
}
#[must_use]
pub const fn contains(self, reason: ExitReason) -> bool {
reason.is_maskable() && self.0 & (1 << reason.0) != 0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Exit {
pub reason: ExitReason,
pub pc: u64,
pub len: u8,
pub access: Access,
pub detail: u64,
pub address: u64,
}
impl Exit {
#[must_use]
pub const fn new(reason: ExitReason, pc: u64, len: u8) -> Exit {
Exit {
reason,
pc,
len,
access: Access::None,
detail: 0,
address: 0,
}
}
#[must_use]
pub const fn with_detail(mut self, detail: u64) -> Exit {
self.detail = detail;
self
}
#[must_use]
pub const fn with_access(mut self, address: u64, access: Access) -> Exit {
self.address = address;
self.access = access;
self
}
#[must_use]
pub const fn resume_pc(&self) -> u64 {
if self.reason.0 == ExitReason::SYSCALL.0 {
self.pc.wrapping_add(self.len as u64)
} else {
self.pc
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Run {
pub consumed: Consumed,
pub exit: Option<Exit>,
}
impl Run {
#[must_use]
pub const fn completed(consumed: Consumed) -> Run {
Run {
consumed,
exit: None,
}
}
#[must_use]
pub const fn exited(consumed: Consumed, exit: Exit) -> Run {
Run {
consumed,
exit: Some(exit),
}
}
}
pub trait ExitingCore: Send + Sync + core::fmt::Debug {
fn exit_mask(&self) -> ExitMask;
fn set_exit_mask(&self, mask: ExitMask);
fn run_to_exit(&self, budget: Budget) -> Run;
fn pc(&self) -> u64;
fn set_pc(&self, pc: u64);
fn sp(&self) -> u64;
fn set_sp(&self, sp: u64);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_mask_holds_the_reasons_put_in_it() {
let mask = ExitMask::NONE
.with(ExitReason::SYSCALL)
.with(ExitReason::BREAKPOINT);
assert!(mask.contains(ExitReason::SYSCALL));
assert!(mask.contains(ExitReason::BREAKPOINT));
assert!(!mask.contains(ExitReason::FAULT));
assert!(!mask.is_empty());
let mask = mask.without(ExitReason::SYSCALL);
assert!(!mask.contains(ExitReason::SYSCALL));
assert!(mask.contains(ExitReason::BREAKPOINT));
}
#[test]
fn the_user_mask_is_syscalls_and_faults() {
assert!(ExitMask::USER.contains(ExitReason::SYSCALL));
assert!(ExitMask::USER.contains(ExitReason::FAULT));
assert!(!ExitMask::USER.contains(ExitReason::BREAKPOINT));
}
#[test]
fn an_empty_mask_is_the_default() {
assert_eq!(ExitMask::default(), ExitMask::NONE);
assert!(ExitMask::default().is_empty());
assert!(!ExitMask::NONE.contains(ExitReason::SYSCALL));
}
#[test]
fn reason_zero_is_never_a_mask_bit() {
assert!(!ExitReason::NONE.is_maskable());
assert_eq!(ExitMask::NONE.with(ExitReason::NONE), ExitMask::NONE);
assert!(!ExitMask::from_bits(u32::MAX).contains(ExitReason::NONE));
}
#[test]
fn an_unknown_reason_survives_a_mask_round_trip() {
let bits = ExitMask::USER.bits() | (1 << 20);
assert_eq!(ExitMask::from_bits(bits).bits(), bits);
assert!(ExitMask::from_bits(bits).contains(ExitReason(20)));
}
#[test]
fn reasons_above_the_mask_width_are_not_maskable() {
assert!(!ExitReason(32).is_maskable());
assert!(!ExitReason(u16::MAX).is_maskable());
assert_eq!(ExitMask::NONE.with(ExitReason(40)), ExitMask::NONE);
}
#[test]
fn a_reason_names_itself_or_admits_it_cannot() {
assert_eq!(ExitReason::SYSCALL.name(), Some("syscall"));
assert_eq!(ExitReason(999).name(), None);
assert_eq!(alloc::format!("{}", ExitReason::FAULT), "fault");
assert_eq!(alloc::format!("{}", ExitReason(999)), "exit reason #999");
}
#[test]
fn an_exit_carries_its_detail_address_and_access() {
let exit = Exit::new(ExitReason::FAULT, 0x1000, 4)
.with_detail(13)
.with_access(0xdead_beef, Access::Write);
assert_eq!(exit.reason, ExitReason::FAULT);
assert_eq!(exit.pc, 0x1000);
assert_eq!(exit.len, 4);
assert_eq!(exit.detail, 13);
assert_eq!(exit.address, 0xdead_beef);
assert!(exit.access.is_write());
assert!(!Access::Read.is_write());
assert_eq!(Access::default(), Access::None);
}
#[test]
fn only_a_syscall_resumes_past_its_instruction() {
assert_eq!(
Exit::new(ExitReason::SYSCALL, 0x1000, 4).resume_pc(),
0x1004
);
assert_eq!(Exit::new(ExitReason::FAULT, 0x1000, 4).resume_pc(), 0x1000);
assert_eq!(
Exit::new(ExitReason::BREAKPOINT, 0x1000, 2).resume_pc(),
0x1000
);
}
#[test]
fn a_completed_run_has_no_exit() {
let run = Run::completed(Consumed::new(7));
assert_eq!(run.consumed.ticks, 7);
assert!(run.exit.is_none());
let run = Run::exited(Consumed::new(3), Exit::new(ExitReason::SYSCALL, 0x20, 4));
assert_eq!(run.consumed.ticks, 3);
assert_eq!(run.exit.unwrap().reason, ExitReason::SYSCALL);
}
}