diff options
author | Miriam Zimmerman <mutexlox@google.com> | 2019-02-15 12:14:38 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-04-03 18:14:09 -0700 |
commit | e8243e326ad918d4e5ab1633abaa769e811e8670 (patch) | |
tree | 28bf2559f7b341a2fb2d3b7606550755f21a7acb | |
parent | ef495df7c1d9fc806f04b0d05c3ae734fe2fa158 (diff) | |
download | crosvm-e8243e326ad918d4e5ab1633abaa769e811e8670.tar crosvm-e8243e326ad918d4e5ab1633abaa769e811e8670.tar.gz crosvm-e8243e326ad918d4e5ab1633abaa769e811e8670.tar.bz2 crosvm-e8243e326ad918d4e5ab1633abaa769e811e8670.tar.lz crosvm-e8243e326ad918d4e5ab1633abaa769e811e8670.tar.xz crosvm-e8243e326ad918d4e5ab1633abaa769e811e8670.tar.zst crosvm-e8243e326ad918d4e5ab1633abaa769e811e8670.zip |
Add PIC device.
The PIC device that this commit provides isn't quite complete: It needs to interact with a userspace APIC in order to properly route interrupts, but crosvm does not yet have a userspace APIC. In the interest of not making this CL too much larger, the userspace APIC implementation will come in a future CL. BUG=chromium:908689 TEST=Unit tests in file. Integration testing is blocked on rest of split-irqchip being implemented. Change-Id: Id1f23da12fa7b83511a2a4df895b0cfacdbc559e Reviewed-on: https://chromium-review.googlesource.com/1475057 Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com> Tested-by: Miriam Zimmerman <mutexlox@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Zach Reizner <zachr@chromium.org>
-rw-r--r-- | devices/src/lib.rs | 2 | ||||
-rw-r--r-- | devices/src/pic.rs | 1139 |
2 files changed, 1141 insertions, 0 deletions
diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 07b61c5..2bac47d 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -33,6 +33,7 @@ mod cmos; mod i8042; mod ioapic; mod pci; +mod pic; mod pit; pub mod pl030; mod proxy; @@ -52,6 +53,7 @@ pub use self::ioapic::Ioapic; pub use self::pci::{ Ac97Dev, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, }; +pub use self::pic::Pic; pub use self::pit::{Pit, PitError}; pub use self::pl030::Pl030; pub use self::proxy::Error as ProxyError; diff --git a/devices/src/pic.rs b/devices/src/pic.rs new file mode 100644 index 0000000..cdc4d39 --- /dev/null +++ b/devices/src/pic.rs @@ -0,0 +1,1139 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Software implementation of an Intel 8259A Programmable Interrupt Controller +// This is a legacy device used by older OSs and briefly during start-up by +// modern OSs that use a legacy BIOS. +// The PIC is connected to the Local APIC on CPU0. + +// Terminology note: The 8259A spec refers to "master" and "slave" PITs; the "slave"s are daisy +// chained to the "master"s. +// For the purposes of both using more descriptive terms and avoiding terms with lots of charged +// emotional context, this file refers to them instead as "primary" and "secondary" PITs. + +use BusDevice; + +#[repr(usize)] +#[derive(Debug, Clone, Copy, PartialEq)] +enum PicSelect { + Primary = 0, + Secondary = 1, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +enum PicInitState { + Icw1 = 0, + Icw2 = 1, + Icw3 = 2, + Icw4 = 3, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +struct PicState { + last_irr: u8, // Edge detection. + irr: u8, // Interrupt Request Register. + imr: u8, // Interrupt Mask Register. + isr: u8, // Interrupt Service Register. + priority_add: u8, // Highest priority, for priority rotation. + irq_base: u8, + read_reg_select: bool, + poll: bool, + special_mask: bool, + auto_eoi: bool, + rotate_on_auto_eoi: bool, + special_fully_nested_mode: bool, + // PIC takes either 3 or 4 bytes of initialization command word during + // initialization. use_4_byte_icw is true if 4 bytes of ICW are needed. + use_4_byte_icw: bool, + // "Edge/Level Control Registers", for edge trigger selection. + // When a particular bit is set, the corresponding IRQ is in level-triggered mode. Otherwise it + // is in edge-triggered mode. + elcr: u8, + elcr_mask: u8, + init_state: Option<PicInitState>, +} + +pub struct Pic { + // TODO(mutexlox): Implement an APIC and add necessary state to the Pic. + + // index 0 (aka PicSelect::Primary) is the primary pic, the rest are secondary. + pics: [PicState; 2], +} + +const PIC_NUM_PINS: u8 = 16; + +// Register offsets. +const PIC_PRIMARY: u64 = 0x20; +const PIC_PRIMARY_COMMAND: u64 = PIC_PRIMARY; +const PIC_PRIMARY_DATA: u64 = PIC_PRIMARY + 1; +const PIC_PRIMARY_ELCR: u64 = 0x4d0; + +const PIC_SECONDARY: u64 = 0xa0; +const PIC_SECONDARY_COMMAND: u64 = PIC_SECONDARY; +const PIC_SECONDARY_DATA: u64 = PIC_SECONDARY + 1; +const PIC_SECONDARY_ELCR: u64 = 0x4d1; + +const LEVEL_HIGH: bool = true; +const LEVEL_LOW: bool = false; +const INVALID_PRIORITY: u8 = 8; +const SPURIOUS_IRQ: u8 = 0x07; +const PRIMARY_PIC_CASCADE_PIN: u8 = 2; +const PRIMARY_PIC_CASCADE_PIN_MASK: u8 = 0x04; +const PRIMARY_PIC_MAX_IRQ: u8 = 7; + +// Command Words +const ICW1_MASK: u8 = 0x10; +const OCW3_MASK: u8 = 0x08; + +// ICW1 bits +const ICW1_NEED_ICW4: u8 = 0x01; // ICW4 needed +const ICW1_SINGLE_PIC_MODE: u8 = 0x02; +const ICW1_LEVEL_TRIGGER_MODE: u8 = 0x08; + +const ICW2_IRQ_BASE_MASK: u8 = 0xf8; + +const ICW4_SPECIAL_FULLY_NESTED_MODE: u8 = 0x10; +const ICW4_AUTO_EOI: u8 = 0x02; + +// OCW2 bits +const OCW2_IRQ_MASK: u8 = 0x07; +const OCW2_COMMAND_MASK: u8 = 0xe0; +#[derive(Debug, Clone, Copy, PartialEq, enumn::N)] +enum Ocw2 { + RotateAutoEoiClear = 0x00, + NonSpecificEoi = 0x20, + NoOp = 0x40, + SpecificEoi = 0x60, + RotateAutoEoiSet = 0x80, + RotateNonSpecificEoi = 0xa0, + SetPriority = 0xc0, + RotateSpecificEoi = 0xe0, +} + +// OCW3 bits +const OCW3_POLL_COMMAND: u8 = 0x04; +const OCW3_READ_REGISTER: u8 = 0x02; +// OCW3_READ_IRR (0x00) intentionally omitted. +const OCW3_READ_ISR: u8 = 0x01; +const OCW3_SPECIAL_MASK: u8 = 0x40; +const OCW3_SPECIAL_MASK_VALUE: u8 = 0x20; + +impl BusDevice for Pic { + fn debug_label(&self) -> String { + "userspace PIC".to_string() + } + + fn write(&mut self, offset: u64, data: &[u8]) { + if data.len() != 1 { + warn!("PIC: Bad write size: {}", data.len()); + return; + } + match offset { + PIC_PRIMARY_COMMAND => self.pic_write_command(PicSelect::Primary, data[0]), + PIC_PRIMARY_DATA => self.pic_write_data(PicSelect::Primary, data[0]), + PIC_PRIMARY_ELCR => self.pic_write_elcr(PicSelect::Primary, data[0]), + PIC_SECONDARY_COMMAND => self.pic_write_command(PicSelect::Secondary, data[0]), + PIC_SECONDARY_DATA => self.pic_write_data(PicSelect::Secondary, data[0]), + PIC_SECONDARY_ELCR => self.pic_write_elcr(PicSelect::Secondary, data[0]), + _ => warn!("PIC: Invalid write to offset {}", offset), + } + } + + fn read(&mut self, offset: u64, data: &mut [u8]) { + if data.len() != 1 { + warn!("PIC: Bad read size: {}", data.len()); + return; + } + data[0] = match offset { + PIC_PRIMARY_COMMAND => self.pic_read_command(PicSelect::Primary), + PIC_PRIMARY_DATA => self.pic_read_data(PicSelect::Primary), + PIC_PRIMARY_ELCR => self.pic_read_elcr(PicSelect::Primary), + PIC_SECONDARY_COMMAND => self.pic_read_command(PicSelect::Secondary), + PIC_SECONDARY_DATA => self.pic_read_data(PicSelect::Secondary), + PIC_SECONDARY_ELCR => self.pic_read_elcr(PicSelect::Secondary), + _ => { + warn!("PIC: Invalid read from offset {}", offset); + return; + } + }; + } +} + +impl Pic { + pub fn new() -> Pic { + let mut primary_pic: PicState = Default::default(); + let mut secondary_pic: PicState = Default::default(); + // These two masks are taken from KVM code (but do not appear in the 8259 specification). + + // These IRQ lines are edge triggered, and so have 0 bits in the masks: + // - IRQs 0, 1, 8, and 13 are dedicated to special I/O devices on the system board. + // - IRQ 2 is the primary pic's cascade line. + // The primary pic has IRQs 0-7. + primary_pic.elcr_mask = !((1 << 0) | (1 << 1) | (1 << 2)); + // The secondary PIC has IRQs 8-15, so we subtract 8 from the IRQ number to get the bit + // that should be masked here. In this case, bits 8 - 8 = 0 and 13 - 8 = 5. + secondary_pic.elcr_mask = !((1 << 0) | (1 << 5)); + // TODO(mutexlox): Add logic to initialize APIC interrupt-related fields. + + Pic { + pics: [primary_pic, secondary_pic], + } + } + + pub fn service_irq(&mut self, irq: u8, level: bool) -> bool { + assert!(irq <= 15, "Unexpectedly high value irq: {} vs 15", irq); + + let pic = if irq <= PRIMARY_PIC_MAX_IRQ { + PicSelect::Primary + } else { + PicSelect::Secondary + }; + Pic::set_irq_internal(&mut self.pics[pic as usize], irq & 7, level); + + self.update_irq() + } + + /// Determines whether the (primary) PIC is completely masked. + pub fn masked(&self) -> bool { + self.pics[PicSelect::Primary as usize].imr == 0xFF + } + + /// Determines whether the PIC has an interrupt ready. + pub fn has_interrupt(&self) -> bool { + self.get_irq(PicSelect::Primary).is_some() + } + + /// Determines the external interrupt number that the PIC is prepared to inject, if any. + pub fn get_external_interrupt(&mut self) -> Option<u8> { + let irq_primary = if let Some(irq) = self.get_irq(PicSelect::Primary) { + irq + } else { + // The architecturally correct behavior in this case is to inject a spurious interrupt. + // Although this case only occurs as a result of a race condition where the interrupt + // might also be avoided entirely. Here we return `None` to avoid the interrupt + // entirely. The KVM unit test OS, which several unit tests rely upon, doesn't + // properly handle spurious interrupts. Also spurious interrupts are much more common + // in this code than real hardware because the hardware race is much much much smaller. + return None; + }; + + Pic::interrupt_ack(&mut self.pics[PicSelect::Primary as usize], irq_primary); + let int_num = if irq_primary == PRIMARY_PIC_CASCADE_PIN { + // IRQ on secondary pic. + let irq_secondary = if let Some(irq) = self.get_irq(PicSelect::Secondary) { + Pic::interrupt_ack(&mut self.pics[PicSelect::Secondary as usize], irq); + irq + } else { + SPURIOUS_IRQ + }; + self.pics[PicSelect::Secondary as usize].irq_base + irq_secondary + } else { + self.pics[PicSelect::Primary as usize].irq_base + irq_primary + }; + + self.update_irq(); + Some(int_num) + } + + fn pic_read_command(&mut self, pic_type: PicSelect) -> u8 { + if self.pics[pic_type as usize].poll { + let (ret, update_irq_needed) = self.poll_read(pic_type); + self.pics[pic_type as usize].poll = false; + + if update_irq_needed { + self.update_irq(); + } + + ret + } else { + if self.pics[pic_type as usize].read_reg_select { + self.pics[pic_type as usize].isr + } else { + self.pics[pic_type as usize].irr + } + } + } + + fn pic_read_data(&mut self, pic_type: PicSelect) -> u8 { + if self.pics[pic_type as usize].poll { + let (ret, update_needed) = self.poll_read(pic_type); + self.pics[pic_type as usize].poll = false; + if update_needed { + self.update_irq(); + } + ret + } else { + self.pics[pic_type as usize].imr + } + } + + fn pic_read_elcr(&mut self, pic_type: PicSelect) -> u8 { + self.pics[pic_type as usize].elcr + } + + fn pic_write_command(&mut self, pic_type: PicSelect, value: u8) { + if value & ICW1_MASK != 0 { + Pic::init_command_word_1(&mut self.pics[pic_type as usize], value); + } else if value & OCW3_MASK != 0 { + Pic::operation_command_word_3(&mut self.pics[pic_type as usize], value); + } else { + self.operation_command_word_2(pic_type, value); + } + } + + fn pic_write_data(&mut self, pic_type: PicSelect, value: u8) { + match self.pics[pic_type as usize].init_state { + Some(PicInitState::Icw1) | None => { + if self.pics[pic_type as usize].init_state.is_none() { + debug!( + "PIC: {:?}: Uninitialized data write of {:#x}", + pic_type, value + ); + } + self.pics[pic_type as usize].imr = value; + self.update_irq(); + } + Some(PicInitState::Icw2) => { + self.pics[pic_type as usize].irq_base = value & ICW2_IRQ_BASE_MASK; + self.pics[pic_type as usize].init_state = Some(PicInitState::Icw3); + } + Some(PicInitState::Icw3) => { + if self.pics[pic_type as usize].use_4_byte_icw { + self.pics[pic_type as usize].init_state = Some(PicInitState::Icw4); + } else { + self.pics[pic_type as usize].init_state = Some(PicInitState::Icw1); + } + } + Some(PicInitState::Icw4) => { + self.pics[pic_type as usize].special_fully_nested_mode = + (value & ICW4_SPECIAL_FULLY_NESTED_MODE) != 0; + self.pics[pic_type as usize].auto_eoi = (value & ICW4_AUTO_EOI) != 0; + self.pics[pic_type as usize].init_state = Some(PicInitState::Icw1); + } + } + } + + fn pic_write_elcr(&mut self, pic_type: PicSelect, value: u8) { + self.pics[pic_type as usize].elcr = value & !self.pics[pic_type as usize].elcr; + } + + fn reset_pic(pic: &mut PicState) { + let edge_irr = pic.irr & !pic.elcr; + + pic.last_irr = 0; + pic.irr &= pic.elcr; + pic.imr = 0; + pic.priority_add = 0; + pic.special_mask = false; + pic.read_reg_select = false; + if !pic.use_4_byte_icw { + pic.special_fully_nested_mode = false; + pic.auto_eoi = false; + } + pic.init_state = Some(PicInitState::Icw2); + + for irq in 0..PIC_NUM_PINS / 2 { + if edge_irr & (1 << irq) != 0 { + Pic::clear_isr(pic, irq); + } + } + } + + /// Determine the priority and whether an update_irq call is needed. + fn poll_read(&mut self, pic_type: PicSelect) -> (u8, bool) { + if let Some(mut irq) = self.get_irq(pic_type) { + irq &= 0xff; + if pic_type == PicSelect::Secondary { + self.pics[PicSelect::Primary as usize].isr &= !PRIMARY_PIC_CASCADE_PIN_MASK; + self.pics[PicSelect::Primary as usize].irr &= !PRIMARY_PIC_CASCADE_PIN_MASK; + } + self.pics[pic_type as usize].irr &= !(1 << irq); + Pic::clear_isr(&mut self.pics[pic_type as usize], irq); + let update_irq_needed = + pic_type == PicSelect::Secondary && irq != PRIMARY_PIC_CASCADE_PIN; + (irq, update_irq_needed) + } else { + // Spurious interrupt + (SPURIOUS_IRQ, true) + } + } + + fn get_irq(&self, pic_type: PicSelect) -> Option<u8> { + let pic = &self.pics[pic_type as usize]; + let mut irq_bitmap = pic.irr & !pic.imr; + let priority = if let Some(p) = Pic::get_priority(pic, irq_bitmap) { + p + } else { + return None; + }; + + // If the primary is in fully-nested mode, the IRQ coming from the secondary is not taken + // into account for the priority computation below. + irq_bitmap = pic.isr; + if pic_type == PicSelect::Primary && pic.special_fully_nested_mode { + irq_bitmap &= !PRIMARY_PIC_CASCADE_PIN_MASK; + } + let new_priority = Pic::get_priority(pic, irq_bitmap).unwrap_or(INVALID_PRIORITY); + if priority < new_priority { + // Higher priority found. IRQ should be generated. + Some((priority + pic.priority_add) & 7) + } else { + None + } + } + + fn clear_isr(pic: &mut PicState, irq: u8) { + assert!(irq <= 7, "Unexpectedly high value for irq: {} vs 7", irq); + pic.isr &= !(1 << irq); + } + + fn update_irq(&mut self) -> bool { + if let Some(_) = self.get_irq(PicSelect::Secondary) { + // If secondary pic has an IRQ request, signal primary's cascade pin. + Pic::set_irq_internal( + &mut self.pics[PicSelect::Primary as usize], + PRIMARY_PIC_CASCADE_PIN, + LEVEL_HIGH, + ); + Pic::set_irq_internal( + &mut self.pics[PicSelect::Primary as usize], + PRIMARY_PIC_CASCADE_PIN, + LEVEL_LOW, + ); + } + + if let Some(_) = self.get_irq(PicSelect::Primary) { + // TODO(mutexlox): Signal local interrupt on APIC bus. + // Note: this does not check if the interrupt is succesfully injected into + // the CPU, just whether or not one is fired. + true + } else { + false + } + } + + /// Set Irq level. If edge is detected, then IRR is set to 1. + fn set_irq_internal(pic: &mut PicState, irq: u8, level: bool) { + assert!(irq <= 7, "Unexpectedly high value for irq: {} vs 7", irq); + let irq_bitmap = 1 << irq; + if (pic.elcr & irq_bitmap) != 0 { + // Level-triggered. + if level { + // Same IRQ already requested. + pic.irr |= irq_bitmap; + pic.last_irr |= irq_bitmap; + } else { + pic.irr &= !irq_bitmap; + pic.last_irr &= !irq_bitmap; + } + } else { + // Edge-triggered + if level { + if (pic.last_irr & irq_bitmap) == 0 { + // Raising edge detected. + pic.irr |= irq_bitmap; + } + pic.last_irr |= irq_bitmap; + } else { + pic.last_irr &= !irq_bitmap; + } + } + } + + fn get_priority(pic: &PicState, irq_bitmap: u8) -> Option<u8> { + if irq_bitmap == 0 { + None + } else { + // Find the highest priority bit in irq_bitmap considering the priority + // rotation mechanism (priority_add). + let mut priority = 0; + let mut priority_mask = 1 << ((priority + pic.priority_add) & 7); + while (irq_bitmap & priority_mask) == 0 { + priority += 1; + priority_mask = 1 << ((priority + pic.priority_add) & 7); + } + Some(priority) + } + } + + /// Move interrupt from IRR to ISR to indicate that the interrupt is injected. If + /// auto EOI is set, then ISR is immediately cleared (since the purpose of EOI is + /// to clear ISR bit). + fn interrupt_ack(pic: &mut PicState, irq: u8) { + assert!(irq <= 7, "Unexpectedly high value for irq: {} vs 7", irq); + + let irq_bitmap = 1 << irq; + pic.isr |= irq_bitmap; + + if (pic.elcr & irq_bitmap) == 0 { + pic.irr &= !irq_bitmap; + } + + if pic.auto_eoi { + if pic.rotate_on_auto_eoi { + pic.priority_add = (irq + 1) & 7; + } + Pic::clear_isr(pic, irq); + } + } + + fn init_command_word_1(pic: &mut PicState, value: u8) { + pic.use_4_byte_icw = (value & ICW1_NEED_ICW4) != 0; + if (value & ICW1_SINGLE_PIC_MODE) != 0 { + debug!("PIC: Single PIC mode not supported."); + } + if (value & ICW1_LEVEL_TRIGGER_MODE) != 0 { + debug!("PIC: Level triggered IRQ not supported."); + } + Pic::reset_pic(pic); + } + + fn operation_command_word_2(&mut self, pic_type: PicSelect, value: u8) { + let mut irq = value & OCW2_IRQ_MASK; + if let Some(cmd) = Ocw2::n(value & OCW2_COMMAND_MASK) { + match cmd { + Ocw2::RotateAutoEoiSet => self.pics[pic_type as usize].rotate_on_auto_eoi = true, + Ocw2::RotateAutoEoiClear => self.pics[pic_type as usize].rotate_on_auto_eoi = false, + Ocw2::NonSpecificEoi | Ocw2::RotateNonSpecificEoi => { + if let Some(priority) = Pic::get_priority( + &self.pics[pic_type as usize], + self.pics[pic_type as usize].isr, + ) { + irq = (priority + self.pics[pic_type as usize].priority_add) & 7; + if cmd == Ocw2::RotateNonSpecificEoi { + self.pics[pic_type as usize].priority_add = (irq + 1) & 7; + } + Pic::clear_isr(&mut self.pics[pic_type as usize], irq); + self.update_irq(); + } + } + Ocw2::SpecificEoi => { + Pic::clear_isr(&mut self.pics[pic_type as usize], irq); + self.update_irq(); + } + Ocw2::SetPriority => { + self.pics[pic_type as usize].priority_add = (irq + 1) & 7; + self.update_irq(); + } + Ocw2::RotateSpecificEoi => { + self.pics[pic_type as usize].priority_add = (irq + 1) & 7; + Pic::clear_isr(&mut self.pics[pic_type as usize], irq); + self.update_irq(); + } + Ocw2::NoOp => {} /* noop */ + } + } + } + + fn operation_command_word_3(pic: &mut PicState, value: u8) { + if value & OCW3_POLL_COMMAND != 0 { + pic.poll = true; + } + if value & OCW3_READ_REGISTER != 0 { + // Select to read ISR or IRR + pic.read_reg_select = value & OCW3_READ_ISR != 0; + } + if value & OCW3_SPECIAL_MASK != 0 { + pic.special_mask = value & OCW3_SPECIAL_MASK_VALUE != 0; + } + } +} + +#[cfg(test)] +mod tests { + // ICW4: Special fully nested mode with no auto EOI. + const FULLY_NESTED_NO_AUTO_EOI: u8 = 0x11; + use super::*; + + struct TestData { + pic: Pic, + } + + fn set_up() -> TestData { + let mut pic = Pic::new(); + // Use edge-triggered mode. + pic.write(PIC_PRIMARY_ELCR, &[0]); + pic.write(PIC_SECONDARY_ELCR, &[0]); + TestData { pic } + } + + /// Convenience wrapper to initialize PIC using 4 ICWs. Validity of values is NOT checked. + fn icw_init(pic: &mut Pic, pic_type: PicSelect, icw1: u8, icw2: u8, icw3: u8, icw4: u8) { + let command_offset = match pic_type { + PicSelect::Primary => PIC_PRIMARY_COMMAND, + PicSelect::Secondary => PIC_SECONDARY_COMMAND, + }; + let data_offset = match pic_type { + PicSelect::Primary => PIC_PRIMARY_DATA, + PicSelect::Secondary => PIC_SECONDARY_DATA, + }; + + pic.write(command_offset, &[icw1]); + pic.write(data_offset, &[icw2]); + pic.write(data_offset, &[icw3]); + pic.write(data_offset, &[icw4]); + } + + /// Convenience function for primary ICW init. + fn icw_init_primary(pic: &mut Pic) { + // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. + // ICW2 0x08: Interrupt vector base address 0x08. + // ICW3 0xff: Value written does not matter. + // ICW4 0x13: Special fully nested mode, auto EOI. + icw_init(pic, PicSelect::Primary, 0x11, 0x08, 0xff, 0x13); + } + + /// Convenience function for secondary ICW init. + fn icw_init_secondary(pic: &mut Pic) { + // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. + // ICW2 0x70: Interrupt vector base address 0x70. + // ICW3 0xff: Value written does not matter. + // ICW4 0x13: Special fully nested mode, auto EOI. + icw_init(pic, PicSelect::Secondary, 0x11, 0x70, 0xff, 0x13); + } + + /// Convenience function for initializing ICW with custom value for ICW4. + fn icw_init_both_with_icw4(pic: &mut Pic, icw4: u8) { + // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. + // ICW2 0x08: Interrupt vector base address 0x08. + // ICW3 0xff: Value written does not matter. + icw_init(pic, PicSelect::Primary, 0x11, 0x08, 0xff, icw4); + // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. + // ICW2 0x70: Interrupt vector base address 0x70. + // ICW3 0xff: Value written does not matter. + icw_init(pic, PicSelect::Secondary, 0x11, 0x70, 0xff, icw4); + } + + fn icw_init_both(pic: &mut Pic) { + icw_init_primary(pic); + icw_init_secondary(pic); + } + + /// Test that elcr register can be written and read correctly. + #[test] + fn write_read_elcr() { + let mut data = set_up(); + let data_write = [0x5f]; + let mut data_read = [0]; + + data.pic.write(PIC_PRIMARY_ELCR, &data_write); + data.pic.read(PIC_PRIMARY_ELCR, &mut data_read); + assert_eq!(data_read, data_write); + + data.pic.write(PIC_SECONDARY_ELCR, &data_write); + data.pic.read(PIC_SECONDARY_ELCR, &mut data_read); + assert_eq!(data_read, data_write); + } + + /// Test the three-word ICW. + #[test] + fn icw_2_step() { + let mut data = set_up(); + + // ICW1 + let mut data_write = [0x10]; + data.pic.write(PIC_PRIMARY_COMMAND, &data_write); + + data_write[0] = 0x08; + data.pic.write(PIC_PRIMARY_DATA, &data_write); + + data_write[0] = 0xff; + data.pic.write(PIC_PRIMARY_DATA, &data_write); + + assert_eq!( + data.pic.pics[PicSelect::Primary as usize].init_state, + Some(PicInitState::Icw1) + ); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irq_base, 0x08); + assert_eq!( + data.pic.pics[PicSelect::Primary as usize].use_4_byte_icw, + false + ); + } + + /// Verify that PIC is in expected state after initialization. + #[test] + fn initial_values() { + let mut data = set_up(); + icw_init_primary(&mut data.pic); + + let primary_pic = &data.pic.pics[PicSelect::Primary as usize]; + assert_eq!(primary_pic.last_irr, 0x00); + assert_eq!(primary_pic.irr, 0x00); + assert_eq!(primary_pic.imr, 0x00); + assert_eq!(primary_pic.isr, 0x00); + assert_eq!(primary_pic.priority_add, 0); + assert_eq!(primary_pic.irq_base, 0x08); + assert_eq!(primary_pic.read_reg_select, false); + assert_eq!(primary_pic.poll, false); + assert_eq!(primary_pic.special_mask, false); + assert_eq!(primary_pic.init_state, Some(PicInitState::Icw1)); + assert_eq!(primary_pic.auto_eoi, true); + assert_eq!(primary_pic.rotate_on_auto_eoi, false); + assert_eq!(primary_pic.special_fully_nested_mode, true); + assert_eq!(primary_pic.use_4_byte_icw, true); + assert_eq!(primary_pic.elcr, 0x00); + assert_eq!(primary_pic.elcr_mask, 0xf8); + } + + /// Verify effect that OCW has on PIC registers & state. + #[test] + fn ocw() { + let mut data = set_up(); + + icw_init_secondary(&mut data.pic); + + // OCW1: Write to IMR. + data.pic.write(PIC_SECONDARY_DATA, &[0x5f]); + + // OCW2: Set rotate on auto EOI. + data.pic.write(PIC_SECONDARY_COMMAND, &[0x80]); + + // OCW2: Set priority. + data.pic.write(PIC_SECONDARY_COMMAND, &[0xc0]); + + // OCW3: Change flags. + data.pic.write(PIC_SECONDARY_COMMAND, &[0x6b]); + + let mut data_read = [0]; + data.pic.read(PIC_SECONDARY_DATA, &mut data_read); + assert_eq!(data_read, [0x5f]); + + let secondary_pic = &data.pic.pics[PicSelect::Secondary as usize]; + + // Check OCW1 result. + assert_eq!(secondary_pic.imr, 0x5f); + + // Check OCW2 result. + assert!(secondary_pic.rotate_on_auto_eoi); + assert_eq!(secondary_pic.priority_add, 1); + + // Check OCW3 result. + assert!(secondary_pic.special_mask); + assert_eq!(secondary_pic.poll, false); + assert!(secondary_pic.read_reg_select); + } + + /// Verify that we can set and clear the AutoRotate bit in OCW. + #[test] + fn ocw_auto_rotate_set_and_clear() { + let mut data = set_up(); + + icw_init_secondary(&mut data.pic); + + // OCW2: Set rotate on auto EOI. + data.pic.write(PIC_SECONDARY_COMMAND, &[0x80]); + + { + let secondary_pic = &data.pic.pics[PicSelect::Secondary as usize]; + assert!(secondary_pic.rotate_on_auto_eoi); + } + + // OCW2: Clear rotate on auto EOI. + data.pic.write(PIC_SECONDARY_COMMAND, &[0x00]); + + let secondary_pic = &data.pic.pics[PicSelect::Secondary as usize]; + assert!(!secondary_pic.rotate_on_auto_eoi); + } + + /// Test basic auto EOI case. + #[test] + fn auto_eoi() { + let mut data = set_up(); + + icw_init_both(&mut data.pic); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 12, /*level=*/ true); + + // Check that IRQ is requesting acknowledgment. + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, (1 << 4)); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, (1 << 2)); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + + // 0x70 is interrupt base on secondary PIC. 0x70 + 4 is the interrupt entry number. + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4)); + + // Check that IRQ is acknowledged and EOI is automatically done. + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + } + + /// Test with fully-nested mode on. When the secondary PIC has an IRQ in service, it shouldn't + /// be locked out by the primary's priority logic. + /// This means that the secondary should still be able to request a higher-priority IRQ. + /// Auto EOI is off in order to keep IRQ in service. + #[test] + fn fully_nested_mode_on() { + let mut data = set_up(); + + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 12, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4)); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + // Request higher-priority IRQ on secondary. + data.pic.service_irq(/*irq=*/ 8, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 0)); + + // Check that IRQ is ack'd and EOI is automatically done. + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 0); + assert_eq!( + data.pic.pics[PicSelect::Secondary as usize].isr, + (1 << 4) + (1 << 0) + ); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 2); + } + + /// Test with fully-nested mode off. When the secondary PIC has an IRQ in service, it should + /// NOT be able to request another higher-priority IRQ. + /// Auto EOI is off in order to keep IRQ in service. + #[test] + fn fully_nested_mode_off() { + let mut data = set_up(); + + // ICW4 0x01: No special fully nested mode, no auto EOI. + icw_init_both_with_icw4(&mut data.pic, 0x01); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 12, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4)); + + data.pic.service_irq(/*irq=*/ 8, /*level=*/ true); + // Primary cannot get any IRQ, so this should not provide any interrupt. + assert_eq!(data.pic.get_external_interrupt(), None); + + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 1 << 0); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 1 << 4); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, 1 << 2); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 2); + + // 2 EOIs will cause 2 interrupts. + // TODO(mutexlox): Verify APIC interaction when it is implemented. + + // OCW2: Non-specific EOI, one for primary and one for secondary. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x20]); + data.pic.write(PIC_SECONDARY_COMMAND, &[0x20]); + + // Now that the first IRQ is no longer in service, the second IRQ can be ack'd. + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 0)); + + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 1 << 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 2); + } + + /// Write IMR to mask an IRQ. The masked IRQ can't be served until unmasked. + #[test] + fn mask_irq() { + let mut data = set_up(); + + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // OCW2: Mask IRQ line 6 on secondary (IRQ 14). + data.pic.write(PIC_SECONDARY_DATA, &[0x40]); + + data.pic.service_irq(/*irq=*/ 14, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), None); + + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 1 << 6); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + + // OCW2: Unmask IRQ line 6 on secondary (IRQ 14) + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.write(PIC_SECONDARY_DATA, &[0x00]); + + // Previously-masked interrupt can now be served. + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 6)); + + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 1 << 6); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 2); + } + + /// Write IMR to mask multiple IRQs. They masked IRQs cannot be served until they're unmasked. + /// The highest priority IRQ must be served first, no matter the original order of request. + /// (To simplify the test, we won't check irr and isr and so we'll leave auto EOI on.) + #[test] + fn mask_multiple_irq() { + let mut data = set_up(); + icw_init_both(&mut data.pic); + + // OCW2: Mask *all* IRQ lines on primary and secondary. + data.pic.write(PIC_PRIMARY_DATA, &[0xff]); + data.pic.write(PIC_SECONDARY_DATA, &[0xff]); + + data.pic.service_irq(/*irq=*/ 14, /*level=*/ true); + data.pic.service_irq(/*irq=*/ 4, /*level=*/ true); + data.pic.service_irq(/*irq=*/ 12, /*level=*/ true); + + // Primary cannot get any IRQs since they're all masked. + assert_eq!(data.pic.get_external_interrupt(), None); + + // OCW2: Unmask IRQ lines on secondary. + data.pic.write(PIC_SECONDARY_DATA, &[0x00]); + + // Cascade line is masked, so the primary *still* cannot get any IRQs. + assert_eq!(data.pic.get_external_interrupt(), None); + + // Unmask cascade line on primary. + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.write(PIC_PRIMARY_DATA, &[0xfb]); + + // Previously-masked IRQs should now be served in order of priority. + // TODO(mutexlox): Verify APIC interaction when it is implemented. + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4)); + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 6)); + + // Unmask all other IRQ lines on primary. + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.write(PIC_PRIMARY_DATA, &[0x00]); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4)); + } + + /// Test OCW3 poll (reading irr and isr). + #[test] + fn ocw3() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + // Poplate some data on irr/isr. IRQ4 will be in isr and IRQ5 in irr. + data.pic.service_irq(/*irq=*/ 5, /*level=*/ true); + data.pic.service_irq(/*irq=*/ 4, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4)); + + // Read primary IRR. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x0a]); + let mut data_read = [0]; + data.pic.read(PIC_PRIMARY_COMMAND, &mut data_read); + assert_eq!(data_read[0], 1 << 5); + + // Read primary ISR. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x0b]); + data_read = [0]; + data.pic.read(PIC_PRIMARY_COMMAND, &mut data_read); + assert_eq!(data_read[0], 1 << 4); + + // Non-sepcific EOI to end IRQ4. Then, PIC should signal CPU about IRQ5. + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x20]); + + // Poll command on primary. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x0c]); + data_read = [0]; + data.pic.read(PIC_PRIMARY_COMMAND, &mut data_read); + assert_eq!(data_read[0], 5); + } + + /// Assert on primary PIC's IRQ2 without any IRQ on secondary asserted. This should result in a + /// spurious IRQ on secondary. + #[test] + fn fake_irq_on_primary_irq2() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 2, /*level=*/ true); + // 0x70 is secondary IRQ base, 7 is for a spurious IRQ. + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 7)); + } + + /// Raising the same IRQ line twice in edge trigger mode should only send one IRQ request out. + #[test] + fn edge_trigger_mode() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 4, /*level=*/ true); + // get_external_interrupt clears the irr so it is possible to request the same IRQ again. + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4)); + + data.pic.service_irq(/*irq=*/ 4, /*level=*/ true); + + // In edge triggered mode, there should be no IRQ after this EOI. + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x20]); + } + + /// Raising the same IRQ line twice in level-triggered mode should send two IRQ requests out. + #[test] + fn level_trigger_mode() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // Turn IRQ4 to level-triggered mode. + data.pic.write(PIC_PRIMARY_ELCR, &[0x10]); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 4, /*level=*/ true); + // get_external_interrupt clears the irr so it is possible to request the same IRQ again. + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4)); + + data.pic.service_irq(/*irq=*/ 4, /*level=*/ true); + + // In level-triggered mode, there should be another IRQ request after this EOI. + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x20]); + } + + /// Specific EOI command in OCW2. + #[test] + fn specific_eoi() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 4, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4)); + + // Specific EOI command on IRQ3. Primary PIC's ISR should be unaffected since it's targeted + // at the wrong IRQ number. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x63]); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 4); + + // Specific EOI command on IRQ4. Primary PIC's ISR should now be cleared. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x64]); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + } + + /// Test rotate on auto EOI. + #[test] + fn rotate_on_auto_eoi() { + let mut data = set_up(); + icw_init_both(&mut data.pic); + + // OCW3: Clear rotate on auto EOI mode. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x00]); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 5, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5)); + data.pic.service_irq(/*irq=*/ 5, /*level=*/ false); + + // EOI automatically happened. Now priority should not be rotated. + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].imr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].last_irr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].priority_add, 0); + + // OCW2: Set rotate on auto EOI mode. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x80]); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 5, /*level*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5)); + data.pic.service_irq(/*irq=*/ 5, /*level=*/ false); + + // EOI automatically happened, and the priority *should* be rotated. + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].priority_add, 6); + } + + /// Test rotate on specific (non-auto) EOI. + #[test] + fn rotate_on_specific_eoi() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 5, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5)); + data.pic.service_irq(/*irq=*/ 5, /*level=*/ false); + + // Rotate on specific EOI IRQ4. Since this is a different IRQ number, Should not have an + // effect on isr. + data.pic.write(PIC_PRIMARY_COMMAND, &[0xe4]); + + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 5); + + // Rotate on specific EOI IRQ5. This should clear the isr. + data.pic.write(PIC_PRIMARY_COMMAND, &[0xe5]); + + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].priority_add, 6); + } + + /// Test rotate on non-specific EOI. + #[test] + fn rotate_non_specific_eoi() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 5, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5)); + data.pic.service_irq(/*irq=*/ 5, /*level=*/ false); + + // Rotate on non-specific EOI. + data.pic.write(PIC_PRIMARY_COMMAND, &[0xa0]); + + // The EOI should have cleared isr. + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Primary as usize].priority_add, 6); + } + + /// Verify that no-op doesn't change state. + #[test] + fn no_op_ocw2() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 5, /*level=*/ true); + assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5)); + data.pic.service_irq(/*irq=*/ 5, /*level=*/ false); + + let orig = data.pic.pics[PicSelect::Primary as usize].clone(); + + // Run a no-op. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x40]); + + // Nothing should have changed. + assert_eq!(orig, data.pic.pics[PicSelect::Primary as usize]); + } + + /// Tests cascade IRQ that happens on secondary PIC. + #[test] + fn cascade_irq() { + let mut data = set_up(); + icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI); + + // TODO(mutexlox): Verify APIC interaction when it is implemented. + data.pic.service_irq(/*irq=*/ 12, /*level=*/ true); + + assert_eq!(data.pic.pics[PicSelect::Primary as usize].irr, 1 << 2); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 1 << 4); + + assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4)); + + // Check that the IRQ is now acknowledged after get_external_interrupt(). + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 0); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 1 << 4); + + // OCW2: Two non-specific EOIs to primary rather than secondary. + // We need two non-specific EOIs: + // - The first resets bit 2 in the primary isr (the highest-priority bit that was set + // before the EOI) + // - The second resets the secondary PIC's highest-priority isr bit. + data.pic.write(PIC_PRIMARY_COMMAND, &[0x20]); + // Rotate non-specific EOI. + data.pic.write(PIC_SECONDARY_COMMAND, &[0xa0]); + + assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 0); + assert_eq!(data.pic.pics[PicSelect::Secondary as usize].priority_add, 5); + } +} |