// 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" 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 crate::BusDevice;
use base::{debug, warn, EventFd};
use hypervisor::{PicInitState, PicSelect, PicState};

pub struct Pic {
    // Indicates a pending INTR signal to LINT0 of vCPU, checked by vCPU thread.
    interrupt_request: bool,
    // EventFds that need to be triggered when an ISR is cleared
    resample_events: Vec<Option<EventFd>>,
    // 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 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));

        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<Option<EventFd>>) {
        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<u8> {
        self.interrupt_request = false;
        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;
        };

        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<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(&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(Some(resample_evt)) = self.resample_events.get(irq as usize) {
            resample_evt.write(1).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<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(&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;
        }
    }
}

#[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,
            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_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);
    }

    /// 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);
    }
}
