use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::space::{
AccessConstraints, AddressSpace, MemAttrs, MemOps, MemResult, RamStore, Region,
};
use crate::core::sync::{self, LockRank};
use super::{Config, Variant, X86};
const DEFAULT_STEPS: usize = 20_000_000;
#[derive(Debug)]
struct PortLog {
seen: sync::Mutex<Vec<u16>>,
}
impl Default for PortLog {
fn default() -> PortLog {
PortLog {
seen: sync::Mutex::with_rank(LockRank::DEVICE, Vec::new()),
}
}
}
impl MemOps for PortLog {
fn read(&self, offset: u64, dst: &mut [u8], _: MemAttrs) -> MemResult {
self.seen.lock().push(offset as u16);
dst.fill(0xff);
Ok(())
}
fn write(&self, offset: u64, _: &[u8], _: MemAttrs) -> MemResult {
self.seen.lock().push(offset as u16);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::ANY
}
}
#[test]
fn a_pc_firmware_image_reaches_protected_mode_and_keeps_running() {
let Ok(path) = std::env::var("RSEMU_BIOS") else {
println!(
"firmware: set RSEMU_BIOS to a legacy PC BIOS image to run it on this \
core; see the module docs"
);
return;
};
let image = std::fs::read(&path).unwrap_or_else(|e| panic!("{path}: {e}"));
let size = image.len() as u64;
assert!(
size.is_power_of_two() && (0x1_0000..=0x10_0000).contains(&size),
"{path}: {size} bytes is not a plausible BIOS image"
);
let ram = Arc::new(RamStore::new(0x100_0000));
let rom = Arc::new(RamStore::new(size));
for (i, byte) in image.iter().enumerate() {
let i = i as u64;
rom.write_u8(i, *byte).unwrap();
if i >= size - 0x2_0000 {
ram.write_u8(0xe_0000 + (i - (size - 0x2_0000)), *byte)
.unwrap();
}
}
let mem = AddressSpace::new("mem", 32);
mem.topology()
.map(Region::ram("ram", ram), 0)
.expect("16 MiB at zero");
mem.topology()
.map(Region::ram("rom", rom), 0x1_0000_0000u64 - size)
.expect("the image at the top of the space");
let ports = Arc::new(PortLog::default());
let io = AddressSpace::new("io", 16);
io.topology()
.map(Region::io("ports", 0x1_0000, ports.clone()), 0)
.expect("64 KiB fits in 16 bits");
let cpu = X86::new(Config::default().with_variant(Variant::I80486));
cpu.attach_space(Arc::new(mem));
cpu.attach_io_space(Arc::new(io));
let limit: usize = std::env::var("RSEMU_BIOS_STEPS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_STEPS);
let mut protected_after = None;
let mut executed = 0usize;
for step in 0..limit {
if cpu.step() == 0 {
break;
}
executed += 1;
if protected_after.is_none() && cpu.sys().protected() {
protected_after = Some(step);
}
}
let regs = cpu.regs();
let sys = cpu.sys();
println!(
"firmware: {executed} instructions; protected mode after {:?}; stopped at \
{:04x}:{:08x}",
protected_after, regs.cs, regs.eip
);
println!("firmware: {regs}");
println!(
"firmware: cr0={:08x} gdtr={:08x}+{:x} idtr={:08x}+{:x}",
sys.cr0, sys.gdtr.base, sys.gdtr.limit, sys.idtr.base, sys.idtr.limit
);
let (faults, last) = cpu.bus_faults();
println!("firmware: {faults} unanswered bus access(es), last at {last:08x}");
let mut seen = ports.seen.lock().clone();
seen.sort_unstable();
seen.dedup();
println!("firmware: I/O ports touched: {seen:04x?}");
assert!(
protected_after.is_some(),
"the image never set CR0.PE — see the trace above"
);
assert!(sys.gdtr.limit > 0, "no descriptor table was loaded");
assert_eq!(
executed, limit,
"the core stopped early: halted or shut down"
);
assert!(
seen.contains(&0x0070) && seen.contains(&0x0043),
"a PC firmware image should have reached for the RTC and the timer"
);
}