// Copyright 2019 The ChromiumOS Authors // 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" PICs; 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" PICs. use base::debug; use base::warn; use base::Event; use hypervisor::PicInitState; use hypervisor::PicSelect; use hypervisor::PicState; use crate::bus::BusAccessInfo; use crate::pci::CrosvmDeviceId; use crate::BusDevice; use crate::DeviceId; use crate::Suspendable; pub struct Pic { // Indicates a pending INTR signal to LINT0 of vCPU, checked by vCPU thread. interrupt_request: bool, // Events that need to be triggered when an ISR is cleared. The outer Vec is indexed by GSI, // and the inner Vec is an unordered list of registered resample events for the GSI. resample_events: Vec>, // Index 0 (aka PicSelect::Primary) is the primary pic, the rest are secondary. pics: [PicState; 2], } // 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 device_id(&self) -> DeviceId { CrosvmDeviceId::Pic.into() } fn debug_label(&self) -> String { "userspace PIC".to_string() } fn write(&mut self, info: BusAccessInfo, data: &[u8]) { if data.len() != 1 { warn!("PIC: Bad write size: {}", data.len()); return; } match info.address { 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 {}", info), } } fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) { if data.len() != 1 { warn!("PIC: Bad read size: {}", data.len()); return; } data[0] = match info.address { 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 {}", info); 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)); Pic { interrupt_request: false, pics: [primary_pic, secondary_pic], resample_events: Vec::new(), } } /// Get the current state of the primary or secondary PIC pub fn get_pic_state(&self, select: PicSelect) -> PicState { self.pics[select as usize] } /// Set the current state of the primary or secondary PIC pub fn set_pic_state(&mut self, select: PicSelect, state: &PicState) { self.pics[select as usize] = *state; } pub fn register_resample_events(&mut self, resample_events: Vec>) { self.resample_events = resample_events; } 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 whether the PIC has fired an interrupt to LAPIC. pub fn interrupt_requested(&self) -> bool { self.interrupt_request } /// Determines the external interrupt number that the PIC is prepared to inject, if any. pub fn get_external_interrupt(&mut self) -> Option { self.interrupt_request = false; // If there is no interrupt request, return `None` to avoid the interrupt entirely. // 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. 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. let irq_primary = self.get_irq(PicSelect::Primary)?; self.interrupt_ack(PicSelect::Primary, 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) { self.interrupt_ack(PicSelect::Secondary, 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 { self.init_command_word_1(pic_type, 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 { PicInitState::Icw1 => { self.pics[pic_type as usize].imr = value; self.update_irq(); } PicInitState::Icw2 => { self.pics[pic_type as usize].irq_base = value & ICW2_IRQ_BASE_MASK; self.pics[pic_type as usize].init_state = PicInitState::Icw3; } PicInitState::Icw3 => { if self.pics[pic_type as usize].use_4_byte_icw { self.pics[pic_type as usize].init_state = PicInitState::Icw4; } else { self.pics[pic_type as usize].init_state = PicInitState::Icw1; } } 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 = 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(&mut self, pic_type: PicSelect) { let pic = &mut self.pics[pic_type as usize]; 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 = PicInitState::Icw2; for irq in 0..8 { if edge_irr & (1 << irq) != 0 { self.clear_isr(pic_type, 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(irq) = self.get_irq(pic_type) { 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); self.clear_isr(pic_type, 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 { let pic = &self.pics[pic_type as usize]; let mut irq_bitmap = pic.irr & !pic.imr; let priority = Pic::get_priority(pic, irq_bitmap)?; // 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(&mut self, pic_type: PicSelect, irq: u8) { let pic = &mut self.pics[pic_type as usize]; assert!(irq <= 7, "Unexpectedly high value for irq: {} vs 7", irq); pic.isr &= !(1 << irq); Pic::set_irq_internal(pic, irq, false); let irq = if pic_type == PicSelect::Primary { irq } else { irq + 8 }; if let Some(resample_events) = self.resample_events.get(irq as usize) { for resample_evt in resample_events { resample_evt.signal().unwrap(); } } } fn update_irq(&mut self) -> bool { if self.get_irq(PicSelect::Secondary).is_some() { // 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 self.get_irq(PicSelect::Primary).is_some() { self.interrupt_request = true; // 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 { 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(&mut self, pic_type: PicSelect, irq: u8) { let pic = &mut self.pics[pic_type as usize]; 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; } self.clear_isr(pic_type, irq); } } fn init_command_word_1(&mut self, pic_type: PicSelect, value: u8) { let pic = &mut self.pics[pic_type as usize]; 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."); } self.reset_pic(pic_type); } 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; } self.clear_isr(pic_type, irq); self.update_irq(); } } Ocw2::SpecificEoi => { self.clear_isr(pic_type, 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; self.clear_isr(pic_type, 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; } } } impl Suspendable for Pic { fn sleep(&mut self) -> anyhow::Result<()> { Ok(()) } fn wake(&mut self) -> anyhow::Result<()> { Ok(()) } } #[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 pic_bus_address(address: u64) -> BusAccessInfo { // The PIC is added to the io_bus in three locations, so the offset depends on which // address range the address is in. The PIC implementation currently does not use the // offset, but we're setting it accurately here in case it does in the future. let base_address = if (PIC_PRIMARY..PIC_PRIMARY + 0x2).contains(&address) { PIC_PRIMARY } else if (PIC_SECONDARY..PIC_SECONDARY + 0x2).contains(&address) { PIC_SECONDARY } else if (PIC_PRIMARY_ELCR..PIC_PRIMARY_ELCR + 0x2).contains(&address) { PIC_PRIMARY_ELCR } else { panic!("invalid PIC address: {:#x}", address); }; BusAccessInfo { offset: address - base_address, address, id: 0, } } fn set_up() -> TestData { let mut pic = Pic::new(); // Use edge-triggered mode. pic.write(pic_bus_address(PIC_PRIMARY_ELCR), &[0]); pic.write(pic_bus_address(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(pic_bus_address(command_offset), &[icw1]); pic.write(pic_bus_address(data_offset), &[icw2]); pic.write(pic_bus_address(data_offset), &[icw3]); pic.write(pic_bus_address(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_bus_address(PIC_PRIMARY_ELCR), &data_write); data.pic .read(pic_bus_address(PIC_PRIMARY_ELCR), &mut data_read); assert_eq!(data_read, data_write); data.pic .write(pic_bus_address(PIC_SECONDARY_ELCR), &data_write); data.pic .read(pic_bus_address(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_bus_address(PIC_PRIMARY_COMMAND), &data_write); data_write[0] = 0x08; data.pic .write(pic_bus_address(PIC_PRIMARY_DATA), &data_write); data_write[0] = 0xff; data.pic .write(pic_bus_address(PIC_PRIMARY_DATA), &data_write); assert_eq!( data.pic.pics[PicSelect::Primary as usize].init_state, 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, 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_bus_address(PIC_SECONDARY_DATA), &[0x5f]); // OCW2: Set rotate on auto EOI. data.pic .write(pic_bus_address(PIC_SECONDARY_COMMAND), &[0x80]); // OCW2: Set priority. data.pic .write(pic_bus_address(PIC_SECONDARY_COMMAND), &[0xc0]); // OCW3: Change flags. data.pic .write(pic_bus_address(PIC_SECONDARY_COMMAND), &[0x6b]); let mut data_read = [0]; data.pic .read(pic_bus_address(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_bus_address(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_bus_address(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_bus_address(PIC_PRIMARY_COMMAND), &[0x20]); data.pic .write(pic_bus_address(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_bus_address(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_bus_address(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_bus_address(PIC_PRIMARY_DATA), &[0xff]); data.pic.write(pic_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(PIC_PRIMARY_COMMAND), &[0x0a]); let mut data_read = [0]; data.pic .read(pic_bus_address(PIC_PRIMARY_COMMAND), &mut data_read); assert_eq!(data_read[0], 1 << 5); // Read primary ISR. data.pic .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x0b]); data_read = [0]; data.pic .read(pic_bus_address(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_bus_address(PIC_PRIMARY_COMMAND), &[0x20]); // Poll command on primary. data.pic .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x0c]); data_read = [0]; data.pic .read(pic_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(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_bus_address(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); } /// 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_bus_address(PIC_PRIMARY_COMMAND), &[0x20]); // Rotate non-specific EOI. data.pic .write(pic_bus_address(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); } }