use std::fmt;
use std::path::Path;
use crate::device::Level;
use crate::machine::Board;
use crate::session::Session;
use crate::soc::Width;
use crate::time::VirtualNanos;
#[cfg(not(emulator_absent))]
mod qemu;
#[cfg(not(emulator_absent))]
pub use qemu::run;
#[cfg(emulator_absent)]
pub fn run<D, R>(
_session: Session<D>,
_start: &Start<'_>,
_driver: R,
) -> Result<Finished<D, R>, Error>
where
D: Board + ?Sized + 'static,
R: Driver<D> + 'static,
{
Err(Error::NoEmulator)
}
pub const INTERRUPT_LINES: u16 = 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InterruptLine(u16);
impl InterruptLine {
#[must_use]
pub const fn new(index: u16) -> Option<Self> {
if index < INTERRUPT_LINES {
Some(Self(index))
} else {
None
}
}
#[must_use]
pub const fn index(self) -> u16 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AccessKind {
Read,
Write,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Access {
pub kind: AccessKind,
pub address: u64,
pub width: Width,
pub value: u64,
pub instruction_count: u64,
pub virtual_time: VirtualNanos,
pub program_counter: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum StopReason {
Deadline,
Idle,
Reset,
}
impl StopReason {
#[must_use]
pub const fn from_code(code: u32) -> Option<Self> {
match code {
0 => Some(Self::Deadline),
1 => Some(Self::Idle),
2 => Some(Self::Reset),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Stop {
pub at: VirtualNanos,
pub instruction_count: u64,
pub reason: StopReason,
}
pub trait Memory {
fn read(&mut self, address: u64, width: Width) -> Result<u64, Error>;
fn write(&mut self, address: u64, width: Width, value: u64) -> Result<(), Error>;
}
pub trait Host: Memory {
fn set_interrupt(&mut self, line: InterruptLine, level: Level);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EmulatorCommand {
pub machine: &'static str,
pub cpu: &'static str,
pub properties: Vec<Property>,
pub icount_shift: u32,
pub aliases: Vec<Alias>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Property {
pub name: &'static str,
pub value: String,
}
impl Property {
pub fn new(name: &'static str, value: impl fmt::Display) -> Self {
Self {
name,
value: value.to_string(),
}
}
}
impl fmt::Display for Property {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}={}", self.name, self.value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Alias {
pub region: &'static str,
pub at: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Start<'a> {
pub firmware: &'a Path,
pub debugger_port: Option<u16>,
pub wait_for_debugger: bool,
}
pub trait Driver<D: Board + ?Sized>: Send {
fn on_access(&mut self, access: &Access, value: u64) {
let _ = (access, value);
}
fn on_stop(&mut self, session: &mut Session<D>, stop: Stop) -> Option<VirtualNanos>;
}
#[derive(Debug)]
pub struct Finished<D: Board + ?Sized, R> {
pub session: Session<D>,
pub driver: R,
pub stop: Option<Stop>,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("{machine} has no target {target}; it has {count}")]
NoTarget {
machine: &'static str,
target: usize,
count: usize,
},
#[error("guest memory has nothing at {address:#x} for a {} bit access", width.bits())]
Memory {
address: u64,
width: Width,
},
#[error("the emulator has already run in this process; it runs once")]
AlreadyRan,
#[error("this build has no emulator; only docs.rs and Miri build the crate without one")]
NoEmulator,
#[error("the emulator's argument {0:?} is not a C string")]
Argument(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_property_renders_as_the_machine_spells_it() {
assert_eq!(Property::new("num-irq", 91u16).to_string(), "num-irq=91");
}
#[test]
fn a_line_exists_below_the_ceiling_and_not_at_it() {
assert_eq!(InterruptLine::new(0).map(InterruptLine::index), Some(0));
assert!(InterruptLine::new(INTERRUPT_LINES - 1).is_some());
assert!(InterruptLine::new(INTERRUPT_LINES).is_none());
}
#[test]
fn every_reason_the_emulator_names_decodes_and_nothing_else_does() {
assert_eq!(StopReason::from_code(0), Some(StopReason::Deadline));
assert_eq!(StopReason::from_code(1), Some(StopReason::Idle));
assert_eq!(StopReason::from_code(2), Some(StopReason::Reset));
assert_eq!(StopReason::from_code(3), None);
}
}