use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use crate::sidecar::layout::{InterruptLevels, Version};
use crate::sidecar::message::{DecodeError, SidecarMessage, StopReason};
use crate::sidecar::turn::WaitError;
use crate::soc::Width;
use crate::time::VirtualNanos;
pub trait CpuBackend {
fn run_until(
&mut self,
deadline: VirtualNanos,
on_access: &mut dyn FnMut(Access, &InterruptLevels) -> Reply,
) -> Result<Stop, BackendError>;
fn interrupts(&self) -> &InterruptLevels;
fn shutdown(self) -> Result<(), BackendError>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Phase {
Handshake,
Run,
Shutdown,
}
impl core::fmt::Display for Phase {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(match self {
Self::Handshake => "waiting for Hello",
Self::Run => "waiting for the next access or the stop",
Self::Shutdown => "shutting down",
})
}
}
#[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 struct Reply {
pub value: u64,
pub next_deadline: VirtualNanos,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Stop {
pub at: VirtualNanos,
pub instruction_count: u64,
pub reason: StopReason,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum BackendError {
#[error("no sidecar binary: {}", describe_search(explicit.as_deref(), on_path))]
NoSidecar {
explicit: Option<PathBuf>,
on_path: &'static str,
},
#[error("spawning {binary}: {source}", binary = binary.display())]
Spawn {
binary: PathBuf,
#[source]
source: std::io::Error,
},
#[error("shared file {path}: {source}", path = path.display())]
Files {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("the sidecar speaks protocol {sidecar}, this side expects {expected}")]
Version {
sidecar: Version,
expected: Version,
},
#[error(transparent)]
Peer(#[from] WaitError),
#[error("the run was cancelled")]
Cancelled,
#[error(transparent)]
Decode(#[from] DecodeError),
#[error("the sidecar sent {message:?} where it was not expected")]
Unexpected {
message: SidecarMessage,
},
#[error("the sidecar exited with {status} while {during}")]
Died {
status: ExitStatus,
during: Phase,
},
#[error("the sidecar process: {0}")]
Process(#[source] std::io::Error),
#[error("the sidecar ignored Shutdown past the grace period and was killed")]
ShutdownTimedOut,
#[error("the sidecar has already been shut down")]
ShutDown,
}
const SIDECAR_RELEASES: &str = "https://gitlab.com/rigless-labs/qemu/-/releases";
fn describe_search(explicit: Option<&Path>, on_path: &str) -> String {
explicit.map_or_else(
|| format!("no {on_path} on PATH; it is the package published at {SIDECAR_RELEASES}"),
|explicit| format!("the explicit path {} is not a file", explicit.display()),
)
}