use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::Props;
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::{Endian, Width};
use crate::core::wire::{FanIn, Level, Resolve, WireId, WireSink, WireSource};
use crate::machine::realize::Instance;
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "pc.sysctl";
const STATE_VERSION: u32 = 2;
pub const REGISTER_WINDOW_LEN: u64 = 1;
const B_GATE2: u8 = 0x01;
const B_SPEAKER: u8 = 0x02;
const B_PARITY_ENABLE: u8 = 0x04;
const B_IOCHK_ENABLE: u8 = 0x08;
const B_REFRESH: u8 = 0x10;
const B_TIMER2_OUT: u8 = 0x20;
const B_IOCHK_STATUS: u8 = 0x40;
const B_PARITY_STATUS: u8 = 0x80;
const B_LATCH_MASK: u8 = B_GATE2 | B_SPEAKER | B_PARITY_ENABLE | B_IOCHK_ENABLE;
const A_FAST_RESET: u8 = 0x01;
const A_GATE_A20: u8 = 0x02;
const C_SYS_RST: u8 = 0x02;
const C_RST_CPU: u8 = 0x04;
const C_FULL_RST: u8 = 0x08;
const C_LATCH_MASK: u8 = C_SYS_RST | C_FULL_RST;
const LINE_REFRESH: u32 = 0;
const LINE_TIMER2: u32 = 1;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
struct State {
port_b: u8,
port_a: u8,
parity_status: bool,
iochk_status: bool,
refresh_toggle: bool,
refresh_in: bool,
timer2_in: bool,
reset_ctl: u8,
rst_cpu: bool,
}
struct Registers {
state: Mutex<State>,
gate2: Mutex<Option<WireSource>>,
a20: Mutex<Option<WireSource>>,
reset: Mutex<Option<WireSource>>,
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Registers");
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
fn drive(pin: &Mutex<Option<WireSource>>, level: Level) {
let out = pin.lock().clone();
if let Some(out) = out {
out.set(level);
}
}
impl Registers {
fn read_b(&self) -> u8 {
let s = self.state.lock();
let mut value = s.port_b & B_LATCH_MASK;
if s.refresh_toggle {
value |= B_REFRESH;
}
if s.timer2_in {
value |= B_TIMER2_OUT;
}
if s.iochk_status {
value |= B_IOCHK_STATUS;
}
if s.parity_status {
value |= B_PARITY_STATUS;
}
value
}
fn write_b(&self, value: u8) {
let gate2 = {
let mut s = self.state.lock();
s.port_b = value & B_LATCH_MASK;
if value & B_PARITY_ENABLE != 0 {
s.parity_status = false;
}
if value & B_IOCHK_ENABLE != 0 {
s.iochk_status = false;
}
Level::from_bool(s.port_b & B_GATE2 != 0)
};
drive(&self.gate2, gate2);
}
fn read_a(&self) -> u8 {
self.state.lock().port_a
}
fn pulse_reset(&self) {
let out = self.reset.lock().clone();
if let Some(out) = out {
out.pulse(Level::High);
}
}
fn write_a(&self, value: u8) {
let (a20, fast_reset) = {
let mut s = self.state.lock();
s.port_a = value & !A_FAST_RESET;
(
Level::from_bool(value & A_GATE_A20 != 0),
value & A_FAST_RESET != 0,
)
};
drive(&self.a20, a20);
if fast_reset {
self.pulse_reset();
}
}
fn read_c(&self) -> u8 {
self.state.lock().reset_ctl
}
fn write_c(&self, value: u8) {
let trigger = {
let mut s = self.state.lock();
s.reset_ctl = value & C_LATCH_MASK;
let armed = value & C_RST_CPU != 0;
let trigger = armed && !s.rst_cpu;
s.rst_cpu = armed;
trigger
};
if trigger {
self.pulse_reset();
}
}
fn refresh_edge(&self, level: Level) {
let mut s = self.state.lock();
if s.refresh_in == level.is_high() {
return;
}
s.refresh_in = level.is_high();
s.refresh_toggle = !s.refresh_toggle;
}
fn timer2_level(&self, level: Level) {
self.state.lock().timer2_in = level.is_high();
}
fn drive_outputs(&self) {
let (gate2, a20) = {
let s = self.state.lock();
(
Level::from_bool(s.port_b & B_GATE2 != 0),
Level::from_bool(s.port_a & A_GATE_A20 != 0),
)
};
drive(&self.gate2, gate2);
drive(&self.a20, a20);
}
}
#[derive(Debug)]
struct PortB(Arc<Registers>);
#[derive(Debug)]
struct PortA(Arc<Registers>);
#[derive(Debug)]
struct ResetCtl(Arc<Registers>);
fn byte_port() -> AccessConstraints {
AccessConstraints::word(Width::U8, Endian::Little)
}
fn only_byte_zero(offset: u64, len: usize) -> MemResult {
if offset == 0 && len == 1 {
Ok(())
} else {
Err(BusError::BadAccess)
}
}
impl MemOps for PortB {
fn read(&self, offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
only_byte_zero(offset, dst.len())?;
dst[0] = self.0.read_b();
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
only_byte_zero(offset, src.len())?;
if attrs.debug {
return Err(BusError::BadAccess);
}
self.0.write_b(src[0]);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
byte_port()
}
}
impl MemOps for PortA {
fn read(&self, offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
only_byte_zero(offset, dst.len())?;
dst[0] = self.0.read_a();
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
only_byte_zero(offset, src.len())?;
if attrs.debug {
return Err(BusError::BadAccess);
}
self.0.write_a(src[0]);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
byte_port()
}
}
impl MemOps for ResetCtl {
fn read(&self, offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
only_byte_zero(offset, dst.len())?;
dst[0] = self.0.read_c();
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
only_byte_zero(offset, src.len())?;
if attrs.debug {
return Err(BusError::BadAccess);
}
self.0.write_c(src[0]);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
byte_port()
}
}
#[derive(Debug)]
struct InputPin {
regs: Arc<Registers>,
line: u32,
inputs: FanIn,
}
impl WireSink for InputPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let level = self.inputs.resolve(Resolve::Or);
if self.line == LINE_REFRESH {
self.regs.refresh_edge(level);
} else {
self.regs.timer2_level(level);
}
}
}
#[derive(Debug)]
pub struct SysCtl {
regs: Arc<Registers>,
port_b: RegionRef,
port_a: RegionRef,
reset_ctl: RegionRef,
pins: Mutex<Vec<Arc<InputPin>>>,
}
impl SysCtl {
pub fn new(props: &Props) -> Result<SysCtl> {
props.reader().finish()?;
Ok(SysCtl::default_device())
}
#[must_use]
pub fn default_device() -> SysCtl {
let regs = Arc::new(Registers {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
gate2: Mutex::with_rank(LockRank::LEAF, None),
a20: Mutex::with_rank(LockRank::LEAF, None),
reset: Mutex::with_rank(LockRank::LEAF, None),
});
let port_b: RegionRef = Arc::new(Region::io(
"pc.sysctl.portb",
REGISTER_WINDOW_LEN,
Arc::new(PortB(Arc::clone(®s))) as Arc<dyn MemOps>,
));
let port_a: RegionRef = Arc::new(Region::io(
"pc.sysctl.porta",
REGISTER_WINDOW_LEN,
Arc::new(PortA(Arc::clone(®s))) as Arc<dyn MemOps>,
));
let reset_ctl: RegionRef = Arc::new(Region::io(
"pc.sysctl.resetctl",
REGISTER_WINDOW_LEN,
Arc::new(ResetCtl(Arc::clone(®s))) as Arc<dyn MemOps>,
));
SysCtl {
regs,
port_b,
port_a,
reset_ctl,
pins: Mutex::with_rank(LockRank::LEAF, Vec::new()),
}
}
pub fn raise_parity_check(&self) {
self.regs.state.lock().parity_status = true;
}
pub fn raise_io_channel_check(&self) {
self.regs.state.lock().iochk_status = true;
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "the AT system control ports: speaker gate, refresh toggle, A20 and the reset paths",
properties: &[],
construct: |props| Ok(Box::new(SysCtl::new(props)?)),
};
fn unknown_pin(port: &str) -> Error {
Error::Config {
at: port.to_string(),
message: String::from(
"the system control ports take `refresh` and `timer2` in, \
and drive `gate2`, `a20` and `reset` out",
),
}
}
impl Device for SysCtl {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
let (refresh_in, timer2_in) = {
let s = self.regs.state.lock();
(s.refresh_in, s.timer2_in)
};
*self.regs.state.lock() = State {
refresh_in,
timer2_in,
..State::default()
};
self.regs.drive_outputs();
}
fn region(&self, name: &str) -> Option<RegionRef> {
match name {
"" | "portb" => Some(Arc::clone(&self.port_b)),
"porta" => Some(Arc::clone(&self.port_a)),
"resetctl" => Some(Arc::clone(&self.reset_ctl)),
_ => None,
}
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
let line = match port {
"refresh" => LINE_REFRESH,
"timer2" => LINE_TIMER2,
_ => return None,
};
let pin = Arc::new(InputPin {
regs: Arc::clone(&self.regs),
line,
inputs: FanIn::new(sources),
});
self.pins.lock().push(Arc::clone(&pin));
Some(SinkPin { sink: pin, line })
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
let pin = match port {
"gate2" => &self.regs.gate2,
"a20" => &self.regs.a20,
"reset" => &self.regs.reset,
_ => return Err(unknown_pin(port)),
};
*pin.lock() = Some(source);
Ok(())
}
fn announce(&self, port: &str) {
match port {
"gate2" | "a20" => self.regs.drive_outputs(),
_ => {}
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let s = *self.regs.state.lock();
w.write_u8(s.port_b)?;
w.write_u8(s.port_a)?;
w.write_bool(s.parity_status)?;
w.write_bool(s.iochk_status)?;
w.write_bool(s.refresh_toggle)?;
w.write_bool(s.refresh_in)?;
w.write_bool(s.timer2_in)?;
w.write_u8(s.reset_ctl)?;
w.write_bool(s.rst_cpu)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let state = State {
port_b: r.read_u8()? & B_LATCH_MASK,
port_a: r.read_u8()? & !A_FAST_RESET,
parity_status: r.read_bool()?,
iochk_status: r.read_bool()?,
refresh_toggle: r.read_bool()?,
refresh_in: r.read_bool()?,
timer2_in: r.read_bool()?,
reset_ctl: r.read_u8()? & C_LATCH_MASK,
rst_cpu: r.read_bool()?,
};
*self.regs.state.lock() = state;
self.regs.drive_outputs();
Ok(())
}
}
impl Instance for SysCtl {}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(SysCtl::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::PortDir;
ClassSchema::new(CLASS_NAME)
.region("")
.region("portb")
.region("porta")
.region("resetctl")
.port("refresh", PortDir::In)
.port("timer2", PortDir::In)
.port("gate2", PortDir::Out)
.port("a20", PortDir::Out)
.port("reset", PortDir::Out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::sync::{AtomicU32, Ordering};
use crate::core::wire::{Wire, WireIdAllocator};
#[derive(Debug, Default)]
struct Probe {
level: AtomicU32,
rises: AtomicU32,
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
if level.is_high() {
self.rises.fetch_add(1, Ordering::Relaxed);
}
self.level
.store(u32::from(level.is_high()), Ordering::Relaxed);
}
}
impl Probe {
fn high(&self) -> bool {
self.level.load(Ordering::Relaxed) == 1
}
fn rises(&self) -> u32 {
self.rises.load(Ordering::Relaxed)
}
}
fn watch(dev: &SysCtl, port: &str) -> Arc<Probe> {
let ids = WireIdAllocator::new();
let id = ids.alloc();
let probe = Arc::new(Probe::default());
let wire = Wire::builder()
.source(id)
.sink(Arc::clone(&probe) as Arc<dyn WireSink>, 0)
.build_shared();
dev.connect(port, WireSource::new(wire, id))
.expect("the system control ports drive this pin");
probe
}
fn feed(dev: &SysCtl, port: &str) -> WireSource {
let ids = WireIdAllocator::new();
let id = ids.alloc();
let pin = dev
.sink(port, &[id])
.expect("the system control ports listen on this pin");
let wire = Wire::builder()
.source(id)
.sink(pin.sink, pin.line)
.build_shared();
WireSource::new(wire, id)
}
fn peek(dev: &SysCtl, region: &str) -> u8 {
let mut byte = [0u8; 1];
ops(dev, region)
.read(0, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
byte[0]
}
fn poke(dev: &SysCtl, region: &str, value: u8) {
ops(dev, region)
.write(0, &[value], MemAttrs::DEFAULT)
.expect("a byte write is legal");
}
fn ops(dev: &SysCtl, region: &str) -> Arc<dyn MemOps> {
match region {
"porta" => Arc::new(PortA(Arc::clone(&dev.regs))) as Arc<dyn MemOps>,
"resetctl" => Arc::new(ResetCtl(Arc::clone(&dev.regs))) as Arc<dyn MemOps>,
_ => Arc::new(PortB(Arc::clone(&dev.regs))) as Arc<dyn MemOps>,
}
}
#[test]
fn port_b_bit_0_gates_the_timer_and_reads_back() {
let dev = SysCtl::default_device();
let gate2 = watch(&dev, "gate2");
assert!(!gate2.high(), "the speaker is silent at power-on");
poke(&dev, "portb", B_GATE2);
assert!(gate2.high());
assert_eq!(peek(&dev, "portb") & B_LATCH_MASK, B_GATE2);
poke(&dev, "portb", B_GATE2 | B_SPEAKER);
assert!(gate2.high(), "and stays gated");
assert_eq!(peek(&dev, "portb") & B_LATCH_MASK, B_GATE2 | B_SPEAKER);
poke(&dev, "portb", 0);
assert!(!gate2.high());
}
#[test]
fn port_b_bit_5_follows_the_timer2_pin() {
let dev = SysCtl::default_device();
let timer2 = feed(&dev, "timer2");
assert_eq!(peek(&dev, "portb") & B_TIMER2_OUT, 0);
timer2.set(Level::High);
assert_eq!(peek(&dev, "portb") & B_TIMER2_OUT, B_TIMER2_OUT);
timer2.set(Level::Low);
assert_eq!(peek(&dev, "portb") & B_TIMER2_OUT, 0);
}
#[test]
fn each_refresh_edge_flips_port_b_bit_4() {
let dev = SysCtl::default_device();
let refresh = feed(&dev, "refresh");
let mut expected = 0u8;
for _ in 0..4 {
for level in [Level::High, Level::Low] {
refresh.set(level);
expected ^= B_REFRESH;
assert_eq!(peek(&dev, "portb") & B_REFRESH, expected);
}
}
refresh.set(Level::Low);
assert_eq!(peek(&dev, "portb") & B_REFRESH, expected);
}
#[test]
fn port_a_bit_1_drives_a20_and_bit_0_pulses_reset() {
let dev = SysCtl::default_device();
let a20 = watch(&dev, "a20");
let reset = watch(&dev, "reset");
poke(&dev, "porta", A_GATE_A20);
assert!(a20.high());
assert_eq!(reset.rises(), 0, "A20 alone resets nothing");
assert_eq!(peek(&dev, "porta"), A_GATE_A20);
poke(&dev, "porta", A_GATE_A20 | A_FAST_RESET);
assert_eq!(reset.rises(), 1);
assert!(!reset.high(), "a pulse, not a level");
assert!(a20.high(), "and A20 stayed where it was put");
assert_eq!(peek(&dev, "porta"), A_GATE_A20);
poke(&dev, "porta", 0);
assert!(!a20.high());
}
#[test]
fn reset_control_pulses_on_bit_2s_transition_and_not_on_a_repeat() {
let dev = SysCtl::default_device();
let reset = watch(&dev, "reset");
poke(&dev, "resetctl", C_SYS_RST);
assert_eq!(reset.rises(), 0, "arming the kind of reset is not a reset");
poke(&dev, "resetctl", C_SYS_RST | C_RST_CPU);
assert_eq!(reset.rises(), 1);
assert!(!reset.high(), "a pulse, not a level");
poke(&dev, "resetctl", C_SYS_RST | C_RST_CPU);
assert_eq!(
reset.rises(),
1,
"a write that leaves bit 2 set is not an edge"
);
poke(&dev, "resetctl", C_SYS_RST);
assert_eq!(reset.rises(), 1);
poke(&dev, "resetctl", C_SYS_RST | C_RST_CPU);
assert_eq!(reset.rises(), 2);
}
#[test]
fn reset_control_reaches_the_same_reset_pin_as_port_a() {
let dev = SysCtl::default_device();
let reset = watch(&dev, "reset");
poke(&dev, "porta", A_FAST_RESET);
assert_eq!(reset.rises(), 1);
poke(&dev, "resetctl", C_RST_CPU);
assert_eq!(reset.rises(), 2);
}
#[test]
fn reset_control_bit_2_reads_back_clear_and_the_others_read_back() {
let dev = SysCtl::default_device();
assert_eq!(peek(&dev, "resetctl"), 0, "clear at power-on");
poke(&dev, "resetctl", C_SYS_RST | C_RST_CPU | C_FULL_RST);
assert_eq!(peek(&dev, "resetctl"), C_SYS_RST | C_FULL_RST);
poke(&dev, "resetctl", C_FULL_RST);
assert_eq!(peek(&dev, "resetctl"), C_FULL_RST);
poke(&dev, "resetctl", 0xf0 | C_SYS_RST);
assert_eq!(peek(&dev, "resetctl"), C_SYS_RST);
}
#[test]
fn a_debug_access_of_the_reset_control_register_neither_reboots_nor_arms() {
let dev = SysCtl::default_device();
let reset = watch(&dev, "reset");
poke(&dev, "resetctl", C_SYS_RST);
let before = peek(&dev, "resetctl");
let mut byte = [0u8; 1];
for _ in 0..3 {
ops(&dev, "resetctl")
.read(0, &mut byte, MemAttrs::DEBUG)
.expect("a debug read is legal");
assert_eq!(byte[0], before, "and it moved nothing");
}
assert!(
ops(&dev, "resetctl")
.write(0, &[C_SYS_RST | C_RST_CPU], MemAttrs::DEBUG)
.is_err()
);
assert_eq!(reset.rises(), 0, "nothing was rebooted");
assert_eq!(peek(&dev, "resetctl"), before, "and nothing was latched");
poke(&dev, "resetctl", C_SYS_RST | C_RST_CPU);
assert_eq!(reset.rises(), 1);
}
#[test]
fn writing_the_enable_bits_clears_the_check_status_bits() {
let dev = SysCtl::default_device();
dev.raise_parity_check();
dev.raise_io_channel_check();
assert_eq!(
peek(&dev, "portb") & (B_PARITY_STATUS | B_IOCHK_STATUS),
B_PARITY_STATUS | B_IOCHK_STATUS
);
poke(&dev, "portb", B_PARITY_ENABLE);
assert_eq!(peek(&dev, "portb") & B_PARITY_STATUS, 0);
assert_eq!(
peek(&dev, "portb") & B_IOCHK_STATUS,
B_IOCHK_STATUS,
"the other one stands"
);
poke(&dev, "portb", B_IOCHK_ENABLE);
assert_eq!(peek(&dev, "portb") & B_IOCHK_STATUS, 0);
}
#[test]
fn a_debug_read_of_port_b_changes_nothing_and_a_debug_write_is_refused() {
let dev = SysCtl::default_device();
let refresh = feed(&dev, "refresh");
let gate2 = watch(&dev, "gate2");
refresh.set(Level::High);
let before = peek(&dev, "portb");
assert_eq!(before & B_REFRESH, B_REFRESH);
let mut byte = [0u8; 1];
for _ in 0..3 {
ops(&dev, "portb")
.read(0, &mut byte, MemAttrs::DEBUG)
.expect("a debug read is legal");
assert_eq!(byte[0], before, "the refresh toggle did not move");
}
assert!(
ops(&dev, "portb")
.write(0, &[B_GATE2], MemAttrs::DEBUG)
.is_err()
);
assert!(!gate2.high(), "and nothing was gated");
assert!(
ops(&dev, "porta")
.write(0, &[A_FAST_RESET], MemAttrs::DEBUG)
.is_err()
);
}
#[test]
fn an_access_that_is_not_a_single_byte_at_offset_zero_is_refused() {
let dev = SysCtl::default_device();
for region in ["portb", "porta", "resetctl"] {
let ops = ops(&dev, region);
assert!(ops.read(0, &mut [0u8; 2], MemAttrs::DEFAULT).is_err());
assert!(ops.read(1, &mut [0u8; 1], MemAttrs::DEFAULT).is_err());
assert!(ops.write(0, &[0u8; 4], MemAttrs::DEFAULT).is_err());
assert!(ops.write(1, &[0u8], MemAttrs::DEFAULT).is_err());
}
}
#[test]
fn the_two_ports_are_separate_regions() {
let dev = SysCtl::default_device();
assert!(dev.region("").is_some());
assert!(dev.region("portb").is_some());
assert!(dev.region("porta").is_some());
assert!(
dev.region("regs").is_none(),
"one name per port, not a lump"
);
poke(&dev, "portb", B_GATE2 | B_SPEAKER);
assert_eq!(peek(&dev, "porta"), 0);
poke(&dev, "porta", A_GATE_A20);
assert_eq!(peek(&dev, "portb") & B_LATCH_MASK, B_GATE2 | B_SPEAKER);
}
#[test]
fn an_unknown_pin_is_an_error_rather_than_a_silent_no_op() {
let dev = SysCtl::default_device();
let ids = WireIdAllocator::new();
let id = ids.alloc();
let wire = Wire::builder().source(id).build_shared();
assert!(dev.connect("speaker", WireSource::new(wire, id)).is_err());
assert!(dev.sink("gate2", &[id]).is_none(), "gate2 is an output");
}
#[test]
fn a_reset_silences_the_speaker_and_masks_a20() {
let dev = SysCtl::default_device();
let gate2 = watch(&dev, "gate2");
let a20 = watch(&dev, "a20");
poke(&dev, "portb", B_GATE2 | B_SPEAKER);
poke(&dev, "porta", A_GATE_A20);
dev.raise_parity_check();
dev.reset(ResetKind::Cold);
assert!(!gate2.high());
assert!(!a20.high());
assert_eq!(peek(&dev, "portb"), 0);
assert_eq!(peek(&dev, "porta"), 0);
}
#[test]
fn a_reset_does_not_forget_what_the_timer_is_driving() {
let dev = SysCtl::default_device();
let refresh = feed(&dev, "refresh");
let timer2 = feed(&dev, "timer2");
timer2.raise();
refresh.raise();
assert_eq!(peek(&dev, "portb") & B_TIMER2_OUT, B_TIMER2_OUT);
let toggle = peek(&dev, "portb") & B_REFRESH;
dev.reset(ResetKind::Cold);
assert_eq!(
peek(&dev, "portb") & B_TIMER2_OUT,
B_TIMER2_OUT,
"the speaker's own waveform vanished from port 0x61 across a reset"
);
assert_eq!(peek(&dev, "portb") & B_REFRESH, 0);
let _ = toggle;
refresh.set(Level::High);
timer2.set(Level::High);
assert_eq!(
peek(&dev, "portb") & B_REFRESH,
0,
"a level that never moved was counted as a refresh edge"
);
refresh.lower();
assert_eq!(peek(&dev, "portb") & B_REFRESH, B_REFRESH);
assert_eq!(peek(&dev, "portb") & B_TIMER2_OUT, B_TIMER2_OUT);
}
fn save_image(dev: &SysCtl) -> Vec<u8> {
let mut shape = MachineShape::new();
shape.add_device("sysctl", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("sysctl", CLASS.name, CLASS.version).unwrap();
dev.save(&mut chunk).unwrap();
}
w.to_vec().unwrap()
}
#[test]
fn a_snapshot_round_trips_every_latch_and_both_input_levels() {
let saved = SysCtl::default_device();
let refresh = feed(&saved, "refresh");
let timer2 = feed(&saved, "timer2");
poke(&saved, "portb", B_GATE2 | B_SPEAKER | B_IOCHK_ENABLE);
poke(&saved, "porta", A_GATE_A20 | 0x40);
poke(&saved, "resetctl", C_SYS_RST | C_RST_CPU | C_FULL_RST);
saved.raise_parity_check();
refresh.set(Level::High);
timer2.set(Level::High);
let image = save_image(&saved);
let restored = SysCtl::default_device();
let gate2 = watch(&restored, "gate2");
let a20 = watch(&restored, "a20");
let reset = watch(&restored, "reset");
let reader = StateReader::new(&image).unwrap();
let chunk = reader
.load("sysctl", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
restored.load(&mut chunk.reader()).unwrap();
assert_eq!(peek(&restored, "portb"), peek(&saved, "portb"));
assert_eq!(peek(&restored, "porta"), peek(&saved, "porta"));
assert_eq!(peek(&restored, "resetctl"), peek(&saved, "resetctl"));
assert!(gate2.high(), "the levels were re-driven on load");
assert!(a20.high());
let refresh = feed(&restored, "refresh");
refresh.set(Level::High);
assert_eq!(
peek(&restored, "portb") & B_REFRESH,
peek(&saved, "portb") & B_REFRESH
);
poke(&restored, "resetctl", C_SYS_RST | C_RST_CPU | C_FULL_RST);
assert_eq!(
reset.rises(),
0,
"a repeat is not an edge, even after a load"
);
assert_eq!(save_image(&restored), image, "byte-identical");
}
}