use bitfield_struct::bitfield;
use chipset_device::interrupt::AcknowledgePicInterrupt;
use chipset_device::interrupt::LineInterruptTarget;
use chipset_device::io::IoError;
use chipset_device::io::IoResult;
use chipset_device::pio::ControlPortIoIntercept;
use chipset_device::pio::PortIoIntercept;
use chipset_device::pio::RegisterPortIoIntercept;
use chipset_device::ChipsetDevice;
use inspect::Inspect;
use inspect::InspectMut;
use inspect_counters::Counter;
use std::num::Wrapping;
use vmcore::device_state::ChangeDeviceState;
use vmcore::line_interrupt::LineInterrupt;
const PRIMARY_PIC_COMMAND_PORT: u16 = 0x20;
const PRIMARY_PIC_DATA_PORT: u16 = 0x21;
const SECONDARY_PIC_COMMAND_PORT: u16 = 0xa0;
const SECONDARY_PIC_DATA_PORT: u16 = 0xa1;
const PRIMARY_PIC_ELCR_PORT: u16 = 0x4d0;
const SECONDARY_PIC_ELCR_PORT: u16 = 0x4d1;
const PIC_CHAIN_COMMUNICATION_IRQ: u8 = 2;
const IRQ_MASK: u8 = 0b111;
const SPURIOUS_IRQ: u8 = 7;
#[derive(InspectMut)]
pub struct DualPic {
ready: LineInterrupt,
#[inspect(iter_by_index)]
port_io_regions: [Box<dyn ControlPortIoIntercept>; 3],
stats: DualPicStats,
#[inspect(flatten, with = r#"|x| inspect::iter_by_index(x).prefix("pic")"#)]
pics: [Pic; 2],
}
#[derive(Inspect, Default)]
struct DualPicStats {
#[inspect(iter_by_index)]
interrupts_per_irq: [Counter; 16],
interrupts: Counter,
}
impl DualPic {
pub fn new(ready: LineInterrupt, port_io: &mut dyn RegisterPortIoIntercept) -> Self {
let mut primary_region = port_io.new_io_region(
"primary",
(PRIMARY_PIC_COMMAND_PORT..=PRIMARY_PIC_DATA_PORT).len() as u16,
);
let mut secondary_region = port_io.new_io_region(
"secondary",
(SECONDARY_PIC_COMMAND_PORT..=SECONDARY_PIC_DATA_PORT).len() as u16,
);
let mut elcr_region = port_io.new_io_region(
"edge_level_control",
(PRIMARY_PIC_ELCR_PORT..=SECONDARY_PIC_ELCR_PORT).len() as u16,
);
primary_region.map(PRIMARY_PIC_COMMAND_PORT);
secondary_region.map(SECONDARY_PIC_COMMAND_PORT);
elcr_region.map(PRIMARY_PIC_ELCR_PORT);
DualPic {
pics: [Pic::new(true), Pic::new(false)],
ready,
port_io_regions: [primary_region, secondary_region, elcr_region],
stats: Default::default(),
}
}
fn sync_outputs(&mut self) {
self.pics[0].set_irq(
PIC_CHAIN_COMMUNICATION_IRQ,
self.pics[1].interrupt_pending(),
);
self.ready.set_level(self.pics[0].interrupt_pending());
}
fn set_irq(&mut self, n: u8, high: bool) {
if n >= 8 {
self.pics[1].set_irq(n - 8, high);
} else {
self.pics[0].set_irq(n, high);
}
self.sync_outputs();
}
}
impl AcknowledgePicInterrupt for DualPic {
fn acknowledge_interrupt(&mut self) -> Option<u8> {
let (requested, n) = self.pics[0].acknowledge_interrupt(&mut self.stats);
let irq = requested.then(|| {
if n & IRQ_MASK == PIC_CHAIN_COMMUNICATION_IRQ {
let (requested, m) = self.pics[1].acknowledge_interrupt(&mut self.stats);
assert!(requested, "pic1 ready was set");
m
} else {
n
}
});
self.sync_outputs();
irq
}
}
impl LineInterruptTarget for DualPic {
fn set_irq(&mut self, n: u32, high: bool) {
self.set_irq(n as u8, high);
}
fn valid_lines(&self) -> &[std::ops::RangeInclusive<u32>] {
&[0..=1, 3..=15]
}
}
impl ChangeDeviceState for DualPic {
fn start(&mut self) {}
async fn stop(&mut self) {}
async fn reset(&mut self) {
for pic in &mut self.pics {
pic.reset(false);
}
self.sync_outputs();
}
}
impl ChipsetDevice for DualPic {
fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
Some(self)
}
fn supports_line_interrupt_target(&mut self) -> Option<&mut dyn LineInterruptTarget> {
Some(self)
}
fn supports_acknowledge_pic_interrupt(&mut self) -> Option<&mut dyn AcknowledgePicInterrupt> {
Some(self)
}
}
impl PortIoIntercept for DualPic {
fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
if data.len() != 1 {
return IoResult::Err(IoError::InvalidAccessSize);
}
data[0] = match io_port {
PRIMARY_PIC_COMMAND_PORT => self.pics[0].read_command(&mut self.stats),
PRIMARY_PIC_DATA_PORT => self.pics[0].read_data(),
SECONDARY_PIC_COMMAND_PORT => self.pics[1].read_command(&mut self.stats),
SECONDARY_PIC_DATA_PORT => self.pics[1].read_data(),
PRIMARY_PIC_ELCR_PORT => self.pics[0].elcr,
SECONDARY_PIC_ELCR_PORT => self.pics[1].elcr,
_ => return IoResult::Err(IoError::InvalidRegister),
};
IoResult::Ok
}
fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
if data.len() == 2
&& (io_port == PRIMARY_PIC_COMMAND_PORT || io_port == SECONDARY_PIC_COMMAND_PORT)
{
let &[mut prim, mut sec] = data else {
unreachable!()
};
if io_port == SECONDARY_PIC_COMMAND_PORT {
(prim, sec) = (sec, prim);
}
self.pics[0].write_command(prim);
self.pics[1].write_command(sec);
self.sync_outputs();
return IoResult::Ok;
}
if data.len() != 1 {
return IoResult::Err(IoError::InvalidAccessSize);
}
match io_port {
PRIMARY_PIC_COMMAND_PORT => self.pics[0].write_command(data[0]),
PRIMARY_PIC_DATA_PORT => self.pics[0].write_data(data[0]),
SECONDARY_PIC_COMMAND_PORT => self.pics[1].write_command(data[0]),
SECONDARY_PIC_DATA_PORT => self.pics[1].write_data(data[0]),
PRIMARY_PIC_ELCR_PORT => self.pics[0].elcr = data[0],
SECONDARY_PIC_ELCR_PORT => self.pics[1].elcr = data[0],
_ => return IoResult::Err(IoError::InvalidRegister),
}
self.sync_outputs();
IoResult::Ok
}
}
#[derive(Debug, Copy, Clone, Inspect)]
struct Pic {
init: InitStage,
#[inspect(skip)]
primary: bool,
#[inspect(hex)]
icw2: u8,
#[inspect(binary)]
imr: u8,
ocw3: Ocw3,
#[inspect(binary)]
isr: u8,
#[inspect(binary)]
elcr: u8,
#[inspect(binary)]
lines: u8,
#[inspect(binary)]
line_low_latch: u8,
}
#[derive(Copy, Clone, Debug, Inspect)]
enum InitStage {
Uninitialized,
ExpectingIcw2,
ExpectingIcw3,
ExpectingIcw4,
Initialized,
}
impl Pic {
fn new(primary: bool) -> Self {
Pic {
imr: 0,
init: InitStage::Uninitialized,
icw2: if primary { 0 } else { 8 },
ocw3: Ocw3(0),
isr: 0,
elcr: 0,
primary,
lines: 0,
line_low_latch: !0,
}
}
fn reset(&mut self, during_icw1: bool) {
*self = Self {
lines: self.lines,
elcr: if during_icw1 { self.elcr } else { 0 },
..Self::new(self.primary)
};
}
fn irr(&self) -> u8 {
self.lines & (self.elcr | self.line_low_latch)
}
fn set_irq(&mut self, n: u8, high: bool) {
let bit = 1 << n;
if high {
if !matches!(self.init, InitStage::Initialized) {
tracelimit::warn_ratelimited!(
primary = self.primary,
?n,
"interrupt request sent to uninitialized PIC"
);
}
self.lines |= bit;
} else {
self.lines &= !bit;
self.line_low_latch |= bit;
}
}
fn ready_vec(&self) -> u8 {
let isr = Wrapping(self.isr);
let highest_isr = isr & -isr;
let higher_not_isr = highest_isr - Wrapping(1);
self.irr() & !self.imr & higher_not_isr.0
}
fn interrupt_pending(&self) -> bool {
self.ready_vec() != 0
}
fn pending_line(&self) -> Option<u8> {
let m = self.ready_vec();
if m != 0 {
Some(m.trailing_zeros() as u8)
} else {
None
}
}
fn acknowledge_interrupt(&mut self, stats: &mut DualPicStats) -> (bool, u8) {
if !matches!(self.init, InitStage::Initialized) {
tracelimit::warn_ratelimited!(
primary = self.primary,
"interrupt servicing sent to uninitialized PIC"
);
}
let (requested, irq) = if let Some(n) = self.pending_line() {
let bit = 1 << n;
self.line_low_latch &= !bit;
self.isr |= bit;
stats.interrupts.increment();
stats.interrupts_per_irq[if self.primary { 0 } else { 8 } + n as usize].increment();
(true, n)
} else {
(false, SPURIOUS_IRQ)
};
assert!(self.icw2 & IRQ_MASK == 0);
(requested, self.icw2 | irq)
}
fn eoi(&mut self, n: Option<u8>) {
tracing::trace!(primary = self.primary, n, "eoi");
let bit = match n {
Some(level) => 1 << level,
None => self.isr & self.isr.wrapping_neg(),
};
self.isr &= !bit;
}
fn read_command(&mut self, stats: &mut DualPicStats) -> u8 {
if self.ocw3.p() {
self.ocw3.set_p(false);
let (int, irq) = self.acknowledge_interrupt(stats);
((int as u8) << 7) | (irq & IRQ_MASK)
} else if self.ocw3.rr() {
if self.ocw3.ris() {
self.isr
} else {
self.irr()
}
} else {
0
}
}
fn read_data(&self) -> u8 {
self.imr
}
fn write_command(&mut self, data: u8) {
const INIT_BIT: u8 = 0b10000;
const COMMAND_BIT: u8 = 0b1000;
if data & INIT_BIT != 0 {
if data != 0b00010001 {
tracelimit::error_ratelimited!(primary = self.primary, ?data, "unsupported ICW1");
}
self.reset(true);
self.init = InitStage::ExpectingIcw2;
} else {
if !matches!(self.init, InitStage::Initialized) {
tracelimit::warn_ratelimited!(
primary = self.primary,
?data,
"OCW sent to uninitialized PIC"
);
}
if data & COMMAND_BIT == 0 {
let ocw2 = Ocw2(data);
match (ocw2.r(), ocw2.sl(), ocw2.eoi()) {
(true, _, _) | (false, false, false) => {
tracelimit::error_ratelimited!(
primary = self.primary,
?data,
"unsupported OCW2"
)
}
(false, true, true) => self.eoi(Some(ocw2.level())),
(false, false, true) => self.eoi(None),
(false, true, false) => {} }
} else {
self.ocw3 = Ocw3(data);
if self.ocw3.esmm() || self.ocw3.smm() {
tracelimit::error_ratelimited!(
primary = self.primary,
?data,
"unsupported OCW3"
);
}
}
}
}
fn write_data(&mut self, data: u8) {
match self.init {
InitStage::Uninitialized | InitStage::Initialized => {
self.imr = data; }
InitStage::ExpectingIcw2 => {
if data & IRQ_MASK != 0 {
tracelimit::error_ratelimited!(primary = self.primary, ?data, "invalid ICW2");
}
self.icw2 = data & !IRQ_MASK;
self.init = InitStage::ExpectingIcw3;
}
InitStage::ExpectingIcw3 => {
if self.primary {
if data != (1 << PIC_CHAIN_COMMUNICATION_IRQ) {
tracelimit::error_ratelimited!(
primary = self.primary,
?data,
"invalid primary ICW3"
);
}
} else {
if data != PIC_CHAIN_COMMUNICATION_IRQ {
tracelimit::error_ratelimited!(
primary = self.primary,
?data,
"invalid secondary ICW3"
);
}
}
self.init = InitStage::ExpectingIcw4;
}
InitStage::ExpectingIcw4 => {
if data != 1 {
if data == 3 {
tracing::debug!(
primary = self.primary,
"got ICW4 of 3, this is expected for Linux boot but not any other time"
);
} else {
tracelimit::error_ratelimited!(
primary = self.primary,
?data,
"unsupported ICW4"
);
}
};
self.init = InitStage::Initialized;
}
}
}
}
#[bitfield(u8)]
struct Ocw2 {
#[bits(3)]
level: u8,
#[bits(2)]
_command: u8,
eoi: bool,
sl: bool,
r: bool,
}
#[derive(Inspect)]
#[bitfield(u8)]
struct Ocw3 {
ris: bool,
rr: bool,
p: bool,
#[bits(2)]
_command: u8,
smm: bool,
esmm: bool,
_reserved: bool,
}
mod save_restore {
use super::DualPic;
use super::InitStage;
use super::Ocw3;
use super::Pic;
use super::IRQ_MASK;
use thiserror::Error;
use vmcore::save_restore::RestoreError;
use vmcore::save_restore::SaveError;
use vmcore::save_restore::SaveRestore;
mod state {
use mesh::payload::Protobuf;
use vmcore::save_restore::SavedStateRoot;
#[derive(Protobuf, SavedStateRoot)]
#[mesh(package = "chipset.pic")]
pub struct SavedState {
#[mesh(1)]
pub(super) primary: SavedPic,
#[mesh(2)]
pub(super) secondary: SavedPic,
}
#[derive(Protobuf)]
#[mesh(package = "chipset.pic")]
pub struct SavedPic {
#[mesh(1)]
pub init: SavedInitStage,
#[mesh(2)]
pub icw2: u8,
#[mesh(3)]
pub imr: u8,
#[mesh(4)]
pub ocw3: u8,
#[mesh(5)]
pub isr: u8,
#[mesh(6)]
pub elcr: u8,
#[mesh(7)]
pub line_low_latch: u8,
}
#[derive(Protobuf)]
#[mesh(package = "chipset.pic")]
pub enum SavedInitStage {
#[mesh(1)]
Uninitialized,
#[mesh(2)]
ExpectingIcw2,
#[mesh(3)]
ExpectingIcw3,
#[mesh(4)]
ExpectingIcw4,
#[mesh(5)]
Initialized,
}
}
#[derive(Debug, Error)]
enum Error {
#[error("invalid icw2 value {0:#x}")]
InvalidIcw2(u8),
}
impl state::SavedPic {
fn restore(self, pic: &mut Pic) -> Result<(), Error> {
let Self {
init,
icw2,
imr,
ocw3,
isr,
elcr,
line_low_latch,
} = self;
pic.init = match init {
state::SavedInitStage::Uninitialized => InitStage::Uninitialized,
state::SavedInitStage::ExpectingIcw2 => InitStage::ExpectingIcw2,
state::SavedInitStage::ExpectingIcw3 => InitStage::ExpectingIcw3,
state::SavedInitStage::ExpectingIcw4 => InitStage::ExpectingIcw4,
state::SavedInitStage::Initialized => InitStage::Initialized,
};
if icw2 & IRQ_MASK != 0 {
return Err(Error::InvalidIcw2(icw2));
}
pic.icw2 = icw2;
pic.imr = imr;
pic.ocw3 = Ocw3(ocw3);
pic.isr = isr;
pic.elcr = elcr;
pic.line_low_latch = line_low_latch;
Ok(())
}
fn save(pic: &Pic) -> Self {
let &Pic {
init,
primary: _,
icw2,
imr,
ocw3,
isr,
elcr,
lines: _,
line_low_latch,
} = pic;
Self {
init: match init {
InitStage::Uninitialized => state::SavedInitStage::Uninitialized,
InitStage::ExpectingIcw2 => state::SavedInitStage::ExpectingIcw2,
InitStage::ExpectingIcw3 => state::SavedInitStage::ExpectingIcw3,
InitStage::ExpectingIcw4 => state::SavedInitStage::ExpectingIcw4,
InitStage::Initialized => state::SavedInitStage::Initialized,
},
icw2,
imr,
ocw3: ocw3.0,
isr,
elcr,
line_low_latch,
}
}
}
impl SaveRestore for DualPic {
type SavedState = state::SavedState;
fn save(&mut self) -> Result<Self::SavedState, SaveError> {
let Self {
ready: _,
stats: _,
port_io_regions: _,
pics: [primary, secondary],
} = &self;
Ok(state::SavedState {
primary: state::SavedPic::save(primary),
secondary: state::SavedPic::save(secondary),
})
}
fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
let state::SavedState { primary, secondary } = state;
primary
.restore(&mut self.pics[0])
.map_err(|err| RestoreError::Other(err.into()))?;
secondary
.restore(&mut self.pics[1])
.map_err(|err| RestoreError::Other(err.into()))?;
self.sync_outputs();
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chipset_device::pio::ExternallyManagedPortIoIntercepts;
use chipset_device::pio::PortIoIntercept;
use vmcore::line_interrupt::test_helpers::TestLineInterruptTarget;
const IV_BASE: u8 = 0x30;
fn create_pic() -> (impl Fn() -> bool, DualPic) {
let ready = TestLineInterruptTarget::new_arc();
let mut pic = DualPic::new(
LineInterrupt::new_with_target("ready", ready.clone(), 0),
&mut ExternallyManagedPortIoIntercepts,
);
pic.io_write(0x20, &[0x11]).unwrap();
pic.io_write(0x21, &[IV_BASE]).unwrap(); pic.io_write(0x21, &[0x04]).unwrap();
pic.io_write(0x21, &[0x01]).unwrap();
pic.io_write(0xa0, &[0x11]).unwrap();
pic.io_write(0xa1, &[IV_BASE + 8]).unwrap(); pic.io_write(0xa1, &[0x02]).unwrap();
pic.io_write(0xa1, &[0x01]).unwrap();
(move || ready.is_high(0), pic)
}
fn send_eoi(pic: &mut DualPic, consts: PicTestConstants) {
let (v, _) = consts;
match v {
0..=7 => pic
.io_write(
PRIMARY_PIC_COMMAND_PORT,
&[Ocw2::new().with_eoi(true).with_sl(true).with_level(v).0],
)
.unwrap(),
8..=15 => {
pic.io_write(
PRIMARY_PIC_COMMAND_PORT,
&[Ocw2::new()
.with_eoi(true)
.with_sl(true)
.with_level(PIC_CHAIN_COMMUNICATION_IRQ)
.0],
)
.unwrap();
pic.io_write(
SECONDARY_PIC_COMMAND_PORT,
&[Ocw2::new().with_eoi(true).with_sl(true).with_level(v - 8).0],
)
.unwrap();
}
_ => unreachable!(),
}
}
#[test]
fn test_create_pic() {
let (ready, pic) = create_pic();
assert!(matches!(pic.pics[0].init, InitStage::Initialized));
assert!(matches!(pic.pics[1].init, InitStage::Initialized));
assert!(!ready());
}
type PicTestConstants = (u8, usize);
const PRIMARY: PicTestConstants = (0, 0);
const SECONDARY: PicTestConstants = (8, 1);
#[test]
fn test_edge_interrupt_primary() {
test_edge_interrupt(PRIMARY);
}
#[test]
fn test_edge_interrupt_secondary() {
test_edge_interrupt(SECONDARY);
}
fn test_edge_interrupt(consts: PicTestConstants) {
let (ready, mut pic) = create_pic();
let (v, i) = consts;
pic.set_irq(v, true);
assert!(ready());
assert!(pic.pics[i].irr() == 0b1);
pic.set_irq(v, false);
assert!(pic.pics[i].irr() == 0b0);
let pending = pic.acknowledge_interrupt();
assert!(pending.is_none());
assert!(!ready());
pic.set_irq(v, true);
assert!(ready());
assert!(pic.pics[i].irr() == 0b1);
let pending = pic.acknowledge_interrupt();
assert_eq!(pending, Some(IV_BASE + v));
assert!(pic.pics[i].irr() == 0);
assert!(pic.pics[i].isr == 0b1);
send_eoi(&mut pic, consts);
assert!(pic.pics[i].irr() == 0);
assert!(pic.pics[i].isr == 0);
let pending = pic.acknowledge_interrupt();
assert!(pending.is_none());
assert!(!ready());
pic.set_irq(v, true);
assert!(pic.pics[i].irr() == 0b0);
pic.set_irq(v, false);
pic.set_irq(v, true);
assert!(pic.pics[i].irr() == 0b1);
assert!(ready());
pic.set_irq(v, false);
assert!(pic.pics[i].irr() == 0b0);
}
#[test]
fn test_level_interrupts_primary() {
test_level_interrupts(PRIMARY);
}
#[test]
fn test_level_interrupts_secondary() {
test_level_interrupts(SECONDARY);
}
fn test_level_interrupts(consts: PicTestConstants) {
let (ready, mut pic) = create_pic();
let (v, i) = consts;
pic.io_write(
match i {
0 => PRIMARY_PIC_ELCR_PORT,
1 => SECONDARY_PIC_ELCR_PORT,
_ => unreachable!(),
},
&[0b1],
)
.unwrap();
pic.set_irq(v, true);
assert!(ready());
assert!(pic.pics[i].irr() == 0b1);
pic.set_irq(v, false);
assert!(pic.pics[i].irr() == 0b0);
let pending = pic.acknowledge_interrupt();
assert!(pending.is_none());
assert!(!ready());
pic.set_irq(v, true);
assert!(ready());
assert!(pic.pics[i].irr() == 0b1);
let pending = pic.acknowledge_interrupt();
assert_eq!(pending, Some(IV_BASE + v));
assert!(pic.pics[i].irr() == 0b1);
assert!(pic.pics[i].isr == 0b1);
send_eoi(&mut pic, consts);
assert!(pic.pics[i].irr() == 0b1);
assert!(pic.pics[i].isr == 0);
let pending = pic.acknowledge_interrupt();
assert_eq!(pending, Some(IV_BASE + v));
pic.set_irq(v, false);
send_eoi(&mut pic, consts);
assert!(pic.pics[i].irr() == 0);
let pending = pic.acknowledge_interrupt();
assert!(pending.is_none());
}
#[test]
fn test_multiple_edge_interrupt_primary() {
test_multiple_edge_interrupt(PRIMARY);
}
#[test]
fn test_multiple_edge_interrupt_secondary() {
test_multiple_edge_interrupt(SECONDARY);
}
fn test_multiple_edge_interrupt(consts: PicTestConstants) {
let (ready, mut pic) = create_pic();
let (v, i) = consts;
pic.set_irq(v, true);
assert!(ready());
assert!(pic.pics[i].irr() == 0b1);
pic.set_irq(v, false);
assert!(pic.pics[i].irr() == 0b0);
pic.set_irq(v + 1, true);
assert!(pic.pics[i].irr() == 0b10);
pic.set_irq(v + 1, false);
assert!(pic.pics[i].irr() == 0b00);
assert!(!ready());
pic.set_irq(v, true);
assert!(ready());
assert!(pic.pics[i].irr() == 0b1);
pic.set_irq(v + 1, true);
assert!(pic.pics[i].irr() == 0b11);
let pending = pic.acknowledge_interrupt();
assert_eq!(pending, Some(IV_BASE + v));
assert!(pic.pics[i].irr() == 0b10);
assert!(pic.pics[i].isr == 0b01);
send_eoi(&mut pic, consts);
assert!(pic.pics[i].irr() == 0b10);
assert!(pic.pics[i].isr == 0b00);
assert!(ready());
let pending = pic.acknowledge_interrupt();
assert_eq!(pending, Some(IV_BASE + 1 + v));
assert!(!ready());
}
#[test]
fn test_non_specific_eois() {
let (_, mut pic) = create_pic();
pic.set_irq(5, true);
assert_eq!(pic.acknowledge_interrupt(), Some(IV_BASE + 5));
pic.set_irq(3, true);
assert_eq!(pic.acknowledge_interrupt(), Some(IV_BASE + 3));
pic.set_irq(1, true);
assert_eq!(pic.acknowledge_interrupt(), Some(IV_BASE + 1));
assert_eq!(pic.pics[0].isr, 0b101010);
pic.io_write(
PRIMARY_PIC_COMMAND_PORT,
&[Ocw2::new().with_eoi(true).with_sl(false).0],
)
.unwrap();
assert_eq!(pic.pics[0].isr, 0b101000);
}
}