pub mod disasm;
mod exec;
pub mod isa;
pub mod paging;
pub mod prot;
#[cfg(test)]
mod tests;
#[cfg(all(test, feature = "std"))]
mod firmware;
#[cfg(all(test, feature = "std"))]
mod conformance;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{
Device, DeviceClass, Initiator, PropertySpec, RealizeCtx, ResetKind, SinkPin,
};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::registry::Registry;
use crate::core::sched::{Budget, Consumed};
use crate::core::space::{AddressSpace, MemAttrs, RequesterId};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{self, AtomicBool, AtomicU32, LockRank, Ordering};
use crate::core::value::Width;
use crate::core::wire::{
FanIn, IntAck, IntAckCycle, IntAckHandlers, IntAckResponse, Level, Resolve, WireId, WireSink,
};
use exec::{Exec, State};
pub mod flags {
pub const CF: u32 = 0x0001;
pub const PF: u32 = 0x0004;
pub const AF: u32 = 0x0010;
pub const ZF: u32 = 0x0040;
pub const SF: u32 = 0x0080;
pub const TF: u32 = 0x0100;
pub const IF: u32 = 0x0200;
pub const DF: u32 = 0x0400;
pub const OF: u32 = 0x0800;
pub const IOPL: u32 = 0x3000;
pub const IOPL_SHIFT: u32 = 12;
pub const NT: u32 = 0x4000;
pub const RF: u32 = 0x0001_0000;
pub const VM: u32 = 0x0002_0000;
pub const AC: u32 = 0x0004_0000;
pub const DEFINED: u32 = CF | PF | AF | ZF | SF | TF | IF | DF | OF;
pub const RESERVED_SET: u32 = 0xf002;
pub const DEFINED_386: u32 = DEFINED | IOPL | NT | RF | VM;
pub const DEFINED_486: u32 = DEFINED_386 | AC;
pub const ALWAYS_SET: u32 = 0x0002;
pub const POPF_FORBIDDEN: u32 = VM | RF;
pub const LOW_BYTE: u32 = CF | PF | AF | ZF | SF;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Variant {
I8086,
I8088,
I80386,
I80486,
}
impl Variant {
#[must_use]
pub const fn queue_bytes(self) -> u8 {
match self {
Variant::I8086 => 6,
Variant::I8088 => 4,
Variant::I80386 | Variant::I80486 => 16,
}
}
#[must_use]
pub const fn bus_bytes(self) -> u8 {
match self {
Variant::I8086 => 2,
Variant::I8088 => 1,
Variant::I80386 | Variant::I80486 => 4,
}
}
#[must_use]
pub const fn bus_clocks(self) -> u32 {
match self {
Variant::I8086 | Variant::I8088 => 4,
Variant::I80386 | Variant::I80486 => 2,
}
}
#[must_use]
pub const fn map(self) -> isa::Gen {
match self {
Variant::I8086 | Variant::I8088 => isa::Gen::I8086,
Variant::I80386 | Variant::I80486 => isa::Gen::I386,
}
}
#[must_use]
pub const fn is_32bit(self) -> bool {
matches!(self, Variant::I80386 | Variant::I80486)
}
#[must_use]
pub const fn has_486_extras(self) -> bool {
matches!(self, Variant::I80486)
}
#[must_use]
pub const fn flag_mask(self) -> u32 {
match self {
Variant::I8086 | Variant::I8088 => flags::DEFINED,
Variant::I80386 => flags::DEFINED_386,
Variant::I80486 => flags::DEFINED_486,
}
}
#[must_use]
pub const fn flag_fixed(self) -> u32 {
match self {
Variant::I8086 | Variant::I8088 => flags::RESERVED_SET,
Variant::I80386 | Variant::I80486 => flags::ALWAYS_SET,
}
}
#[must_use]
pub const fn reset_signature(self) -> u32 {
match self {
Variant::I8086 | Variant::I8088 => 0,
Variant::I80386 => 0x0000_0308,
Variant::I80486 => 0x0000_0480,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Variant::I8086 => "8086",
Variant::I8088 => "8088",
Variant::I80386 => "80386",
Variant::I80486 => "80486",
}
}
pub const NAMES: &'static [&'static str] = &["8086", "8088", "80386", "80486"];
#[must_use]
pub fn from_name(name: &str) -> Option<Variant> {
match name {
"8086" => Some(Variant::I8086),
"8088" => Some(Variant::I8088),
"80386" | "386" | "i386" => Some(Variant::I80386),
"80486" | "486" | "i486" => Some(Variant::I80486),
_ => None,
}
}
}
impl fmt::Display for Variant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub variant: Variant,
pub requester: RequesterId,
}
impl Config {
pub const I8088: Config = Config {
variant: Variant::I8088,
requester: RequesterId::ANONYMOUS,
};
pub const I8086: Config = Config {
variant: Variant::I8086,
..Config::I8088
};
pub const I80386: Config = Config {
variant: Variant::I80386,
..Config::I8088
};
pub const I80486: Config = Config {
variant: Variant::I80486,
..Config::I8088
};
#[must_use]
pub const fn with_requester(mut self, id: RequesterId) -> Self {
self.requester = id;
self
}
#[must_use]
pub const fn with_variant(mut self, variant: Variant) -> Self {
self.variant = variant;
self
}
}
impl Default for Config {
fn default() -> Self {
Config::I8088
}
}
#[inline]
#[must_use]
pub const fn linear(segment: u16, offset: u16) -> u32 {
(((segment as u32) << 4).wrapping_add(offset as u32)) & 0xf_ffff
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Regs {
pub eax: u32,
pub ecx: u32,
pub edx: u32,
pub ebx: u32,
pub esp: u32,
pub ebp: u32,
pub esi: u32,
pub edi: u32,
pub eip: u32,
pub eflags: u32,
pub es: u16,
pub cs: u16,
pub ss: u16,
pub ds: u16,
pub fs: u16,
pub gs: u16,
}
const WORD_ORDER: [Reg; 8] = [
Reg::Ax,
Reg::Cx,
Reg::Dx,
Reg::Bx,
Reg::Sp,
Reg::Bp,
Reg::Si,
Reg::Di,
];
const DWORD_ORDER: [Reg; 8] = [
Reg::Eax,
Reg::Ecx,
Reg::Edx,
Reg::Ebx,
Reg::Esp,
Reg::Ebp,
Reg::Esi,
Reg::Edi,
];
impl Regs {
#[must_use]
pub const fn new() -> Regs {
Regs {
eax: 0,
ecx: 0,
edx: 0,
ebx: 0,
esp: 0,
ebp: 0,
esi: 0,
edi: 0,
eip: 0,
eflags: flags::RESERVED_SET,
es: 0,
cs: 0xffff,
ss: 0,
ds: 0,
fs: 0,
gs: 0,
}
}
#[inline]
#[must_use]
pub const fn normalise_flags(variant: Variant, value: u32) -> u32 {
(value & variant.flag_mask()) | variant.flag_fixed()
}
#[inline]
#[must_use]
pub const fn flag(&self, mask: u32) -> bool {
self.eflags & mask != 0
}
#[inline]
#[must_use]
pub const fn iopl(&self) -> u8 {
((self.eflags & flags::IOPL) >> flags::IOPL_SHIFT) as u8
}
#[inline]
#[must_use]
pub const fn dword(&self, index: u8) -> u32 {
match index & 7 {
0 => self.eax,
1 => self.ecx,
2 => self.edx,
3 => self.ebx,
4 => self.esp,
5 => self.ebp,
6 => self.esi,
_ => self.edi,
}
}
#[inline]
pub const fn set_dword(&mut self, index: u8, value: u32) {
match index & 7 {
0 => self.eax = value,
1 => self.ecx = value,
2 => self.edx = value,
3 => self.ebx = value,
4 => self.esp = value,
5 => self.ebp = value,
6 => self.esi = value,
_ => self.edi = value,
}
}
#[inline]
#[must_use]
pub const fn word(&self, index: u8) -> u16 {
self.dword(index) as u16
}
#[inline]
pub const fn set_word(&mut self, index: u8, value: u16) {
let merged = (self.dword(index) & 0xffff_0000) | value as u32;
self.set_dword(index, merged);
}
#[inline]
#[must_use]
pub const fn byte(&self, index: u8) -> u8 {
let word = self.word(index & 3);
if index & 4 == 0 {
word as u8
} else {
(word >> 8) as u8
}
}
#[inline]
pub const fn set_byte(&mut self, index: u8, value: u8) {
let word = self.word(index & 3);
let merged = if index & 4 == 0 {
(word & 0xff00) | value as u16
} else {
(word & 0x00ff) | ((value as u16) << 8)
};
self.set_word(index & 3, merged);
}
#[inline]
#[must_use]
pub const fn read(&self, index: u8, size: u8) -> u32 {
match size {
1 => self.byte(index) as u32,
2 => self.word(index) as u32,
_ => self.dword(index),
}
}
#[inline]
pub const fn write(&mut self, index: u8, size: u8, value: u32) {
match size {
1 => self.set_byte(index, value as u8),
2 => self.set_word(index, value as u16),
_ => self.set_dword(index, value),
}
}
#[inline]
#[must_use]
pub const fn segment(&self, index: u8) -> u16 {
match index {
0 => self.es,
1 => self.cs,
2 => self.ss,
3 => self.ds,
4 => self.fs,
5 => self.gs,
_ => 0,
}
}
#[inline]
pub const fn set_segment(&mut self, index: u8, value: u16) {
match index {
0 => self.es = value,
1 => self.cs = value,
2 => self.ss = value,
3 => self.ds = value,
4 => self.fs = value,
5 => self.gs = value,
_ => {}
}
}
}
impl Default for Regs {
fn default() -> Self {
Regs::new()
}
}
impl fmt::Display for Regs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"EAX:{:08x} EBX:{:08x} ECX:{:08x} EDX:{:08x} ESP:{:08x} EBP:{:08x} ESI:{:08x} \
EDI:{:08x} ES:{:04x} CS:{:04x} SS:{:04x} DS:{:04x} FS:{:04x} GS:{:04x} \
EIP:{:08x} F:{:08x}",
self.eax,
self.ebx,
self.ecx,
self.edx,
self.esp,
self.ebp,
self.esi,
self.edi,
self.es,
self.cs,
self.ss,
self.ds,
self.fs,
self.gs,
self.eip,
self.eflags
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Reg {
Eax,
Ecx,
Edx,
Ebx,
Esp,
Ebp,
Esi,
Edi,
Eip,
Eflags,
Es,
Cs,
Ss,
Ds,
Fs,
Gs,
Ax,
Cx,
Dx,
Bx,
Sp,
Bp,
Si,
Di,
Ip,
Flags,
}
impl Reg {
pub const ALL: &'static [Reg] = &[
Reg::Eax,
Reg::Ecx,
Reg::Edx,
Reg::Ebx,
Reg::Esp,
Reg::Ebp,
Reg::Esi,
Reg::Edi,
Reg::Eip,
Reg::Eflags,
Reg::Cs,
Reg::Ss,
Reg::Ds,
Reg::Es,
Reg::Fs,
Reg::Gs,
];
pub const NARROW: &'static [Reg] = &[
Reg::Ax,
Reg::Cx,
Reg::Dx,
Reg::Bx,
Reg::Sp,
Reg::Bp,
Reg::Si,
Reg::Di,
Reg::Ip,
Reg::Flags,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Reg::Eax => "eax",
Reg::Ecx => "ecx",
Reg::Edx => "edx",
Reg::Ebx => "ebx",
Reg::Esp => "esp",
Reg::Ebp => "ebp",
Reg::Esi => "esi",
Reg::Edi => "edi",
Reg::Eip => "eip",
Reg::Eflags => "eflags",
Reg::Es => "es",
Reg::Cs => "cs",
Reg::Ss => "ss",
Reg::Ds => "ds",
Reg::Fs => "fs",
Reg::Gs => "gs",
Reg::Ax => "ax",
Reg::Cx => "cx",
Reg::Dx => "dx",
Reg::Bx => "bx",
Reg::Sp => "sp",
Reg::Bp => "bp",
Reg::Si => "si",
Reg::Di => "di",
Reg::Ip => "ip",
Reg::Flags => "flags",
}
}
#[must_use]
pub const fn width(self) -> Width {
match self {
Reg::Eax
| Reg::Ecx
| Reg::Edx
| Reg::Ebx
| Reg::Esp
| Reg::Ebp
| Reg::Esi
| Reg::Edi
| Reg::Eip
| Reg::Eflags => Width::U32,
_ => Width::U16,
}
}
#[must_use]
pub const fn get(self, regs: &Regs) -> u32 {
match self {
Reg::Eax => regs.eax,
Reg::Ecx => regs.ecx,
Reg::Edx => regs.edx,
Reg::Ebx => regs.ebx,
Reg::Esp => regs.esp,
Reg::Ebp => regs.ebp,
Reg::Esi => regs.esi,
Reg::Edi => regs.edi,
Reg::Eip => regs.eip,
Reg::Eflags => regs.eflags,
Reg::Es => regs.es as u32,
Reg::Cs => regs.cs as u32,
Reg::Ss => regs.ss as u32,
Reg::Ds => regs.ds as u32,
Reg::Fs => regs.fs as u32,
Reg::Gs => regs.gs as u32,
Reg::Ax => regs.eax & 0xffff,
Reg::Cx => regs.ecx & 0xffff,
Reg::Dx => regs.edx & 0xffff,
Reg::Bx => regs.ebx & 0xffff,
Reg::Sp => regs.esp & 0xffff,
Reg::Bp => regs.ebp & 0xffff,
Reg::Si => regs.esi & 0xffff,
Reg::Di => regs.edi & 0xffff,
Reg::Ip => regs.eip & 0xffff,
Reg::Flags => regs.eflags & 0xffff,
}
}
pub const fn set(self, regs: &mut Regs, value: u32) {
match self {
Reg::Eax => regs.eax = value,
Reg::Ecx => regs.ecx = value,
Reg::Edx => regs.edx = value,
Reg::Ebx => regs.ebx = value,
Reg::Esp => regs.esp = value,
Reg::Ebp => regs.ebp = value,
Reg::Esi => regs.esi = value,
Reg::Edi => regs.edi = value,
Reg::Eip => regs.eip = value,
Reg::Eflags => regs.eflags = value,
Reg::Es => regs.es = value as u16,
Reg::Cs => regs.cs = value as u16,
Reg::Ss => regs.ss = value as u16,
Reg::Ds => regs.ds = value as u16,
Reg::Fs => regs.fs = value as u16,
Reg::Gs => regs.gs = value as u16,
Reg::Ax => regs.eax = (regs.eax & 0xffff_0000) | (value & 0xffff),
Reg::Cx => regs.ecx = (regs.ecx & 0xffff_0000) | (value & 0xffff),
Reg::Dx => regs.edx = (regs.edx & 0xffff_0000) | (value & 0xffff),
Reg::Bx => regs.ebx = (regs.ebx & 0xffff_0000) | (value & 0xffff),
Reg::Sp => regs.esp = (regs.esp & 0xffff_0000) | (value & 0xffff),
Reg::Bp => regs.ebp = (regs.ebp & 0xffff_0000) | (value & 0xffff),
Reg::Si => regs.esi = (regs.esi & 0xffff_0000) | (value & 0xffff),
Reg::Di => regs.edi = (regs.edi & 0xffff_0000) | (value & 0xffff),
Reg::Ip => regs.eip = (regs.eip & 0xffff_0000) | (value & 0xffff),
Reg::Flags => regs.eflags = (regs.eflags & 0xffff_0000) | (value & 0xffff),
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Reg> {
Reg::ALL
.iter()
.chain(Reg::NARROW.iter())
.copied()
.find(|r| r.name() == name)
}
#[must_use]
pub const fn from_word_index(index: u8) -> Reg {
WORD_ORDER[(index & 7) as usize]
}
#[must_use]
pub const fn from_dword_index(index: u8) -> Reg {
DWORD_ORDER[(index & 7) as usize]
}
}
impl fmt::Display for Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Interrupt {
Intr,
Nmi,
}
#[derive(Debug)]
pub(crate) struct Lines {
intr: AtomicBool,
intr_vector: AtomicU32,
nmi_level: AtomicBool,
nmi_latch: AtomicBool,
reset: AtomicBool,
a20_mask: AtomicU32,
a20_wired: AtomicBool,
acks: IntAckHandlers,
}
impl Default for Lines {
fn default() -> Lines {
Lines {
intr: AtomicBool::new(false),
intr_vector: AtomicU32::new(0),
nmi_level: AtomicBool::new(false),
nmi_latch: AtomicBool::new(false),
reset: AtomicBool::new(false),
a20_mask: AtomicU32::new(u32::MAX),
a20_wired: AtomicBool::new(false),
acks: IntAckHandlers::new(),
}
}
}
impl Lines {
fn set_intr(&self, asserted: bool) {
self.intr.store(asserted, Ordering::Release);
}
fn intr_asserted(&self) -> bool {
self.intr.load(Ordering::Acquire)
}
fn set_intr_vector(&self, vector: u8) {
self.intr_vector.store(u32::from(vector), Ordering::Release);
}
pub(crate) fn intr_vector(&self) -> u8 {
self.intr_vector.load(Ordering::Acquire) as u8
}
fn set_nmi(&self, asserted: bool) {
let previous = self.nmi_level.swap(asserted, Ordering::AcqRel);
if asserted && !previous {
self.nmi_latch.store(true, Ordering::Release);
}
}
pub(crate) fn nmi_pending(&self) -> bool {
self.nmi_latch.load(Ordering::Acquire)
}
pub(crate) fn take_nmi_pending(&self) -> bool {
self.nmi_latch.swap(false, Ordering::AcqRel)
}
pub(crate) fn intr_pending(&self) -> bool {
self.intr_asserted()
}
fn clear_nmi_latch(&self) {
self.nmi_latch.store(false, Ordering::Release);
}
fn request_reset(&self) {
self.reset.store(true, Ordering::Release);
}
fn take_reset_request(&self) -> bool {
self.reset.swap(false, Ordering::AcqRel)
}
fn set_a20(&self, open: bool) {
let mask = if open { u32::MAX } else { !(1u32 << 20) };
self.a20_mask.store(mask, Ordering::Release);
}
pub(crate) fn a20_mask(&self) -> u32 {
self.a20_mask.load(Ordering::Relaxed)
}
fn wire_a20(&self) {
self.a20_wired.store(true, Ordering::Release);
self.set_a20(false);
}
fn a20_at_reset(&self) -> bool {
!self.a20_wired.load(Ordering::Acquire)
}
fn attach_ack(&self, ack: Weak<dyn IntAck>) {
self.acks.attach(ack);
}
pub(crate) fn acknowledge(&self) -> u8 {
match self.acks.run(IntAckCycle::vector_only()) {
IntAckResponse::Vector(vector) => vector as u8,
IntAckResponse::Autovector | IntAckResponse::Declined => self.intr_vector(),
}
}
fn snapshot(&self) -> (bool, bool, bool, u8) {
(
self.intr_asserted(),
self.nmi_level.load(Ordering::Acquire),
self.nmi_pending(),
self.intr_vector(),
)
}
fn restore(&self, (intr, level, latch, vector): (bool, bool, bool, u8)) {
self.intr.store(intr, Ordering::Release);
self.nmi_level.store(level, Ordering::Release);
self.nmi_latch.store(latch, Ordering::Release);
self.intr_vector.store(u32::from(vector), Ordering::Release);
}
}
#[derive(Debug)]
struct Session {
state: State,
memory: Option<Arc<AddressSpace>>,
io: Option<Arc<AddressSpace>>,
}
#[derive(Debug)]
pub struct X86 {
cfg: Config,
class: &'static DeviceClass,
lines: Arc<Lines>,
requester: AtomicU32,
iospace: String,
session: sync::Mutex<Session>,
pins: sync::Mutex<Vec<(String, Arc<InputPin>)>>,
}
impl X86 {
#[must_use]
pub fn new(cfg: Config) -> X86 {
X86 {
cfg,
class: &CLASS,
lines: Arc::new(Lines::default()),
requester: AtomicU32::new(cfg.requester.0),
iospace: String::new(),
session: sync::Mutex::with_rank(
LockRank::BUS,
Session {
state: State::new(cfg.variant),
memory: None,
io: None,
},
),
pins: sync::Mutex::new(Vec::new()),
}
}
pub fn from_props(props: &Props) -> Result<X86> {
X86::from_props_defaulting(props, Variant::I8088)
}
pub fn from_props_defaulting(props: &Props, default: Variant) -> Result<X86> {
let mut r = props.reader();
let named = r.optional_str("model")?;
let variant = r.or_enum("variant", default.name(), Variant::NAMES)?;
let variant = match named {
Some(name) => Variant::from_name(name)
.ok_or_else(|| Error::Property(alloc::format!("unknown x86 model `{name}`")))?,
None => Variant::from_name(variant).expect("the enum listed above"),
};
let _engine = r.or_enum("engine", "interp", &["interp"])?;
let iospace = r.optional_str("iospace")?.unwrap_or("").to_string();
r.finish()?;
let mut cpu = X86::new(Config {
variant,
requester: RequesterId::ANONYMOUS,
});
cpu.iospace = iospace;
Ok(cpu)
}
#[must_use]
pub fn as_i8086(mut self) -> X86 {
self.class = &I8086_CLASS;
self
}
#[must_use]
pub fn io_space_name(&self) -> &str {
&self.iospace
}
#[must_use]
pub fn config(&self) -> Config {
Config {
requester: RequesterId(self.requester.load(Ordering::Relaxed)),
..self.cfg
}
}
pub fn set_requester(&self, id: RequesterId) {
self.requester.store(id.0, Ordering::Relaxed);
}
#[must_use]
pub fn a20_open(&self) -> bool {
self.lines.a20_mask() == u32::MAX
}
pub fn set_a20(&self, open: bool) {
self.lines.set_a20(open);
}
pub fn attach_space(&self, space: Arc<AddressSpace>) {
self.session.lock().memory = Some(space);
}
pub fn attach_io_space(&self, space: Arc<AddressSpace>) {
self.session.lock().io = Some(space);
}
#[must_use]
pub fn space(&self) -> Option<Arc<AddressSpace>> {
self.session.lock().memory.clone()
}
#[must_use]
pub fn io_space(&self) -> Option<Arc<AddressSpace>> {
self.session.lock().io.clone()
}
#[must_use]
pub fn regs(&self) -> Regs {
self.session.lock().state.regs
}
pub fn set_regs(&self, regs: Regs) {
let mut session = self.session.lock();
session.state.regs = regs;
session.state.regs.eflags = Regs::normalise_flags(self.cfg.variant, regs.eflags);
if !session.state.sys.protected() {
for index in 0..isa::seg::COUNT as u8 {
let selector = session.state.regs.segment(index);
let entry = session.state.sys.seg_mut(index);
entry.selector = selector;
entry.base = u32::from(selector) << 4;
}
}
session.state.queue.flush();
}
#[must_use]
pub fn sys(&self) -> prot::Sys {
self.session.lock().state.sys
}
pub fn set_sys(&self, sys: prot::Sys) {
let mut session = self.session.lock();
session.state.sys = sys;
session.state.tlb.flush();
session.state.queue.flush();
}
#[must_use]
pub fn reg(&self, reg: Reg) -> u32 {
reg.get(&self.session.lock().state.regs)
}
pub fn set_reg(&self, reg: Reg, value: u32) {
let mut session = self.session.lock();
reg.set(&mut session.state.regs, value);
if matches!(reg, Reg::Eflags | Reg::Flags) {
let value = session.state.regs.eflags;
session.state.regs.eflags = Regs::normalise_flags(self.cfg.variant, value);
}
if matches!(reg, Reg::Cs | Reg::Eip | Reg::Ip) {
if reg == Reg::Cs && !session.state.sys.protected() {
let selector = session.state.regs.cs;
let entry = session.state.sys.seg_mut(isa::seg::CS);
entry.selector = selector;
entry.base = u32::from(selector) << 4;
}
session.state.queue.flush();
}
}
#[must_use]
pub fn cycles(&self) -> u64 {
self.session.lock().state.cycles
}
#[must_use]
pub fn is_halted(&self) -> bool {
self.session.lock().state.halted
}
#[must_use]
pub fn reset_pending(&self) -> bool {
self.session.lock().state.reset_pending
}
#[must_use]
pub fn prefetch_queue(&self) -> Vec<u8> {
self.session.lock().state.queue.contents()
}
pub fn set_prefetch_queue(&self, bytes: &[u8]) -> Result<()> {
let mut session = self.session.lock();
session.state.queue.install(bytes).map_err(|()| {
Error::Property(alloc::format!(
"the {} prefetch queue holds {} bytes, not {}",
self.cfg.variant,
self.cfg.variant.queue_bytes(),
bytes.len()
))
})
}
#[must_use]
pub fn bus_faults(&self) -> (u64, u32) {
let s = self.session.lock();
(s.state.faults, s.state.last_fault)
}
pub fn set_intr(&self, asserted: bool) {
self.lines.set_intr(asserted);
}
#[must_use]
pub fn intr_asserted(&self) -> bool {
self.lines.intr_asserted()
}
pub fn set_intr_vector(&self, vector: u8) {
self.lines.set_intr_vector(vector);
}
#[must_use]
pub fn intr_vector(&self) -> u8 {
self.lines.intr_vector()
}
pub fn set_nmi(&self, asserted: bool) {
self.lines.set_nmi(asserted);
}
pub fn pulse_nmi(&self) {
self.lines.set_nmi(true);
self.lines.set_nmi(false);
}
#[must_use]
pub fn nmi_pending(&self) -> bool {
self.lines.nmi_pending()
}
#[must_use]
pub fn interrupt_shadow(&self) -> bool {
self.session.lock().state.int_shadow
}
pub fn request_reset(&self) {
self.session.lock().state.reset_pending = true;
}
#[must_use]
pub fn reset_requested(&self) -> bool {
self.lines.reset.load(Ordering::Acquire)
}
pub fn acknowledge(&self) -> u8 {
self.lines.acknowledge()
}
pub fn step(&self) -> u64 {
let reset = self.lines.take_reset_request();
let cfg = self.config();
let mut session = self.session.lock();
let Session { state, memory, io } = &mut *session;
state.reset_pending |= reset;
let Some(memory) = memory.clone() else {
return 0;
};
let io = io.clone();
Exec::new(state, &memory, io.as_deref(), &cfg, &self.lines).step()
}
pub fn run(&self, budget: u64) -> u64 {
let mut used = 0;
while used < budget {
let n = self.step();
if n == 0 {
break;
}
used += n;
}
used
}
pub fn run_budget(&self, ticks: u64) -> u64 {
let owed = self.session.lock().state.debt;
if owed >= ticks {
self.session.lock().state.debt = owed - ticks;
return ticks;
}
let allowance = ticks - owed;
let mut used = 0u64;
while used < allowance {
let n = self.step();
if n == 0 {
self.session.lock().state.debt = 0;
return ticks;
}
used += n;
}
self.session.lock().state.debt = used - allowance;
ticks
}
#[must_use]
pub fn cycle_debt(&self) -> u64 {
self.session.lock().state.debt
}
#[must_use]
pub fn disassemble(&self, cs: u16, eip: u32, count: usize) -> Vec<disasm::Disassembled> {
let Some(space) = self.space() else {
return Vec::new();
};
let (base, bits32, legacy) = {
let session = self.session.lock();
let seg = session.state.sys.seg(isa::seg::CS);
let legacy = !self.cfg.variant.is_32bit();
let base = if seg.selector == cs {
seg.base
} else {
u32::from(cs) << 4
};
(base, !legacy && seg.big(), legacy)
};
let map = self.cfg.variant.map();
disasm::disassemble_run_as(map, bits32, cs, eip, count, |addr| {
let addr = if legacy {
u64::from(base.wrapping_add(addr) & 0xf_ffff)
} else {
u64::from(base.wrapping_add(addr))
};
space
.read(addr, Width::U8, MemAttrs::DEBUG)
.ok()
.map(|v| v as u8)
})
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: "cpu.x86",
version: 3,
summary: "Intel x86 CPU core: 8086/8088 real mode, or 80386/80486 with protection and paging",
properties: &[
PropertySpec {
name: "variant",
kind: ValueKind::Str,
required: false,
summary: "\"8086\", \"8088\", \"80386\" or \"80486\" (the default)",
},
PropertySpec {
name: "model",
kind: ValueKind::Str,
required: false,
summary: "accepted as a synonym for \"variant\", which this class used to be called",
},
PropertySpec {
name: "engine",
kind: ValueKind::Str,
required: false,
summary: "which execution engine; only `interp` exists until phase 5",
},
PropertySpec {
name: "iospace",
kind: ValueKind::Str,
required: false,
summary: "the name of the separate address space IN and OUT reach",
},
],
construct: |props| {
Ok(Box::new(X86::from_props_defaulting(
props,
Variant::I80486,
)?))
},
};
pub static I8086_CLASS: DeviceClass = DeviceClass {
name: "cpu.i8086",
version: 3,
summary: "Intel 8086 / 8088 16-bit CPU core, real mode, hardware-checked interpreter",
properties: CLASS.properties,
construct: |props| {
Ok(Box::new(
X86::from_props_defaulting(props, Variant::I8088)?.as_i8086(),
))
},
};
pub fn register(reg: &mut Registry) -> Result<()> {
reg.add(&CLASS)?;
reg.add(&I8086_CLASS)
}
impl Device for X86 {
fn class(&self) -> &'static DeviceClass {
self.class
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
let which = match port {
"intr" => Input::Intr,
"nmi" => Input::Nmi,
"reset" => Input::Reset,
"a20" => Input::A20,
_ => return None,
};
if which == Input::A20 {
self.lines.wire_a20();
}
let pin = Arc::new(InputPin::new(Arc::clone(&self.lines), which, sources));
self.pins.lock().push((port.to_string(), Arc::clone(&pin)));
Some(SinkPin { sink: pin, line: 0 })
}
fn attach_int_ack(&self, port: &str, ack: Weak<dyn IntAck>) {
if port == "intr" {
self.lines.attach_ack(ack);
}
}
fn is_runnable(&self) -> bool {
true
}
fn run(&self, budget: Budget) -> Consumed {
Consumed::new(self.run_budget(budget.ticks))
}
fn reset(&self, kind: ResetKind) {
let mut session = self.session.lock();
if kind == ResetKind::Cold {
session.state = State::new(self.cfg.variant);
} else {
session.state.reset_pending = true;
session.state.halted = false;
session.state.int_shadow = false;
session.state.queue.flush();
}
drop(session);
if kind == ResetKind::Cold {
self.lines.restore((false, false, false, 0));
self.lines.set_a20(self.lines.a20_at_reset());
} else {
self.lines.clear_nmi_latch();
}
self.lines.take_reset_request();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let reset = self.lines.take_reset_request();
let state = {
let mut session = self.session.lock();
session.state.reset_pending |= reset;
session.state
};
for reg in Reg::ALL {
w.write_u32(reg.get(&state.regs))?;
}
w.write_u64(state.cycles)?;
for index in 0..isa::seg::COUNT as u8 {
let s = state.sys.seg(index);
w.write_u16(s.selector)?;
w.write_u32(s.base)?;
w.write_u32(s.limit)?;
w.write_u32(s.ar)?;
}
for s in [state.sys.ldtr, state.sys.task] {
w.write_u16(s.selector)?;
w.write_u32(s.base)?;
w.write_u32(s.limit)?;
w.write_u32(s.ar)?;
}
for t in [state.sys.gdtr, state.sys.idtr] {
w.write_u32(t.base)?;
w.write_u32(t.limit)?;
}
w.write_u32(state.sys.cr0)?;
w.write_u32(state.sys.cr2)?;
w.write_u32(state.sys.cr3)?;
for value in state.sys.dr {
w.write_u32(value)?;
}
for value in state.sys.test {
w.write_u32(value)?;
}
w.write_bool(state.halted)?;
w.write_bool(state.shutdown)?;
w.write_bool(state.reset_pending)?;
w.write_bool(state.int_shadow)?;
w.write_u8(state.open_bus)?;
w.write_u64(state.faults)?;
w.write_u32(state.last_fault)?;
let queue = state.queue.contents();
w.write_u8(queue.len() as u8)?;
for byte in queue {
w.write_u8(byte)?;
}
w.write_u64(state.debt)?;
let (intr, nmi_level, nmi_latch, vector) = self.lines.snapshot();
w.write_bool(intr)?;
w.write_bool(nmi_level)?;
w.write_bool(nmi_latch)?;
w.write_u8(vector)?;
w.write_bool(self.a20_open())?;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State::new(self.cfg.variant);
for reg in Reg::ALL {
let value = r.read_u32()?;
reg.set(&mut state.regs, value);
}
state.cycles = r.read_u64()?;
for index in 0..isa::seg::COUNT as u8 {
let s = state.sys.seg_mut(index);
s.selector = r.read_u16()?;
s.base = r.read_u32()?;
s.limit = r.read_u32()?;
s.ar = r.read_u32()?;
}
for slot in [0usize, 1] {
let s = prot::SegReg {
selector: r.read_u16()?,
base: r.read_u32()?,
limit: r.read_u32()?,
ar: r.read_u32()?,
};
if slot == 0 {
state.sys.ldtr = s;
} else {
state.sys.task = s;
}
}
for slot in [0usize, 1] {
let t = prot::TableReg {
base: r.read_u32()?,
limit: r.read_u32()?,
};
if slot == 0 {
state.sys.gdtr = t;
} else {
state.sys.idtr = t;
}
}
state.sys.cr0 = r.read_u32()?;
state.sys.cr2 = r.read_u32()?;
state.sys.cr3 = r.read_u32()?;
for i in 0..8 {
state.sys.dr[i] = r.read_u32()?;
}
for i in 0..8 {
state.sys.test[i] = r.read_u32()?;
}
state.halted = r.read_bool()?;
state.shutdown = r.read_bool()?;
state.reset_pending = r.read_bool()?;
state.int_shadow = r.read_bool()?;
state.open_bus = r.read_u8()?;
state.faults = r.read_u64()?;
state.last_fault = r.read_u32()?;
let len = r.read_u8()?;
let mut queue = Vec::with_capacity(usize::from(len));
for _ in 0..len {
queue.push(r.read_u8()?);
}
state.queue.install(&queue).map_err(|()| {
Error::State(alloc::format!(
"snapshot has a {len}-byte prefetch queue; an {} holds {}",
self.cfg.variant,
self.cfg.variant.queue_bytes()
))
})?;
state.debt = r.read_u64()?;
let intr = r.read_bool()?;
let nmi_level = r.read_bool()?;
let nmi_latch = r.read_bool()?;
let vector = r.read_u8()?;
let a20 = r.read_bool()?;
state.tlb.flush();
self.session.lock().state = state;
self.lines.restore((intr, nmi_level, nmi_latch, vector));
self.lines.set_a20(a20);
Ok(())
}
}
impl Initiator for X86 {
fn requester(&self) -> RequesterId {
RequesterId(self.requester.load(Ordering::Relaxed))
}
}
impl crate::machine::Instance for X86 {
fn bind(&self, ctx: &crate::machine::BindCtx<'_>) -> Result<()> {
let memory = ctx.space().ok_or_else(|| Error::Config {
at: ctx.path().to_string(),
message: String::from("an x86 needs an address space to fetch from (`space = mem`)"),
})?;
self.attach_space(Arc::clone(memory));
if !self.iospace.is_empty() {
let io = ctx
.space_named(&self.iospace)
.ok_or_else(|| Error::Config {
at: ctx.path().to_string(),
message: alloc::format!(
"`iospace = \"{}\"` names no address space in this machine",
self.iospace
),
})?;
self.attach_io_space(Arc::clone(io));
}
self.set_requester(ctx.requester());
Ok(())
}
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS.name, |props| {
Ok(Arc::new(X86::from_props_defaulting(
props,
Variant::I80486,
)?))
})?;
bindings.bind(I8086_CLASS.name, |props| {
Ok(Arc::new(
X86::from_props_defaulting(props, Variant::I8088)?.as_i8086(),
))
})
}
#[must_use]
pub fn schemas() -> Vec<crate::machine::validate::ClassSchema> {
alloc::vec![schema_for(CLASS.name), schema_for(I8086_CLASS.name)]
}
fn schema_for(name: &'static str) -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
ClassSchema::new(name)
.prop(PropSchema::new("variant", ValueKind::Str).values(Variant::NAMES))
.prop(PropSchema::new("model", ValueKind::Str).values(Variant::NAMES))
.prop(PropSchema::new("engine", ValueKind::Str).values(&["interp"]))
.prop(PropSchema::new("iospace", ValueKind::Str))
.port("intr", PortDir::In)
.port("nmi", PortDir::In)
.port("reset", PortDir::In)
.port("a20", PortDir::In)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Input {
Intr,
Nmi,
Reset,
A20,
}
#[derive(Debug)]
pub struct InputPin {
lines: Arc<Lines>,
which: Input,
inputs: FanIn,
resolve: Resolve,
}
impl InputPin {
fn new(lines: Arc<Lines>, which: Input, sources: &[WireId]) -> InputPin {
InputPin {
lines,
which,
inputs: FanIn::new(sources),
resolve: Resolve::Or,
}
}
#[must_use]
pub fn inputs(&self) -> &FanIn {
&self.inputs
}
}
impl WireSink for InputPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let asserted = self.inputs.resolve(self.resolve).is_high();
match self.which {
Input::Intr => self.lines.set_intr(asserted),
Input::Nmi => self.lines.set_nmi(asserted),
Input::Reset => {
if asserted {
self.lines.request_reset();
}
}
Input::A20 => self.lines.set_a20(asserted),
}
}
}
#[derive(Debug)]
pub struct InterruptPin {
lines: Arc<Lines>,
which: Interrupt,
inputs: FanIn,
resolve: Resolve,
}
impl InterruptPin {
#[must_use]
pub fn new(cpu: Arc<X86>, which: Interrupt, sources: &[WireId]) -> InterruptPin {
InterruptPin {
lines: Arc::clone(&cpu.lines),
which,
inputs: FanIn::new(sources),
resolve: Resolve::Or,
}
}
#[must_use]
pub fn with_resolve(mut self, resolve: Resolve) -> Self {
self.resolve = resolve;
self
}
#[must_use]
pub fn which(&self) -> Interrupt {
self.which
}
#[must_use]
pub fn inputs(&self) -> &FanIn {
&self.inputs
}
}
impl WireSink for InterruptPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let asserted = self.inputs.resolve(self.resolve).is_high();
match self.which {
Interrupt::Intr => self.lines.set_intr(asserted),
Interrupt::Nmi => self.lines.set_nmi(asserted),
}
}
}
#[must_use]
pub fn describe_isa() -> String {
describe_isa_for(Variant::I8088)
}
#[must_use]
pub fn describe_isa_for(variant: Variant) -> String {
use core::fmt::Write as _;
let map = variant.map();
let mut out = String::new();
let mark = |class: isa::Class| match class {
isa::Class::Documented => ' ',
isa::Class::Alias => '=',
isa::Class::Undocumented => '*',
isa::Class::Undefined => '?',
isa::Class::Prefix => ':',
isa::Class::Escape => '~',
};
let row = |out: &mut String, prefix: &str, opcode: u8, insn: isa::Insn| {
if insn.group == isa::Grp::None {
let _ = writeln!(
out,
"{prefix}{opcode:02x} {}{:<7} {}",
mark(insn.class),
insn.op.mnemonic(),
insn.op.summary()
);
} else {
for reg in 0..8u8 {
let sub = isa::resolve_as(map, insn, reg);
let _ = writeln!(
out,
"{prefix}{opcode:02x}/{reg} {}{:<7} {}",
mark(sub.class),
sub.op.mnemonic(),
sub.op.summary()
);
}
}
};
for opcode in 0..=255u8 {
row(&mut out, "", opcode, isa::decode_as(map, opcode));
}
if matches!(map, isa::Gen::I386) {
for opcode in 0..=255u8 {
if !isa::LISTED_0F[opcode as usize] {
continue;
}
row(&mut out, "0f ", opcode, isa::decode_0f(opcode));
}
}
out
}