use base::debug;
use base::warn;
use base::Event;
use hypervisor::PicInitState;
use hypervisor::PicSelect;
use hypervisor::PicState;
use snapshot::AnySnapshot;
use crate::bus::BusAccessInfo;
use crate::pci::CrosvmDeviceId;
use crate::BusDevice;
use crate::DeviceId;
use crate::Suspendable;
pub struct Pic {
    interrupt_request: bool,
    resample_events: Vec<Vec<Event>>,
    pics: [PicState; 2],
}
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;
const ICW1_MASK: u8 = 0x10;
const OCW3_MASK: u8 = 0x08;
const ICW1_NEED_ICW4: u8 = 0x01; 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;
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,
}
const OCW3_POLL_COMMAND: u8 = 0x04;
const OCW3_READ_REGISTER: u8 = 0x02;
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();
        primary_pic.elcr_mask = !((1 << 0) | (1 << 1) | (1 << 2));
        secondary_pic.elcr_mask = !((1 << 0) | (1 << 5));
        Pic {
            interrupt_request: false,
            pics: [primary_pic, secondary_pic],
            resample_events: Vec::new(),
        }
    }
    pub fn get_pic_state(&self, select: PicSelect) -> PicState {
        self.pics[select as usize]
    }
    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<Vec<Event>>) {
        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()
    }
    pub fn masked(&self) -> bool {
        self.pics[PicSelect::Primary as usize].imr == 0xFF
    }
    pub fn has_interrupt(&self) -> bool {
        self.get_irq(PicSelect::Primary).is_some()
    }
    pub fn interrupt_requested(&self) -> bool {
        self.interrupt_request
    }
    pub fn get_external_interrupt(&mut self) -> Option<u8> {
        self.interrupt_request = false;
        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 {
            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);
            }
        }
    }
    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_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 = Pic::get_priority(pic, irq_bitmap)?;
        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 {
            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() {
            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;
            true
        } else {
            false
        }
    }
    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 {
            if level {
                pic.irr |= irq_bitmap;
                pic.last_irr |= irq_bitmap;
            } else {
                pic.irr &= !irq_bitmap;
                pic.last_irr &= !irq_bitmap;
            }
        } else {
            if level {
                if (pic.last_irr & irq_bitmap) == 0 {
                    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 {
            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)
        }
    }
    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 => {} }
        }
    }
    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 {
            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 Default for Pic {
    fn default() -> Self {
        Self::new()
    }
}
impl Suspendable for Pic {
    fn sleep(&mut self) -> anyhow::Result<()> {
        Ok(())
    }
    fn wake(&mut self) -> anyhow::Result<()> {
        Ok(())
    }
    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
        AnySnapshot::to_any(())
    }
    fn restore(&mut self, _data: AnySnapshot) -> anyhow::Result<()> {
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    const FULLY_NESTED_NO_AUTO_EOI: u8 = 0x11;
    use super::*;
    struct TestData {
        pic: Pic,
    }
    fn pic_bus_address(address: u64) -> BusAccessInfo {
        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();
        pic.write(pic_bus_address(PIC_PRIMARY_ELCR), &[0]);
        pic.write(pic_bus_address(PIC_SECONDARY_ELCR), &[0]);
        TestData { pic }
    }
    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]);
    }
    fn icw_init_primary(pic: &mut Pic) {
        icw_init(pic, PicSelect::Primary, 0x11, 0x08, 0xff, 0x13);
    }
    fn icw_init_secondary(pic: &mut Pic) {
        icw_init(pic, PicSelect::Secondary, 0x11, 0x70, 0xff, 0x13);
    }
    fn icw_init_both_with_icw4(pic: &mut Pic, icw4: u8) {
        icw_init(pic, PicSelect::Primary, 0x11, 0x08, 0xff, icw4);
        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]
    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]
    fn icw_2_step() {
        let mut data = set_up();
        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
        );
    }
    #[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);
    }
    #[test]
    fn ocw() {
        let mut data = set_up();
        icw_init_secondary(&mut data.pic);
        data.pic.write(pic_bus_address(PIC_SECONDARY_DATA), &[0x5f]);
        data.pic
            .write(pic_bus_address(PIC_SECONDARY_COMMAND), &[0x80]);
        data.pic
            .write(pic_bus_address(PIC_SECONDARY_COMMAND), &[0xc0]);
        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];
        assert_eq!(secondary_pic.imr, 0x5f);
        assert!(secondary_pic.rotate_on_auto_eoi);
        assert_eq!(secondary_pic.priority_add, 1);
        assert!(secondary_pic.special_mask);
        assert_eq!(secondary_pic.poll, false);
        assert!(secondary_pic.read_reg_select);
    }
    #[test]
    fn ocw_auto_rotate_set_and_clear() {
        let mut data = set_up();
        icw_init_secondary(&mut data.pic);
        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);
        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]
    fn auto_eoi() {
        let mut data = set_up();
        icw_init_both(&mut data.pic);
        data.pic.service_irq(12, true);
        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);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4));
        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]
    fn fully_nested_mode_on() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.service_irq(12, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4));
        data.pic.service_irq(8, true);
        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 << 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]
    fn fully_nested_mode_off() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, 0x01);
        data.pic.service_irq(12, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4));
        data.pic.service_irq(8, true);
        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);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x20]);
        data.pic
            .write(pic_bus_address(PIC_SECONDARY_COMMAND), &[0x20]);
        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);
    }
    #[test]
    fn mask_irq() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.write(pic_bus_address(PIC_SECONDARY_DATA), &[0x40]);
        data.pic.service_irq(14, 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);
        data.pic.write(pic_bus_address(PIC_SECONDARY_DATA), &[0x00]);
        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);
    }
    #[test]
    fn mask_multiple_irq() {
        let mut data = set_up();
        icw_init_both(&mut data.pic);
        data.pic.write(pic_bus_address(PIC_PRIMARY_DATA), &[0xff]);
        data.pic.write(pic_bus_address(PIC_SECONDARY_DATA), &[0xff]);
        data.pic.service_irq(14, true);
        data.pic.service_irq(4, true);
        data.pic.service_irq(12, true);
        assert_eq!(data.pic.get_external_interrupt(), None);
        data.pic.write(pic_bus_address(PIC_SECONDARY_DATA), &[0x00]);
        assert_eq!(data.pic.get_external_interrupt(), None);
        data.pic.write(pic_bus_address(PIC_PRIMARY_DATA), &[0xfb]);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 4));
        assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 6));
        data.pic.write(pic_bus_address(PIC_PRIMARY_DATA), &[0x00]);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4));
    }
    #[test]
    fn ocw3() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.service_irq(5, true);
        data.pic.service_irq(4, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4));
        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);
        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);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x20]);
        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);
    }
    #[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);
        data.pic.service_irq(2, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x70 + 7));
    }
    #[test]
    fn edge_trigger_mode() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.service_irq(4, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4));
        data.pic.service_irq(4, true);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x20]);
    }
    #[test]
    fn level_trigger_mode() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.write(pic_bus_address(PIC_PRIMARY_ELCR), &[0x10]);
        data.pic.service_irq(4, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4));
        data.pic.service_irq(4, true);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x20]);
    }
    #[test]
    fn specific_eoi() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.service_irq(4, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 4));
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x63]);
        assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 4);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x64]);
        assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0);
    }
    #[test]
    fn rotate_on_auto_eoi() {
        let mut data = set_up();
        icw_init_both(&mut data.pic);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x00]);
        data.pic.service_irq(5, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5));
        data.pic.service_irq(5, false);
        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);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x80]);
        data.pic.service_irq(5, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5));
        data.pic.service_irq(5, false);
        assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0);
        assert_eq!(data.pic.pics[PicSelect::Primary as usize].priority_add, 6);
    }
    #[test]
    fn rotate_on_specific_eoi() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.service_irq(5, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5));
        data.pic.service_irq(5, false);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0xe4]);
        assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 1 << 5);
        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]
    fn rotate_non_specific_eoi() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.service_irq(5, true);
        assert_eq!(data.pic.get_external_interrupt(), Some(0x08 + 5));
        data.pic.service_irq(5, false);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0xa0]);
        assert_eq!(data.pic.pics[PicSelect::Primary as usize].isr, 0);
        assert_eq!(data.pic.pics[PicSelect::Primary as usize].priority_add, 6);
    }
    #[test]
    fn cascade_irq() {
        let mut data = set_up();
        icw_init_both_with_icw4(&mut data.pic, FULLY_NESTED_NO_AUTO_EOI);
        data.pic.service_irq(12, 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));
        assert_eq!(data.pic.pics[PicSelect::Secondary as usize].irr, 0);
        assert_eq!(data.pic.pics[PicSelect::Secondary as usize].isr, 1 << 4);
        data.pic
            .write(pic_bus_address(PIC_PRIMARY_COMMAND), &[0x20]);
        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);
    }
}