use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
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, IntAck, IntAckCycle, IntAckResponse, Level, Resolve, WireId, WireSink, WireSource,
};
use crate::machine::realize::Instance;
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "pc.pic";
const STATE_VERSION: u32 = 1;
pub const REGISTER_WINDOW_LEN: u64 = 2;
pub const ELCR_WINDOW_LEN: u64 = 1;
pub const INPUTS: u8 = 8;
const SPURIOUS_LEVEL: u8 = 7;
const ICW1_INIT: u8 = 0x10;
const ICW1_IC4: u8 = 0x01;
const ICW1_SNGL: u8 = 0x02;
const ICW1_LTIM: u8 = 0x08;
const ICW4_AEOI: u8 = 0x02;
const ICW4_SFNM: u8 = 0x10;
const OCW3_SELECT: u8 = 0x08;
const OCW3_RIS: u8 = 0x01;
const OCW3_RR: u8 = 0x02;
const OCW3_POLL: u8 = 0x04;
const OCW3_SMM: u8 = 0x20;
const OCW3_ESMM: u8 = 0x40;
const OCW2_CLEAR_ROTATE_AEOI: u8 = 0b000;
const OCW2_EOI: u8 = 0b001;
const OCW2_NOP: u8 = 0b010;
const OCW2_SPECIFIC_EOI: u8 = 0b011;
const OCW2_SET_ROTATE_AEOI: u8 = 0b100;
const OCW2_ROTATE_EOI: u8 = 0b101;
const OCW2_SET_PRIORITY: u8 = 0b110;
const OCW2_ROTATE_SPECIFIC_EOI: u8 = 0b111;
const POLL_PENDING: u8 = 0x80;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Master,
Slave,
Single,
}
impl Mode {
fn parse(text: &str) -> Mode {
match text {
"slave" => Mode::Slave,
"single" => Mode::Single,
_ => Mode::Master,
}
}
fn as_str(self) -> &'static str {
match self {
Mode::Master => "master",
Mode::Slave => "slave",
Mode::Single => "single",
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct State {
irr: u8,
isr: u8,
imr: u8,
vector_base: u8,
icw3: u8,
init_step: u8,
expect_icw4: bool,
single: bool,
level_triggered: bool,
auto_eoi: bool,
sfnm: bool,
priority_base: u8,
read_isr: bool,
special_mask: bool,
poll_armed: bool,
rotate_on_aeoi: bool,
pin_level: u8,
elcr: u8,
elcr_written: bool,
}
impl State {
fn line_is_level(&self, line: u8) -> bool {
if self.elcr_written {
self.elcr & (1 << line) != 0
} else {
self.level_triggered
}
}
fn level_at(&self, index: u8) -> u8 {
self.priority_base.wrapping_add(index) & 7
}
fn in_service_priority(&self) -> u8 {
(0..INPUTS)
.find(|i| self.isr & (1 << self.level_at(*i)) != 0)
.unwrap_or(INPUTS)
}
fn resolve(&self) -> Option<u8> {
let candidates = self.irr & !self.imr;
if candidates == 0 {
return None;
}
let blocked_at = if self.special_mask {
INPUTS
} else {
self.in_service_priority()
};
for i in 0..INPUTS {
let level = self.level_at(i);
if candidates & (1 << level) == 0 {
continue;
}
return if i < blocked_at || (i == blocked_at && self.sfnm) {
Some(level)
} else {
None
};
}
None
}
fn nonspecific_eoi(&mut self) -> Option<u8> {
let index = self.in_service_priority();
if index >= INPUTS {
return None;
}
let level = self.level_at(index);
self.isr &= !(1 << level);
Some(level)
}
fn rotate_to(&mut self, level: u8) {
self.priority_base = level.wrapping_add(1) & 7;
}
fn begin_init(&mut self, icw1: u8) {
self.expect_icw4 = icw1 & ICW1_IC4 != 0;
self.single = icw1 & ICW1_SNGL != 0;
self.level_triggered = icw1 & ICW1_LTIM != 0;
self.imr = 0;
self.isr = 0;
self.read_isr = false;
self.special_mask = false;
self.poll_armed = false;
self.priority_base = 0;
self.rotate_on_aeoi = false;
self.auto_eoi = false;
self.sfnm = false;
self.irr = self.pin_level & self.level_mask();
self.init_step = 1;
}
fn level_mask(&self) -> u8 {
let mut mask = 0u8;
for line in 0..INPUTS {
if self.line_is_level(line) {
mask |= 1 << line;
}
}
mask
}
fn write_elcr(&mut self, value: u8) {
self.elcr = value;
self.elcr_written = true;
let level = self.level_mask();
self.irr = (self.irr & !level) | (self.pin_level & level);
}
fn init_word(&mut self, value: u8) {
match self.init_step {
1 => {
self.vector_base = value & 0xf8;
self.init_step = if !self.single {
2
} else if self.expect_icw4 {
3
} else {
0
};
}
2 => {
self.icw3 = value;
self.init_step = if self.expect_icw4 { 3 } else { 0 };
}
_ => {
self.auto_eoi = value & ICW4_AEOI != 0;
self.sfnm = value & ICW4_SFNM != 0;
self.init_step = 0;
}
}
}
}
#[derive(Debug)]
struct Registers {
state: Mutex<State>,
out: Mutex<Option<WireSource>>,
acks: Mutex<[Option<Weak<dyn IntAck>>; INPUTS as usize]>,
mode: Mode,
}
#[derive(Debug)]
struct Elcr {
regs: Arc<Registers>,
}
impl MemOps for Elcr {
fn read(&self, _offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
*byte = self.regs.state.lock().elcr;
Ok(())
}
fn write(&self, _offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
self.regs.write_elcr(*value);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U8, Endian::Little)
}
}
#[derive(Debug)]
struct InputPin {
regs: Arc<Registers>,
line: u8,
inputs: FanIn,
}
impl WireSink for InputPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let high = self.inputs.resolve(Resolve::Or).is_high();
self.regs.set_pin(self.line, high);
self.regs.refresh();
}
}
#[derive(Debug)]
pub struct Pic8259 {
regs: Arc<Registers>,
region: RegionRef,
elcr: RegionRef,
pins: Mutex<Vec<Arc<InputPin>>>,
}
impl Registers {
fn set_pin(&self, line: u8, high: bool) {
let mask = 1u8 << line;
let mut state = self.state.lock();
let was_high = state.pin_level & mask != 0;
if high {
state.pin_level |= mask;
} else {
state.pin_level &= !mask;
}
if state.line_is_level(line) {
if high {
state.irr |= mask;
} else {
state.irr &= !mask;
}
} else if high && !was_high {
state.irr |= mask;
}
}
fn elcr_mask(&self) -> u8 {
match self.mode {
Mode::Master => !0b0000_0111,
Mode::Slave => !0b0010_0001,
Mode::Single => 0xff,
}
}
fn write_elcr(&self, value: u8) {
{
let mut state = self.state.lock();
let masked = value & self.elcr_mask();
state.write_elcr(masked);
}
self.refresh();
}
fn drive(&self, asserted: bool) {
let out = self.out.lock().clone();
if let Some(out) = out {
out.set(Level::from_bool(asserted));
}
}
fn refresh(&self) {
let asserted = self.state.lock().resolve().is_some();
self.drive(asserted);
}
fn cascades(&self, state: &State, level: u8) -> bool {
self.mode == Mode::Master && state.icw3 & (1 << level) != 0
}
fn take_request(&self, state: &mut State) -> Option<u8> {
let level = state.resolve()?;
if !state.line_is_level(level) {
state.irr &= !(1 << level);
}
if state.auto_eoi {
if state.rotate_on_aeoi {
state.rotate_to(level);
}
} else {
state.isr |= 1 << level;
}
Some(level)
}
fn poll_byte(&self, state: &mut State) -> u8 {
state.poll_armed = false;
match self.take_request(state) {
Some(level) => POLL_PENDING | level,
None => 0,
}
}
fn read_port(&self, port: u8, debug: bool) -> u8 {
let mut state = self.state.lock();
match port {
0 if state.poll_armed && !debug => self.poll_byte(&mut state),
0 if state.read_isr => state.isr,
0 => state.irr,
_ => state.imr,
}
}
fn write_command(&self, state: &mut State, value: u8) {
if value & ICW1_INIT != 0 {
state.begin_init(value);
return;
}
if value & OCW3_SELECT != 0 {
if value & OCW3_ESMM != 0 {
state.special_mask = value & OCW3_SMM != 0;
}
if value & OCW3_RR != 0 {
state.read_isr = value & OCW3_RIS != 0;
}
if value & OCW3_POLL != 0 {
state.poll_armed = true;
}
return;
}
let level = value & 7;
match value >> 5 {
OCW2_EOI => {
state.nonspecific_eoi();
}
OCW2_SPECIFIC_EOI => state.isr &= !(1 << level),
OCW2_ROTATE_EOI => {
if let Some(cleared) = state.nonspecific_eoi() {
state.rotate_to(cleared);
}
}
OCW2_ROTATE_SPECIFIC_EOI => {
state.isr &= !(1 << level);
state.rotate_to(level);
}
OCW2_SET_PRIORITY => state.rotate_to(level),
OCW2_SET_ROTATE_AEOI => state.rotate_on_aeoi = true,
OCW2_CLEAR_ROTATE_AEOI => state.rotate_on_aeoi = false,
_ => debug_assert_eq!(value >> 5, OCW2_NOP),
}
}
fn write_port(&self, port: u8, value: u8) {
{
let mut state = self.state.lock();
if port == 0 {
self.write_command(&mut state, value);
} else if state.init_step != 0 {
state.init_word(value);
} else {
state.imr = value;
}
}
self.refresh();
}
}
impl IntAck for Registers {
fn acknowledge(&self, cycle: IntAckCycle) -> IntAckResponse {
let (vector, delegate) = {
let mut state = self.state.lock();
match self.take_request(&mut state) {
Some(level) => {
let slave = self
.cascades(&state, level)
.then(|| self.acks.lock()[level as usize].clone())
.flatten();
(u32::from(state.vector_base | level), slave)
}
None => (u32::from(state.vector_base | SPURIOUS_LEVEL), None),
}
};
let response = delegate
.as_ref()
.and_then(Weak::upgrade)
.map(|slave| slave.acknowledge(cycle))
.filter(|response| response.answered())
.unwrap_or(IntAckResponse::Vector(vector));
self.refresh();
response
}
}
impl MemOps for Registers {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
*byte = self.read_port((offset & 1) as u8, attrs.debug);
if !attrs.debug {
self.refresh();
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
self.write_port((offset & 1) as u8, *value);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U8, Endian::Little)
}
}
impl Pic8259 {
pub fn new(props: &Props) -> Result<Pic8259> {
let mut r = props.reader();
let mode = Mode::parse(r.or_enum("mode", "master", &["master", "slave", "single"])?);
r.finish()?;
Ok(Pic8259::with_mode(mode))
}
#[must_use]
pub fn default_device() -> Pic8259 {
Pic8259::with_mode(Mode::Master)
}
fn with_mode(mode: Mode) -> Pic8259 {
let regs = Arc::new(Registers {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
out: Mutex::with_rank(LockRank::LEAF, None),
acks: Mutex::with_rank(LockRank::LEAF, [const { None }; INPUTS as usize]),
mode,
});
let region: RegionRef = Arc::new(Region::io(
CLASS_NAME,
REGISTER_WINDOW_LEN,
Arc::clone(®s) as Arc<dyn MemOps>,
));
let elcr: RegionRef = Arc::new(Region::io(
"pc.pic.elcr",
ELCR_WINDOW_LEN,
Arc::new(Elcr {
regs: Arc::clone(®s),
}) as Arc<dyn MemOps>,
));
Pic8259 {
regs,
region,
elcr,
pins: Mutex::with_rank(LockRank::LEAF, Vec::new()),
}
}
#[must_use]
pub fn mode(&self) -> &'static str {
self.regs.mode.as_str()
}
#[must_use]
pub fn int_asserted(&self) -> bool {
self.regs.state.lock().resolve().is_some()
}
#[must_use]
pub fn in_service(&self) -> u8 {
self.regs.state.lock().isr
}
#[must_use]
pub fn requested(&self) -> u8 {
self.regs.state.lock().irr
}
fn pin_number(port: &str) -> Option<u8> {
let level: u8 = port.strip_prefix("ir")?.parse().ok()?;
(level < INPUTS).then_some(level)
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "Intel 8259A programmable interrupt controller",
properties: &[PropertySpec {
name: "mode",
kind: ValueKind::Str,
required: false,
summary: "which chip of a cascade this is: master, slave or single (default master)",
}],
construct: |props| Ok(Box::new(Pic8259::new(props)?)),
};
impl Device for Pic8259 {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut state = self.regs.state.lock();
let pins = state.pin_level;
*state = State::default();
state.pin_level = pins;
state.irr = state.pin_level & state.level_mask();
}
self.regs.drive(false);
}
fn region(&self, name: &str) -> Option<RegionRef> {
match name {
"" | "regs" => Some(Arc::clone(&self.region)),
"elcr" => Some(Arc::clone(&self.elcr)),
_ => None,
}
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
let line = Pic8259::pin_number(port)?;
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: u32::from(line),
})
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != "int" {
return Err(Error::Config {
at: port.to_string(),
message: String::from("an 8259A drives one pin, `int`"),
});
}
*self.regs.out.lock() = Some(source);
Ok(())
}
fn attach_int_ack(&self, port: &str, ack: Weak<dyn IntAck>) {
if let Some(line) = Pic8259::pin_number(port) {
self.regs.acks.lock()[line as usize] = Some(ack);
}
}
fn int_ack(&self, port: &str) -> Option<Arc<dyn IntAck>> {
(port == "int").then(|| Arc::clone(&self.regs) as Arc<dyn IntAck>)
}
fn announce(&self, port: &str) {
if port == "int" {
self.regs.refresh();
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.regs.state.lock();
for byte in [
state.irr,
state.isr,
state.imr,
state.vector_base,
state.icw3,
state.init_step,
state.priority_base,
state.pin_level,
state.elcr,
] {
w.write_u8(byte)?;
}
for flag in [
state.expect_icw4,
state.single,
state.level_triggered,
state.auto_eoi,
state.sfnm,
state.read_isr,
state.special_mask,
state.poll_armed,
state.rotate_on_aeoi,
state.elcr_written,
] {
w.write_bool(flag)?;
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let state = State {
irr: r.read_u8()?,
isr: r.read_u8()?,
imr: r.read_u8()?,
vector_base: r.read_u8()?,
icw3: r.read_u8()?,
init_step: r.read_u8()?,
priority_base: r.read_u8()?,
pin_level: r.read_u8()?,
elcr: r.read_u8()?,
expect_icw4: r.read_bool()?,
single: r.read_bool()?,
level_triggered: r.read_bool()?,
auto_eoi: r.read_bool()?,
sfnm: r.read_bool()?,
read_isr: r.read_bool()?,
special_mask: r.read_bool()?,
poll_armed: r.read_bool()?,
rotate_on_aeoi: r.read_bool()?,
elcr_written: r.read_bool()?,
};
if state.init_step > 3 {
return Err(Error::State(format!(
"snapshot has the 8259A at initialization step {}, of at most 3",
state.init_step
)));
}
if state.priority_base >= INPUTS {
return Err(Error::State(format!(
"snapshot makes level {} the highest priority, of {INPUTS} inputs",
state.priority_base
)));
}
*self.regs.state.lock() = state;
self.regs.refresh();
Ok(())
}
}
impl Instance for Pic8259 {}
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(Pic8259::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::{PortDir, PropSchema};
let mut schema = ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("mode", ValueKind::Str).values(&["master", "slave", "single"]))
.region("")
.region("regs")
.region("elcr")
.port("int", PortDir::Out);
for line in 0..INPUTS {
schema = schema.port(format!("ir{line}"), PortDir::In);
}
schema
}
#[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};
const ICW1_CASCADE: u8 = ICW1_INIT | ICW1_IC4;
const ICW4_8086: u8 = 0x01;
const MASTER_BASE: u8 = 0x08;
const SLAVE_BASE: u8 = 0x70;
#[derive(Debug, Default)]
struct Probe {
level: AtomicU32,
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.level
.store(u32::from(level.is_high()), Ordering::Relaxed);
}
}
impl Probe {
fn high(&self) -> bool {
self.level.load(Ordering::Relaxed) != 0
}
}
struct Bench {
pic: Pic8259,
ids: WireIdAllocator,
src: WireId,
pins: Vec<Arc<dyn WireSink>>,
int: Arc<Wire>,
int_id: WireId,
probe: Arc<Probe>,
}
fn bench(mode: &str) -> Bench {
let pic = Pic8259::new(&Props::new().with("mode", mode)).expect("a legal mode");
let ids = WireIdAllocator::new();
let src = ids.alloc();
let pins: Vec<Arc<dyn WireSink>> = (0..INPUTS)
.map(|line| {
pic.sink(&format!("ir{line}"), &[src])
.expect("every IR pin exists")
.sink
})
.collect();
let int_id = ids.alloc();
let probe = Arc::new(Probe::default());
let int = Wire::builder()
.source(int_id)
.sink(Arc::clone(&probe) as Arc<dyn WireSink>, 0)
.build_shared();
pic.connect("int", WireSource::new(Arc::clone(&int), int_id))
.expect("an 8259A drives int");
Bench {
pic,
ids,
src,
pins,
int,
int_id,
probe,
}
}
impl Bench {
fn poke(&self, port: u64, value: u8) {
self.pic
.regs
.write(port, &[value], MemAttrs::DEFAULT)
.expect("a byte write is legal");
}
fn peek(&self, port: u64) -> u8 {
let mut byte = [0u8; 1];
self.pic
.regs
.read(port, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
byte[0]
}
fn peek_debug(&self, port: u64) -> u8 {
let mut byte = [0u8; 1];
self.pic
.regs
.read(port, &mut byte, MemAttrs::DEBUG)
.expect("a debug byte read is legal");
byte[0]
}
fn set(&self, line: u8, high: bool) {
self.pins[line as usize].set_level(self.src, u32::from(line), Level::from_bool(high));
}
fn raise(&self, line: u8) {
self.set(line, true);
}
fn init(&self, icw1: u8, base: u8, icw3: u8, icw4: u8) {
self.poke(0, icw1);
self.poke(1, base);
if icw1 & ICW1_SNGL == 0 {
self.poke(1, icw3);
}
if icw1 & ICW1_IC4 != 0 {
self.poke(1, icw4);
}
}
fn ack(&self) -> u32 {
self.pic
.regs
.acknowledge(IntAckCycle::vector_only())
.vector()
.expect("an 8259A answers every acknowledge, spuriously if need be")
}
fn elcr_ops(&self) -> Elcr {
Elcr {
regs: Arc::clone(&self.pic.regs),
}
}
fn poke_elcr(&self, value: u8) {
self.elcr_ops()
.write(0, &[value], MemAttrs::DEFAULT)
.expect("a byte write is legal");
}
fn peek_elcr(&self) -> u8 {
let mut byte = [0u8; 1];
self.elcr_ops()
.read(0, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
byte[0]
}
}
#[test]
fn the_initialization_sequence_leaves_the_documented_state() {
let b = bench("master");
b.poke(1, 0xff); b.poke(0, ICW1_CASCADE | ICW1_LTIM);
{
let state = b.pic.regs.state.lock();
assert_eq!(state.init_step, 1, "ICW2 is expected next");
assert_eq!(state.imr, 0, "ICW1 clears the mask");
assert_eq!(state.isr, 0);
assert_eq!(state.priority_base, 0, "IR0 is highest");
assert!(!state.read_isr, "and a read of port 0 returns IRR");
assert!(!state.special_mask);
assert!(state.level_triggered);
}
b.poke(1, MASTER_BASE | 0x03); assert_eq!(b.pic.regs.state.lock().init_step, 2);
b.poke(1, 0x04);
assert_eq!(b.pic.regs.state.lock().init_step, 3);
b.poke(1, ICW4_8086 | ICW4_AEOI);
let state = b.pic.regs.state.lock();
assert_eq!(state.init_step, 0, "the sequence is over");
assert_eq!(state.vector_base, MASTER_BASE);
assert_eq!(state.icw3, 0x04);
assert!(state.auto_eoi);
assert!(!state.sfnm);
}
#[test]
fn a_chip_told_it_is_alone_is_not_sent_an_icw3() {
let b = bench("single");
b.poke(0, ICW1_INIT | ICW1_SNGL | ICW1_IC4);
b.poke(1, MASTER_BASE);
assert_eq!(
b.pic.regs.state.lock().init_step,
3,
"ICW4 comes straight after ICW2"
);
b.poke(1, ICW4_8086);
assert_eq!(b.pic.regs.state.lock().init_step, 0);
}
#[test]
fn a_request_asserts_int_and_the_acknowledge_returns_its_vector() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
assert!(!b.probe.high(), "nothing is pending yet");
b.raise(0);
assert!(b.probe.high());
assert_eq!(b.peek(0), 0x01, "IRR bit 0");
assert_eq!(b.ack(), u32::from(MASTER_BASE));
assert_eq!(b.pic.in_service(), 0x01);
assert!(!b.probe.high(), "and INT falls, because nothing else is up");
}
#[test]
fn a_lower_priority_request_waits_for_the_end_of_interrupt() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(1);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 1));
b.raise(3);
assert!(!b.probe.high(), "IR3 is below IR1, which is in service");
assert_eq!(b.pic.requested() & 0x08, 0x08, "but it is still requested");
b.poke(0, 0x20); assert!(b.probe.high(), "and now it gets through");
assert_eq!(b.ack(), u32::from(MASTER_BASE | 3));
assert_eq!(b.pic.in_service(), 0x08);
}
#[test]
fn a_higher_priority_request_preempts_one_in_service() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(5);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 5));
b.raise(2);
assert!(b.probe.high(), "IR2 outranks IR5");
assert_eq!(b.ack(), u32::from(MASTER_BASE | 2));
assert_eq!(b.pic.in_service(), 0x24, "both are in service, nested");
b.poke(0, 0x20);
assert_eq!(b.pic.in_service(), 0x20);
b.poke(0, 0x20);
assert_eq!(b.pic.in_service(), 0x00);
}
#[test]
fn rotating_on_end_of_interrupt_moves_the_level_to_the_back() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(0);
assert_eq!(b.ack(), u32::from(MASTER_BASE));
b.poke(0, 0xa0); assert_eq!(
b.pic.regs.state.lock().priority_base,
1,
"IR1 is now highest and IR0 lowest"
);
b.raise(0);
b.raise(4);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 4));
}
#[test]
fn a_masked_line_does_not_assert_int() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.poke(1, 0x02); assert_eq!(b.peek(1), 0x02, "OCW1 reads back");
b.raise(1);
assert!(!b.probe.high());
assert_eq!(b.peek(0), 0x02, "the request is latched all the same");
b.poke(1, 0x00);
assert!(b.probe.high(), "unmasking releases it");
assert_eq!(b.ack(), u32::from(MASTER_BASE | 1));
}
#[test]
fn special_mask_mode_lets_a_handler_unmask_below_itself() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(2);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 2));
b.raise(6);
assert!(!b.probe.high(), "IR2 is in service and blocks IR6");
b.poke(0, OCW3_SELECT | OCW3_ESMM | OCW3_SMM);
assert!(b.probe.high(), "special mask mode ignores the ISR");
assert_eq!(b.ack(), u32::from(MASTER_BASE | 6));
}
#[test]
fn acknowledging_with_nothing_pending_gives_the_spurious_vector() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
assert_eq!(b.ack(), u32::from(MASTER_BASE | SPURIOUS_LEVEL));
assert_eq!(b.pic.in_service(), 0, "and no in-service bit is set");
assert!(!b.probe.high());
}
#[test]
fn automatic_end_of_interrupt_sets_no_in_service_bit() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086 | ICW4_AEOI);
b.raise(4);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 4));
assert_eq!(b.pic.in_service(), 0);
b.raise(6);
assert!(b.probe.high());
}
#[test]
fn an_edge_triggered_line_requests_once_but_a_level_re_requests() {
let edge = bench("master");
edge.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
edge.raise(3); assert_eq!(edge.ack(), u32::from(MASTER_BASE | 3));
edge.poke(0, 0x20);
assert!(
!edge.probe.high(),
"the latch was consumed; only a new rising edge requests again"
);
edge.set(3, false);
edge.set(3, true);
assert!(edge.probe.high());
let level = bench("master");
level.init(ICW1_CASCADE | ICW1_LTIM, MASTER_BASE, 0x04, ICW4_8086);
level.raise(3);
assert_eq!(level.ack(), u32::from(MASTER_BASE | 3));
level.poke(0, 0x20);
assert!(
level.probe.high(),
"the line is still asserted, so the request is still there"
);
level.set(3, false);
assert!(!level.probe.high());
assert_eq!(level.pic.requested() & 0x08, 0);
}
#[test]
fn the_elcr_selects_the_trigger_mode_one_line_at_a_time() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
assert_eq!(b.peek_elcr(), 0, "an AT powers up with every line edge");
b.poke_elcr(0x20);
assert_eq!(b.peek_elcr(), 0x20);
b.raise(5); assert_eq!(b.ack(), u32::from(MASTER_BASE | 5));
b.poke(0, 0x20);
assert!(
b.probe.high(),
"a level line still asserted after EOI requests again"
);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 5));
b.poke(0, 0x20);
b.poke_elcr(0x00);
assert!(b.probe.high(), "the request it already held survives");
assert_eq!(b.ack(), u32::from(MASTER_BASE | 5));
b.poke(0, 0x20);
assert!(
!b.probe.high(),
"and as an edge input it does not re-request"
);
b.set(5, false);
b.set(5, true);
assert!(b.probe.high(), "only a fresh edge gets through now");
}
#[test]
fn the_lines_the_board_hardwires_read_back_as_edge() {
let master = bench("master");
master.poke_elcr(0xff);
assert_eq!(master.peek_elcr(), 0xf8, "IR0, IR1 and IR2 are fixed");
let slave = bench("slave");
slave.poke_elcr(0xff);
assert_eq!(slave.peek_elcr(), 0xde, "IRQ8 and IRQ13 are fixed");
let single = bench("single");
single.poke_elcr(0xff);
assert_eq!(single.peek_elcr(), 0xff);
}
#[test]
fn an_unwritten_elcr_leaves_the_trigger_mode_to_ltim() {
let b = bench("master");
b.init(ICW1_CASCADE | ICW1_LTIM, MASTER_BASE, 0x04, ICW4_8086);
b.raise(4);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 4));
b.poke(0, 0x20);
assert!(b.probe.high(), "LTIM still says level");
b.poke_elcr(0x00);
assert_eq!(b.ack(), u32::from(MASTER_BASE | 4));
b.poke(0, 0x20);
assert!(!b.probe.high());
}
#[test]
fn a_debug_write_to_the_elcr_is_refused_but_a_debug_read_is_not() {
let b = bench("master");
b.poke_elcr(0x18);
let mut byte = [0u8; 1];
b.elcr_ops().read(0, &mut byte, MemAttrs::DEBUG).unwrap();
assert_eq!(byte[0], 0x18);
assert!(b.elcr_ops().write(0, &[0x00], MemAttrs::DEBUG).is_err());
assert_eq!(b.peek_elcr(), 0x18, "and nothing changed");
}
#[test]
fn a_poll_acknowledges_and_a_debug_read_does_not() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(5);
b.poke(0, OCW3_SELECT | OCW3_POLL);
assert_eq!(
b.peek_debug(0),
0x20,
"a debug read answers with IRR and leaves the poll armed"
);
assert_eq!(b.pic.in_service(), 0, "and acknowledges nothing");
assert!(b.probe.high());
assert_eq!(b.peek(0), POLL_PENDING | 5);
assert_eq!(b.pic.in_service(), 0x20, "the guest's read did acknowledge");
assert!(!b.probe.high());
b.poke(0, OCW3_SELECT | OCW3_POLL);
assert_eq!(b.peek(0) & POLL_PENDING, 0);
}
#[test]
fn ocw3_selects_which_register_port_zero_reads() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(1);
b.ack();
b.raise(0);
assert_eq!(b.peek(0), 0x01, "IRR by default");
b.poke(0, OCW3_SELECT | OCW3_RR | OCW3_RIS);
assert_eq!(b.peek(0), 0x02, "and now ISR");
b.poke(0, OCW3_SELECT | OCW3_RR);
assert_eq!(b.peek(0), 0x01);
}
#[test]
fn a_debug_write_is_refused_because_no_value_is_harmless() {
let b = bench("master");
assert!(b.pic.regs.write(0, &[0x20], MemAttrs::DEBUG).is_err());
assert!(b.pic.regs.write(1, &[0xff], MemAttrs::DEBUG).is_err());
}
#[test]
fn an_access_that_is_not_a_single_byte_is_refused() {
let b = bench("master");
assert!(
b.pic
.regs
.read(0, &mut [0u8; 2], MemAttrs::DEFAULT)
.is_err()
);
assert!(b.pic.regs.write(0, &[0u8; 2], MemAttrs::DEFAULT).is_err());
}
fn cascade() -> (Bench, Bench) {
let master = bench("master");
let slave = bench("slave");
let slave_int = slave.ids.alloc();
let ir2 = master.pic.sink("ir2", &[slave_int]).expect("IR2 exists");
let net = Wire::builder()
.source(slave_int)
.sink(ir2.sink, ir2.line)
.build_shared();
slave
.pic
.connect("int", WireSource::new(net, slave_int))
.expect("a slave drives int");
let ack = slave.pic.int_ack("int").expect("a slave answers INTA");
master.pic.attach_int_ack("ir2", Arc::downgrade(&ack));
master.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
slave.init(ICW1_CASCADE, SLAVE_BASE, 0x02, ICW4_8086);
(master, slave)
}
#[test]
fn a_cascaded_slave_supplies_the_vector_and_the_master_records_the_level() {
let (master, slave) = cascade();
slave.raise(3); assert!(
master.probe.high(),
"the slave's INT reached the master's IR2"
);
assert_eq!(
master.ack(),
u32::from(SLAVE_BASE | 3),
"the second INTA pulse is answered by the slave"
);
assert_eq!(master.pic.in_service(), 0x04, "master ISR bit 2");
assert_eq!(slave.pic.in_service(), 0x08, "slave ISR bit 3");
assert!(!master.probe.high());
slave.poke(0, 0x20);
assert_eq!(slave.pic.in_service(), 0);
assert_eq!(master.pic.in_service(), 0x04);
master.poke(0, 0x20);
assert_eq!(master.pic.in_service(), 0);
}
#[test]
fn a_cascade_level_the_master_does_not_know_about_is_answered_by_the_master() {
let (master, slave) = cascade();
master.init(ICW1_CASCADE, MASTER_BASE, 0x00, ICW4_8086);
slave.raise(3);
assert_eq!(master.ack(), u32::from(MASTER_BASE | 2));
}
#[test]
fn a_snapshot_round_trips_the_whole_chip() {
let saved = bench("master");
saved.init(ICW1_CASCADE | ICW1_LTIM, MASTER_BASE, 0x04, ICW4_8086);
saved.raise(1);
saved.ack();
saved.raise(4);
saved.poke(1, 0x40); saved.poke(0, 0xc0 | 3); saved.poke(0, OCW3_SELECT | OCW3_RR | OCW3_RIS);
saved.poke(0, OCW3_SELECT | OCW3_POLL);
saved.poke_elcr(0x28);
let mut shape = MachineShape::new();
shape.add_device("pic", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("pic", CLASS.name, CLASS.version).unwrap();
saved.pic.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let restored = bench("master");
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load("pic", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
restored.pic.load(&mut chunk.reader()).unwrap();
let after = restored.pic.regs.state.lock().clone();
let before = saved.pic.regs.state.lock().clone();
assert_eq!(after, before, "every field came back");
assert!(
restored.probe.high(),
"and the INT output was recomputed from it"
);
let mut shape = MachineShape::new();
shape.add_device("pic", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("pic", CLASS.name, CLASS.version).unwrap();
restored.pic.save(&mut chunk).unwrap();
}
assert_eq!(w.to_vec().unwrap(), bytes);
}
#[test]
fn a_reset_puts_every_line_down() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(0);
assert!(b.probe.high());
b.pic.reset(ResetKind::Cold);
assert!(!b.probe.high());
assert_eq!(b.pic.requested(), 0);
assert_eq!(b.pic.in_service(), 0);
}
#[test]
fn a_reset_does_not_forget_which_lines_the_board_is_still_driving() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(3);
assert_eq!(b.pic.requested(), 1 << 3);
b.pic.reset(ResetKind::Cold);
assert_eq!(b.pic.requested(), 0, "the edge sense circuit is reset");
b.set(3, true);
assert_eq!(
b.pic.requested(),
0,
"a level that never moved was read as a rising edge"
);
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.poke_elcr(1 << 3);
assert_eq!(
b.pic.requested(),
1 << 3,
"the chip forgot the board was still driving IR3"
);
}
#[test]
fn properties_and_pins_are_checked_rather_than_ignored() {
let pic = Pic8259::new(&Props::new().with("mode", "slave")).expect("slave is a mode");
assert_eq!(pic.mode(), "slave");
assert!(Pic8259::new(&Props::new().with("mode", "primary")).is_err());
assert!(Pic8259::new(&Props::new().with("mdoe", "master")).is_err());
let pic = Pic8259::default_device();
assert_eq!(pic.mode(), "master");
assert!(pic.sink("ir8", &[WireId::new(1)]).is_none());
assert!(pic.sink("int", &[WireId::new(1)]).is_none());
assert!(pic.int_ack("ir0").is_none());
assert!(
pic.connect("ir0", WireSource::new(dummy_wire(), WireId::new(1)))
.is_err()
);
assert!(pic.region("").is_some());
assert_eq!(
pic.region("elcr").expect("the ELCR is published").len(),
ELCR_WINDOW_LEN
);
assert!(pic.region("porta").is_none());
}
fn dummy_wire() -> Arc<Wire> {
Wire::builder().source(WireId::new(1)).build_shared()
}
#[test]
fn the_int_net_settles_low_when_the_last_request_is_taken() {
let b = bench("master");
b.init(ICW1_CASCADE, MASTER_BASE, 0x04, ICW4_8086);
b.raise(7);
assert!(b.probe.high());
b.pic.announce("int");
assert!(b.probe.high());
assert_eq!(b.int.level_of(b.int_id), Some(Level::High));
b.ack();
b.pic.announce("int");
assert!(!b.probe.high());
}
}