use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::fmt;
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, Level, Resolve, WireId, WireSink};
use crate::dev::pc::apic::bus::{self, ApicBus, Delivery, EoiSink, Message, Shorthand};
use crate::machine::realize::Instance;
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "pc.ioapic";
const STATE_VERSION: u32 = 1;
pub const REGISTER_WINDOW_LEN: u64 = 0x20;
pub const DEFAULT_BASE: u64 = 0xfec0_0000;
pub const INPUTS: usize = 24;
const VERSION: u32 = 0x11;
const IOREGSEL: u64 = 0x00;
const IOWIN: u64 = 0x10;
const IDX_ID: u8 = 0x00;
const IDX_VERSION: u8 = 0x01;
const IDX_ARB: u8 = 0x02;
const IDX_REDIR: u8 = 0x10;
const ENTRY_MASK: u64 = 1 << 16;
const ENTRY_LEVEL: u64 = 1 << 15;
const ENTRY_REMOTE_IRR: u64 = 1 << 14;
const ENTRY_ACTIVE_LOW: u64 = 1 << 13;
const ENTRY_DELIVERY_STATUS: u64 = 1 << 12;
const ENTRY_LOGICAL: u64 = 1 << 11;
const ENTRY_WRITABLE: u64 =
0xff00_0000_0000_0000 | (0x0001_ffff & !(ENTRY_DELIVERY_STATUS | ENTRY_REMOTE_IRR));
const ENTRY_RESET: u64 = ENTRY_MASK;
#[derive(Debug, Clone, PartialEq, Eq)]
struct State {
id: u8,
select: u8,
redir: Vec<u64>,
pins: u32,
}
impl State {
fn new(id: u8, inputs: usize) -> State {
State {
id,
select: 0,
redir: alloc::vec![ENTRY_RESET; inputs],
pins: 0,
}
}
fn asserted(&self, index: usize) -> bool {
let high = self.pins & (1 << index) != 0;
high != (self.redir[index] & ENTRY_ACTIVE_LOW != 0)
}
fn message(&self, index: usize) -> Message {
let entry = self.redir[index];
Message {
vector: entry as u8,
delivery: Delivery(((entry >> 8) & 7) as u8),
logical: entry & ENTRY_LOGICAL != 0,
dest: (entry >> 56) as u8,
level_triggered: entry & ENTRY_LEVEL != 0,
assert: true,
}
}
fn evaluate(&mut self, index: usize, rising: bool) -> Option<Message> {
let entry = self.redir[index];
if entry & ENTRY_MASK != 0 {
return None;
}
if entry & ENTRY_LEVEL != 0 {
if !self.asserted(index) || entry & ENTRY_REMOTE_IRR != 0 {
return None;
}
self.redir[index] |= ENTRY_REMOTE_IRR;
Some(self.message(index))
} else {
rising.then(|| self.message(index))
}
}
}
struct Registers {
state: Mutex<State>,
bus: Arc<ApicBus>,
}
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(),
}
}
}
impl Registers {
fn send(&self, messages: &[Message]) {
for message in messages {
self.bus.deliver(*message, None, Shorthand::Dest);
}
}
fn set_pin(&self, index: usize, high: bool) {
let messages = {
let mut state = self.state.lock();
if index >= state.redir.len() {
return;
}
let was = state.asserted(index);
let bit = 1u32 << index;
if high {
state.pins |= bit;
} else {
state.pins &= !bit;
}
let rising = state.asserted(index) && !was;
state
.evaluate(index, rising)
.into_iter()
.collect::<Vec<_>>()
};
self.send(&messages);
}
fn read_indirect(&self, state: &State, index: u8) -> u32 {
match index {
IDX_ID => u32::from(state.id) << 24,
IDX_VERSION => VERSION | ((state.redir.len() as u32 - 1) << 16),
IDX_ARB => u32::from(state.id) << 24,
_ => {
let offset = index.wrapping_sub(IDX_REDIR) as usize;
let entry = offset / 2;
if index < IDX_REDIR || entry >= state.redir.len() {
return 0;
}
let word = state.redir[entry];
if offset.is_multiple_of(2) {
word as u32
} else {
(word >> 32) as u32
}
}
}
}
fn write_indirect(&self, state: &mut State, index: u8, value: u32) -> Option<Message> {
match index {
IDX_ID => {
state.id = ((value >> 24) & 0x0f) as u8;
None
}
IDX_VERSION | IDX_ARB => None,
_ => {
let offset = index.wrapping_sub(IDX_REDIR) as usize;
let entry = offset / 2;
if index < IDX_REDIR || entry >= state.redir.len() {
return None;
}
let shift = if offset.is_multiple_of(2) { 0 } else { 32 };
let half = 0xffff_ffffu64 << shift;
let writable = ENTRY_WRITABLE & half;
state.redir[entry] =
(state.redir[entry] & !writable) | ((u64::from(value) << shift) & writable);
state.evaluate(entry, false)
}
}
}
}
impl EoiSink for Registers {
fn eoi(&self, vector: u8) {
let messages = {
let mut state = self.state.lock();
let mut out = Vec::new();
for index in 0..state.redir.len() {
let entry = state.redir[index];
if entry & ENTRY_LEVEL == 0
|| entry & ENTRY_REMOTE_IRR == 0
|| entry as u8 != vector
{
continue;
}
state.redir[index] &= !ENTRY_REMOTE_IRR;
out.extend(state.evaluate(index, false));
}
out
};
self.send(&messages);
}
}
impl MemOps for Registers {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [a, b, c, d] = dst else {
return Err(BusError::BadAccess);
};
let state = self.state.lock();
let value = match offset {
IOREGSEL => u32::from(state.select),
IOWIN => self.read_indirect(&state, state.select),
_ => return Err(BusError::BadAccess),
};
let _ = attrs;
let bytes = value.to_le_bytes();
*a = bytes[0];
*b = bytes[1];
*c = bytes[2];
*d = bytes[3];
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [a, b, c, d] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
let value = u32::from_le_bytes([*a, *b, *c, *d]);
let message = {
let mut state = self.state.lock();
match offset {
IOREGSEL => {
state.select = value as u8;
None
}
IOWIN => {
let index = state.select;
self.write_indirect(&mut state, index, value)
}
_ => return Err(BusError::BadAccess),
}
};
if let Some(message) = message {
self.send(&[message]);
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U32, Endian::Little)
}
}
#[derive(Debug)]
pub struct IoApic {
regs: Arc<Registers>,
region: RegionRef,
pins: Mutex<Vec<Arc<InputPin>>>,
reset_id: u8,
inputs: usize,
}
#[derive(Debug)]
struct InputPin {
regs: Arc<Registers>,
index: usize,
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.index, high);
}
}
impl IoApic {
pub fn new(props: &Props) -> Result<IoApic> {
let mut r = props.reader();
let id = u8::try_from(r.or_range::<u64>("id", 0, 0..=15)?).unwrap_or(0);
let inputs = r.or_range::<u64>("inputs", INPUTS as u64, 1..=INPUTS as u64)? as usize;
let name = r.or_str("bus", bus::DEFAULT_NAME)?.to_string();
r.finish()?;
let bus = bus::attach(props, &name)?;
Ok(IoApic::with_bus(id, inputs, bus))
}
#[must_use]
pub fn default_device() -> IoApic {
IoApic::with_bus(0, INPUTS, Arc::new(ApicBus::new()))
}
#[must_use]
pub fn with_bus(id: u8, inputs: usize, bus: Arc<ApicBus>) -> IoApic {
let regs = Arc::new(Registers {
state: Mutex::with_rank(LockRank::DEVICE, State::new(id, inputs)),
bus,
});
let region: RegionRef = Arc::new(Region::io(
CLASS_NAME,
REGISTER_WINDOW_LEN,
Arc::clone(®s) as Arc<dyn MemOps>,
));
IoApic {
regs,
region,
pins: Mutex::with_rank(LockRank::LEAF, Vec::new()),
reset_id: id,
inputs,
}
}
#[must_use]
pub fn bus(&self) -> &Arc<ApicBus> {
&self.regs.bus
}
#[must_use]
pub fn inputs(&self) -> usize {
self.inputs
}
#[must_use]
pub fn entry(&self, index: usize) -> Option<u64> {
self.regs.state.lock().redir.get(index).copied()
}
fn pin_number(port: &str, inputs: usize) -> Option<usize> {
let index: usize = port.strip_prefix("irq")?.parse().ok()?;
(index < inputs).then_some(index)
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "Intel 82093AA I/O APIC",
properties: &[
PropertySpec {
name: "id",
kind: ValueKind::Uint,
required: false,
summary: "the APIC ID this part is strapped to, 0-15 (default 0)",
},
PropertySpec {
name: "inputs",
kind: ValueKind::Uint,
required: false,
summary: "how many interrupt inputs it has, 1-24 (default 24)",
},
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the APIC message bus this part sends on (default `apic`)",
},
],
construct: |props| Ok(Box::new(IoApic::new(props)?)),
};
impl Device for IoApic {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
self.regs
.bus
.attach_eoi(Arc::downgrade(&self.regs) as Weak<dyn EoiSink>);
Ok(())
}
fn reset(&self, _kind: ResetKind) {
let mut state = self.regs.state.lock();
let pins = state.pins;
*state = State::new(self.reset_id, self.inputs);
state.pins = pins;
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
let index = IoApic::pin_number(port, self.inputs)?;
let pin = Arc::new(InputPin {
regs: Arc::clone(&self.regs),
index,
inputs: FanIn::new(sources),
});
self.pins.lock().push(Arc::clone(&pin));
Some(SinkPin {
sink: pin,
line: index as u32,
})
}
fn connect(&self, port: &str, _source: crate::core::wire::WireSource) -> Result<()> {
Err(Error::Config {
at: port.to_string(),
message: String::from(
"an I/O APIC drives no wire: it sends messages on the APIC bus instead",
),
})
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.regs.state.lock();
w.write_u8(state.id)?;
w.write_u8(state.select)?;
w.write_u32(state.pins)?;
w.write_seq_len(state.redir.len() as u64)?;
for entry in &state.redir {
w.write_u64(*entry)?;
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let id = r.read_u8()?;
let select = r.read_u8()?;
let pins = r.read_u32()?;
let count = r.read_seq_len(8)? as usize;
if count != self.inputs {
return Err(Error::State(format!(
"snapshot has {count} redirection entries, this part has {}",
self.inputs
)));
}
let mut redir = Vec::with_capacity(count);
for _ in 0..count {
redir.push(r.read_u64()?);
}
let mut state = self.regs.state.lock();
*state = State {
id,
select,
redir,
pins,
};
Ok(())
}
}
impl Instance for IoApic {}
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(IoApic::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::{PortDir, PropSchema};
let mut schema = ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("id", ValueKind::Uint).range(0, 15))
.prop(PropSchema::new("inputs", ValueKind::Uint).range(1, INPUTS as u64))
.prop(PropSchema::new("bus", ValueKind::Str))
.region("")
.region("regs");
for index in 0..INPUTS {
schema = schema.port(format!("irq{index}"), PortDir::In);
}
schema
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::device::ResetKind;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::wire::{IntAckCycle, IntAckResponse, WireIdAllocator};
use crate::dev::pc::apic::LocalApic;
const VECTOR: u8 = 0x33;
const LINE: usize = 5;
struct Bench {
io: IoApic,
lapic: LocalApic,
pins: Vec<Arc<dyn WireSink>>,
src: WireId,
}
fn bench() -> Bench {
let bus = Arc::new(ApicBus::new());
let io = IoApic::with_bus(0, INPUTS, Arc::clone(&bus));
let lapic = LocalApic::with_bus(0, true, Arc::clone(&bus));
let ids = WireIdAllocator::new();
let src = ids.alloc();
let pins: Vec<Arc<dyn WireSink>> = (0..INPUTS)
.map(|index| {
io.sink(&format!("irq{index}"), &[src])
.expect("every input exists")
.sink
})
.collect();
realize(&io);
realize(&lapic);
write_lapic(&lapic, 0x0f0, 0x1ff);
Bench {
io,
lapic,
pins,
src,
}
}
fn realize(device: &dyn Device) {
let hosts = crate::core::hosts::HostObjects::new();
let mut deferred = crate::core::device::Deferred::new();
let mut ctx = crate::core::device::RealizeCtx::new(
"test",
crate::core::space::RequesterId::default(),
&mut deferred,
&hosts,
);
device.realize(&mut ctx).expect("realize cannot fail here");
deferred.drain();
}
fn ops(device: &dyn Device, name: &str) -> Arc<dyn MemOps> {
match device.region(name).expect("the region exists").kind() {
crate::core::space::RegionKind::Io(ops) => Arc::clone(ops),
_ => unreachable!("a register block is an I/O region"),
}
}
fn write_lapic(lapic: &LocalApic, offset: u64, value: u32) {
ops(lapic, "regs")
.write(offset, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("a 32-bit aligned write is legal");
}
fn read_lapic(lapic: &LocalApic, offset: u64) -> u32 {
let mut bytes = [0u8; 4];
ops(lapic, "regs")
.read(offset, &mut bytes, MemAttrs::DEFAULT)
.expect("a 32-bit aligned read is legal");
u32::from_le_bytes(bytes)
}
impl Bench {
fn write_indirect(&self, index: u8, value: u32) {
self.poke(IOREGSEL, u32::from(index));
self.poke(IOWIN, value);
}
fn read_indirect(&self, index: u8) -> u32 {
self.poke(IOREGSEL, u32::from(index));
self.peek(IOWIN)
}
fn poke(&self, offset: u64, value: u32) {
self.io
.regs
.write(offset, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("a 32-bit aligned write is legal");
}
fn peek(&self, offset: u64) -> u32 {
let mut bytes = [0u8; 4];
self.io
.regs
.read(offset, &mut bytes, MemAttrs::DEFAULT)
.expect("a 32-bit aligned read is legal");
u32::from_le_bytes(bytes)
}
fn program(&self, line: usize, low: u32, dest: u8) {
self.write_indirect(IDX_REDIR + 2 * line as u8 + 1, u32::from(dest) << 24);
self.write_indirect(IDX_REDIR + 2 * line as u8, low);
}
fn drive(&self, line: usize, level: Level) {
self.pins[line].set_level(self.src, line as u32, level);
}
fn requested(&self, vector: u8) -> bool {
let word = read_lapic(&self.lapic, 0x200 + 0x10 * u64::from(vector >> 5));
word & (1 << (vector & 31)) != 0
}
fn ack(&self) -> IntAckResponse {
self.lapic
.int_ack("intr")
.expect("a local APIC answers the acknowledge")
.acknowledge(IntAckCycle::vector_only())
}
fn eoi(&self) {
write_lapic(&self.lapic, 0x0b0, 0);
}
}
#[test]
fn the_version_register_says_how_many_inputs_there_are() {
let b = bench();
let version = b.read_indirect(IDX_VERSION);
assert_eq!(version & 0xff, VERSION, "an 82093AA");
assert_eq!(
(version >> 16) & 0xff,
INPUTS as u32 - 1,
"twenty-four inputs, reported as the highest entry"
);
}
#[test]
fn the_identification_register_keeps_four_bits() {
let b = bench();
b.write_indirect(IDX_ID, 0xff << 24);
assert_eq!(
b.read_indirect(IDX_ID) >> 24,
0x0f,
"the 82093AA carries the ID in bits 27:24 (datasheet 3.2.1)"
);
}
#[test]
fn every_entry_comes_out_of_reset_masked() {
let b = bench();
for line in 0..INPUTS {
assert_eq!(
b.read_indirect(IDX_REDIR + 2 * line as u8) & (ENTRY_MASK as u32),
ENTRY_MASK as u32,
"entry {line}"
);
}
b.drive(LINE, Level::High);
assert!(!b.requested(VECTOR), "so nothing gets through");
}
#[test]
fn an_edge_entry_sends_once_per_rising_edge() {
let b = bench();
b.program(LINE, u32::from(VECTOR), 0);
b.drive(LINE, Level::High);
assert!(b.requested(VECTOR), "the edge became a message");
assert_eq!(b.ack(), IntAckResponse::Vector(u32::from(VECTOR)));
assert!(!b.requested(VECTOR));
b.eoi();
assert!(
!b.requested(VECTOR),
"a level that never fell sends nothing"
);
b.drive(LINE, Level::Low);
b.drive(LINE, Level::High);
assert!(b.requested(VECTOR), "and a fresh edge does");
}
#[test]
fn a_level_entry_holds_its_remote_irr_until_the_end_of_interrupt() {
let b = bench();
b.program(LINE, (ENTRY_LEVEL as u32) | u32::from(VECTOR), 0);
b.drive(LINE, Level::High);
assert!(b.requested(VECTOR));
assert_eq!(
b.io.entry(LINE).unwrap() & ENTRY_REMOTE_IRR,
ENTRY_REMOTE_IRR,
"the entry latched that it had sent"
);
assert_eq!(b.ack(), IntAckResponse::Vector(u32::from(VECTOR)));
assert!(!b.requested(VECTOR));
b.eoi();
assert!(
b.requested(VECTOR),
"still asserting, so it interrupts again"
);
assert_eq!(
b.io.entry(LINE).unwrap() & ENTRY_REMOTE_IRR,
ENTRY_REMOTE_IRR
);
assert_eq!(b.ack(), IntAckResponse::Vector(u32::from(VECTOR)));
b.drive(LINE, Level::Low);
b.eoi();
assert!(!b.requested(VECTOR));
assert_eq!(b.io.entry(LINE).unwrap() & ENTRY_REMOTE_IRR, 0);
}
#[test]
fn unmasking_a_level_entry_whose_line_is_already_high_delivers() {
let b = bench();
b.program(
LINE,
(ENTRY_MASK as u32) | (ENTRY_LEVEL as u32) | u32::from(VECTOR),
0,
);
b.drive(LINE, Level::High);
assert!(!b.requested(VECTOR), "masked, so nothing yet");
b.write_indirect(
IDX_REDIR + 2 * LINE as u8,
(ENTRY_LEVEL as u32) | u32::from(VECTOR),
);
assert!(b.requested(VECTOR), "and unmasking delivers it");
}
#[test]
fn unmasking_an_edge_entry_whose_line_is_already_high_does_not() {
let b = bench();
b.program(LINE, (ENTRY_MASK as u32) | u32::from(VECTOR), 0);
b.drive(LINE, Level::High);
b.write_indirect(IDX_REDIR + 2 * LINE as u8, u32::from(VECTOR));
assert!(!b.requested(VECTOR));
}
#[test]
fn an_active_low_entry_reads_the_pin_the_other_way_up() {
let b = bench();
b.program(
LINE,
(ENTRY_ACTIVE_LOW as u32) | (ENTRY_LEVEL as u32) | u32::from(VECTOR),
0,
);
assert!(b.requested(VECTOR));
assert_eq!(b.ack(), IntAckResponse::Vector(u32::from(VECTOR)));
b.drive(LINE, Level::High);
b.eoi();
assert!(!b.requested(VECTOR), "and a high pin is idle");
}
#[test]
fn a_message_reaches_the_local_apic_the_destination_field_names() {
let bus = Arc::new(ApicBus::new());
let io = IoApic::with_bus(0, INPUTS, Arc::clone(&bus));
let zero = LocalApic::with_bus(0, true, Arc::clone(&bus));
let one = LocalApic::with_bus(1, false, Arc::clone(&bus));
realize(&io);
realize(&zero);
realize(&one);
write_lapic(&zero, 0x0f0, 0x1ff);
write_lapic(&one, 0x0f0, 0x1ff);
let ids = WireIdAllocator::new();
let src = ids.alloc();
let pin = io.sink("irq1", &[src]).unwrap().sink;
for (index, value) in [
(IDX_REDIR + 3, 1u32 << 24),
(IDX_REDIR + 2, u32::from(VECTOR)),
] {
io.regs
.write(IOREGSEL, &u32::from(index).to_le_bytes(), MemAttrs::DEFAULT)
.unwrap();
io.regs
.write(IOWIN, &value.to_le_bytes(), MemAttrs::DEFAULT)
.unwrap();
}
pin.set_level(src, 1, Level::High);
let word = read_lapic(&one, 0x200 + 0x10 * u64::from(VECTOR >> 5));
assert_ne!(word & (1 << (VECTOR & 31)), 0, "APIC 1 has it");
let word = read_lapic(&zero, 0x200 + 0x10 * u64::from(VECTOR >> 5));
assert_eq!(word & (1 << (VECTOR & 31)), 0, "and APIC 0 does not");
}
#[test]
fn a_debug_write_is_refused_and_a_debug_read_is_harmless() {
let b = bench();
b.write_indirect(IDX_VERSION, 0);
assert!(
b.io.regs
.write(IOREGSEL, &0u32.to_le_bytes(), MemAttrs::DEBUG)
.is_err()
);
let mut bytes = [0u8; 4];
b.io.regs
.read(IOWIN, &mut bytes, MemAttrs::DEBUG)
.expect("but reading the window is free");
assert_eq!(u32::from_le_bytes(bytes) & 0xff, VERSION);
assert_eq!(
b.peek(IOREGSEL),
u32::from(IDX_VERSION),
"and moved nothing"
);
}
#[test]
fn the_window_decodes_two_registers_and_nothing_between_them() {
let b = bench();
let mut bytes = [0u8; 4];
assert!(b.io.regs.read(0x04, &mut bytes, MemAttrs::DEFAULT).is_err());
assert!(b.io.regs.read(0x18, &mut bytes, MemAttrs::DEFAULT).is_err());
}
#[test]
fn a_snapshot_round_trips_the_whole_part() {
let saved = bench();
saved.program(LINE, (ENTRY_LEVEL as u32) | u32::from(VECTOR), 2);
saved.program(9, u32::from(VECTOR) + 1, 0);
saved.drive(LINE, Level::High);
saved.write_indirect(IDX_ID, 0x0e << 24);
saved.poke(IOREGSEL, u32::from(IDX_REDIR + 4));
let mut shape = MachineShape::new();
shape.add_device("ioapic", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("ioapic", CLASS.name, CLASS.version).unwrap();
saved.io.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let restored = bench();
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load("ioapic", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
restored.io.load(&mut chunk.reader()).unwrap();
let after = restored.io.regs.state.lock().clone();
let before = saved.io.regs.state.lock().clone();
assert_eq!(after, before, "every field came back");
assert!(
!restored.requested(VECTOR),
"and the interrupt already sent was not sent again"
);
let mut shape = MachineShape::new();
shape.add_device("ioapic", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("ioapic", CLASS.name, CLASS.version).unwrap();
restored.io.save(&mut chunk).unwrap();
}
assert_eq!(w.to_vec().unwrap(), bytes);
}
#[test]
fn a_reset_masks_every_entry_again() {
let b = bench();
b.program(LINE, u32::from(VECTOR), 0);
b.io.reset(ResetKind::Cold);
assert_eq!(b.io.entry(LINE).unwrap(), ENTRY_MASK);
b.drive(LINE, Level::High);
assert!(!b.requested(VECTOR));
}
}