Skip to main content

chipset/pic/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4pub mod resolver;
5
6use bitfield_struct::bitfield;
7use chipset_device::ChipsetDevice;
8use chipset_device::interrupt::AcknowledgePicInterrupt;
9use chipset_device::interrupt::LineInterruptTarget;
10use chipset_device::io::IoError;
11use chipset_device::io::IoResult;
12use chipset_device::pio::ControlPortIoIntercept;
13use chipset_device::pio::PortIoIntercept;
14use chipset_device::pio::RegisterPortIoIntercept;
15use inspect::Inspect;
16use inspect::InspectMut;
17use inspect_counters::Counter;
18use std::num::Wrapping;
19use vmcore::device_state::ChangeDeviceState;
20use vmcore::line_interrupt::LineInterrupt;
21
22const PRIMARY_PIC_COMMAND_PORT: u16 = 0x20;
23const PRIMARY_PIC_DATA_PORT: u16 = 0x21;
24const SECONDARY_PIC_COMMAND_PORT: u16 = 0xa0;
25const SECONDARY_PIC_DATA_PORT: u16 = 0xa1;
26const PRIMARY_PIC_ELCR_PORT: u16 = 0x4d0;
27const SECONDARY_PIC_ELCR_PORT: u16 = 0x4d1;
28
29// x86 standard dictates we must use IRQ2 for cross-PIC communication.
30const PIC_CHAIN_COMMUNICATION_IRQ: u8 = 2;
31
32/// Mask for the IRQ vector within an individual PIC.
33const IRQ_MASK: u8 = 0b111;
34
35/// The spurious IRQ offset within an individual PIC.
36const SPURIOUS_IRQ: u8 = 7;
37
38#[derive(InspectMut)]
39pub struct DualPic {
40    // Runtime glue
41    ready: LineInterrupt,
42    #[inspect(iter_by_index)]
43    port_io_regions: [Box<dyn ControlPortIoIntercept>; 3],
44
45    // Runtime book-keeping
46    stats: DualPicStats,
47
48    // Volatile state
49    #[inspect(flatten, with = r#"|x| inspect::iter_by_index(x).prefix("pic")"#)]
50    pics: [Pic; 2],
51}
52
53#[derive(Inspect, Default)]
54struct DualPicStats {
55    #[inspect(iter_by_index)]
56    interrupts_per_irq: [Counter; 16],
57    interrupts: Counter,
58}
59
60impl DualPic {
61    pub fn new(ready: LineInterrupt, port_io: &mut dyn RegisterPortIoIntercept) -> Self {
62        let mut primary_region = port_io.new_io_region(
63            "primary",
64            (PRIMARY_PIC_COMMAND_PORT..=PRIMARY_PIC_DATA_PORT).len() as u16,
65        );
66        let mut secondary_region = port_io.new_io_region(
67            "secondary",
68            (SECONDARY_PIC_COMMAND_PORT..=SECONDARY_PIC_DATA_PORT).len() as u16,
69        );
70        let mut elcr_region = port_io.new_io_region(
71            "edge_level_control",
72            (PRIMARY_PIC_ELCR_PORT..=SECONDARY_PIC_ELCR_PORT).len() as u16,
73        );
74
75        primary_region.map(PRIMARY_PIC_COMMAND_PORT);
76        secondary_region.map(SECONDARY_PIC_COMMAND_PORT);
77        elcr_region.map(PRIMARY_PIC_ELCR_PORT);
78
79        DualPic {
80            pics: [Pic::new(true), Pic::new(false)],
81            ready,
82            port_io_regions: [primary_region, secondary_region, elcr_region],
83            stats: Default::default(),
84        }
85    }
86
87    /// Sync the interrupt outputs of each PIC with their connection (either the
88    /// primary PIC or the CPU).
89    fn sync_outputs(&mut self) {
90        self.pics[0].set_irq(
91            PIC_CHAIN_COMMUNICATION_IRQ,
92            self.pics[1].interrupt_pending(),
93        );
94        self.ready.set_level(self.pics[0].interrupt_pending());
95    }
96
97    fn set_irq(&mut self, n: u8, high: bool) {
98        if n >= 8 {
99            self.pics[1].set_irq(n - 8, high);
100        } else {
101            self.pics[0].set_irq(n, high);
102        }
103        self.sync_outputs();
104    }
105}
106
107impl AcknowledgePicInterrupt for DualPic {
108    fn acknowledge_interrupt(&mut self) -> Option<u8> {
109        let (requested, n) = self.pics[0].acknowledge_interrupt(&mut self.stats);
110        let irq = requested.then(|| {
111            if n & IRQ_MASK == PIC_CHAIN_COMMUNICATION_IRQ {
112                let (requested, m) = self.pics[1].acknowledge_interrupt(&mut self.stats);
113                assert!(requested, "pic1 ready was set");
114                m
115            } else {
116                n
117            }
118        });
119        // Sync to ensure the IRQ2 line gets lowered while the secondary PIC's
120        // interrupt is in service.
121        self.sync_outputs();
122        irq
123    }
124}
125
126impl LineInterruptTarget for DualPic {
127    fn set_irq(&mut self, n: u32, high: bool) {
128        self.set_irq(n as u8, high);
129    }
130
131    fn valid_lines(&self) -> &[std::ops::RangeInclusive<u32>] {
132        // IRQ2 is used to cascade the secondary PIC to the primary PIC, so it
133        // is not available for use.
134        &[0..=1, 3..=15]
135    }
136}
137
138impl ChangeDeviceState for DualPic {
139    fn start(&mut self) {}
140
141    async fn stop(&mut self) {}
142
143    async fn reset(&mut self) {
144        for pic in &mut self.pics {
145            pic.reset(false);
146        }
147        self.sync_outputs();
148    }
149}
150
151impl ChipsetDevice for DualPic {
152    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
153        Some(self)
154    }
155
156    fn supports_line_interrupt_target(&mut self) -> Option<&mut dyn LineInterruptTarget> {
157        Some(self)
158    }
159
160    fn supports_acknowledge_pic_interrupt(&mut self) -> Option<&mut dyn AcknowledgePicInterrupt> {
161        Some(self)
162    }
163}
164
165impl PortIoIntercept for DualPic {
166    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
167        if data.len() != 1 {
168            return IoResult::Err(IoError::InvalidAccessSize);
169        }
170        data[0] = match io_port {
171            PRIMARY_PIC_COMMAND_PORT => self.pics[0].read_command(&mut self.stats),
172            PRIMARY_PIC_DATA_PORT => self.pics[0].read_data(),
173            SECONDARY_PIC_COMMAND_PORT => self.pics[1].read_command(&mut self.stats),
174            SECONDARY_PIC_DATA_PORT => self.pics[1].read_data(),
175            PRIMARY_PIC_ELCR_PORT => self.pics[0].elcr,
176            SECONDARY_PIC_ELCR_PORT => self.pics[1].elcr,
177            _ => return IoResult::Err(IoError::InvalidRegister),
178        };
179        IoResult::Ok
180    }
181
182    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
183        // Hyper-V-specific optimization: two-byte write to the command
184        // port can be used to write to both command ports in a single
185        // instruction.
186        if data.len() == 2
187            && (io_port == PRIMARY_PIC_COMMAND_PORT || io_port == SECONDARY_PIC_COMMAND_PORT)
188        {
189            let &[mut prim, mut sec] = data else {
190                unreachable!()
191            };
192            if io_port == SECONDARY_PIC_COMMAND_PORT {
193                (prim, sec) = (sec, prim);
194            }
195            self.pics[0].write_command(prim);
196            self.pics[1].write_command(sec);
197            self.sync_outputs();
198            return IoResult::Ok;
199        }
200
201        if data.len() != 1 {
202            return IoResult::Err(IoError::InvalidAccessSize);
203        }
204
205        match io_port {
206            PRIMARY_PIC_COMMAND_PORT => self.pics[0].write_command(data[0]),
207            PRIMARY_PIC_DATA_PORT => self.pics[0].write_data(data[0]),
208            SECONDARY_PIC_COMMAND_PORT => self.pics[1].write_command(data[0]),
209            SECONDARY_PIC_DATA_PORT => self.pics[1].write_data(data[0]),
210            PRIMARY_PIC_ELCR_PORT => self.pics[0].elcr = data[0],
211            SECONDARY_PIC_ELCR_PORT => self.pics[1].elcr = data[0],
212            _ => return IoResult::Err(IoError::InvalidRegister),
213        }
214
215        self.sync_outputs();
216        IoResult::Ok
217    }
218}
219
220/// The PIC is controlled through two categories of commands:
221/// ICWs: Initialization Control Words
222/// OCWs: Operation Command Words
223#[derive(Debug, Copy, Clone, Inspect)]
224struct Pic {
225    /// Our current stage of the initialization process.
226    init: InitStage,
227
228    /// Whether or not this PIC is the primary in the dual-pic chain.
229    #[inspect(skip)]
230    primary: bool,
231
232    /// The Interrupt Vector base address, passed in ICW2.
233    /// The least significant 3 bits must be 0.
234    #[inspect(hex)]
235    icw2: u8,
236
237    /// Interrupt Mask Register, also called OCW1
238    #[inspect(binary)]
239    imr: u8,
240
241    /// The most recently received OCW3
242    ocw3: Ocw3,
243
244    /// Interrupt Service Register
245    #[inspect(binary)]
246    isr: u8,
247
248    /// Edge/Level Control Register
249    #[inspect(binary)]
250    elcr: u8,
251
252    /// The current status of all the interrupt request lines
253    #[inspect(binary)]
254    lines: u8,
255
256    /// Whether the line has been low since the last interrupt injection. Used
257    /// to track whether an edge-triggered interrupt should be requested when
258    /// its line is high.
259    #[inspect(binary)]
260    line_low_latch: u8,
261}
262
263#[derive(Copy, Clone, Debug, Inspect)]
264enum InitStage {
265    Uninitialized,
266    ExpectingIcw2,
267    ExpectingIcw3,
268    ExpectingIcw4,
269    Initialized,
270}
271
272impl Pic {
273    fn new(primary: bool) -> Self {
274        Pic {
275            imr: 0,
276            init: InitStage::Uninitialized,
277            // This is a hack to help set_irq_in_service disambiguate pics before initialization.
278            // Set this back to always 0 when that goes away.
279            icw2: if primary { 0 } else { 8 },
280            ocw3: Ocw3(0),
281            isr: 0,
282            elcr: 0,
283            primary,
284            lines: 0,
285            line_low_latch: !0,
286        }
287    }
288
289    /// Resets the PIC state, preserving the line state. If `during_icw1`,
290    /// preserve ELCR.
291    fn reset(&mut self, during_icw1: bool) {
292        *self = Self {
293            lines: self.lines,
294            elcr: if during_icw1 { self.elcr } else { 0 },
295            ..Self::new(self.primary)
296        };
297    }
298
299    fn irr(&self) -> u8 {
300        self.lines & (self.elcr | self.line_low_latch)
301    }
302
303    fn set_irq(&mut self, n: u8, high: bool) {
304        let bit = 1 << n;
305        if high {
306            if !matches!(self.init, InitStage::Initialized) {
307                tracelimit::warn_ratelimited!(
308                    primary = self.primary,
309                    ?n,
310                    "interrupt request sent to uninitialized PIC"
311                );
312            }
313            self.lines |= bit;
314        } else {
315            self.lines &= !bit;
316            self.line_low_latch |= bit;
317        }
318    }
319
320    fn ready_vec(&self) -> u8 {
321        // Restrict to interrupts with higher priority than the highest priority
322        // IRQ already being serviced.
323        let isr = Wrapping(self.isr);
324        let highest_isr = isr & -isr;
325        let higher_not_isr = highest_isr - Wrapping(1);
326        // Restrict to requested interrupts that are not masked.
327        self.irr() & !self.imr & higher_not_isr.0
328    }
329
330    fn interrupt_pending(&self) -> bool {
331        self.ready_vec() != 0
332    }
333
334    fn pending_line(&self) -> Option<u8> {
335        let m = self.ready_vec();
336        if m != 0 {
337            // Find the highest priority IRQ
338            Some(m.trailing_zeros() as u8)
339        } else {
340            None
341        }
342    }
343
344    fn acknowledge_interrupt(&mut self, stats: &mut DualPicStats) -> (bool, u8) {
345        if !matches!(self.init, InitStage::Initialized) {
346            tracelimit::warn_ratelimited!(
347                primary = self.primary,
348                "interrupt servicing sent to uninitialized PIC"
349            );
350        }
351
352        let (requested, irq) = if let Some(n) = self.pending_line() {
353            let bit = 1 << n;
354            // Clear the edge latch so that the line must be low again before
355            // another interrupt is injected.
356            self.line_low_latch &= !bit;
357            // Set in service.
358            self.isr |= bit;
359            stats.interrupts.increment();
360            stats.interrupts_per_irq[if self.primary { 0 } else { 8 } + n as usize].increment();
361            (true, n)
362        } else {
363            // spurious interrupt
364            (false, SPURIOUS_IRQ)
365        };
366
367        // Combine the IRQ with the base address to construct the full interrupt
368        // service routine address.
369        assert!(self.icw2 & IRQ_MASK == 0);
370        (requested, self.icw2 | irq)
371    }
372
373    fn eoi(&mut self, n: Option<u8>) {
374        tracing::trace!(primary = self.primary, n, "eoi");
375        let bit = match n {
376            Some(level) => 1 << level,
377            // On non-specific EOIs, find the highest priority interrupt in service
378            None => self.isr & self.isr.wrapping_neg(),
379        };
380
381        self.isr &= !bit;
382    }
383
384    fn read_command(&mut self, stats: &mut DualPicStats) -> u8 {
385        if self.ocw3.p() {
386            self.ocw3.set_p(false);
387            let (int, irq) = self.acknowledge_interrupt(stats);
388            ((int as u8) << 7) | (irq & IRQ_MASK)
389        } else if self.ocw3.rr() {
390            if self.ocw3.ris() {
391                self.isr
392            } else {
393                self.irr()
394            }
395        } else {
396            0
397        }
398    }
399
400    fn read_data(&self) -> u8 {
401        self.imr
402    }
403
404    fn write_command(&mut self, data: u8) {
405        const INIT_BIT: u8 = 0b10000;
406        const COMMAND_BIT: u8 = 0b1000;
407
408        if data & INIT_BIT != 0 {
409            // ICW1
410            // We do not support Single PIC mode or Level Triggered mode.
411            // We require IC4 to be set so we can be put in x86 mode in ICW4.
412            if data != 0b00010001 {
413                tracelimit::error_ratelimited!(primary = self.primary, ?data, "unsupported ICW1");
414            }
415
416            self.reset(true);
417            self.init = InitStage::ExpectingIcw2;
418        } else {
419            if !matches!(self.init, InitStage::Initialized) {
420                tracelimit::warn_ratelimited!(
421                    primary = self.primary,
422                    ?data,
423                    "OCW sent to uninitialized PIC"
424                );
425            }
426            if data & COMMAND_BIT == 0 {
427                let ocw2 = Ocw2(data);
428
429                match (ocw2.r(), ocw2.sl(), ocw2.eoi()) {
430                    (true, _, _) | (false, false, false) => {
431                        tracelimit::error_ratelimited!(
432                            primary = self.primary,
433                            ?data,
434                            "unsupported OCW2"
435                        )
436                    }
437                    (false, true, true) => self.eoi(Some(ocw2.level())),
438                    (false, false, true) => self.eoi(None),
439                    (false, true, false) => {} // No-op
440                }
441            } else {
442                self.ocw3 = Ocw3(data);
443                // We do not support Special Mask Mode.
444                if self.ocw3.esmm() || self.ocw3.smm() {
445                    tracelimit::error_ratelimited!(
446                        primary = self.primary,
447                        ?data,
448                        "unsupported OCW3"
449                    );
450                }
451            }
452        }
453    }
454
455    fn write_data(&mut self, data: u8) {
456        match self.init {
457            InitStage::Uninitialized | InitStage::Initialized => {
458                self.imr = data; // OCW1
459            }
460            InitStage::ExpectingIcw2 => {
461                if data & IRQ_MASK != 0 {
462                    tracelimit::error_ratelimited!(primary = self.primary, ?data, "invalid ICW2");
463                }
464                self.icw2 = data & !IRQ_MASK;
465                self.init = InitStage::ExpectingIcw3;
466            }
467            InitStage::ExpectingIcw3 => {
468                // x86 standard dictates we must use IRQ2 for cross-PIC communication.
469                if self.primary {
470                    if data != (1 << PIC_CHAIN_COMMUNICATION_IRQ) {
471                        tracelimit::error_ratelimited!(
472                            primary = self.primary,
473                            ?data,
474                            "invalid primary ICW3"
475                        );
476                    }
477                } else {
478                    if data != PIC_CHAIN_COMMUNICATION_IRQ {
479                        tracelimit::error_ratelimited!(
480                            primary = self.primary,
481                            ?data,
482                            "invalid secondary ICW3"
483                        );
484                    }
485                }
486
487                self.init = InitStage::ExpectingIcw4;
488            }
489            InitStage::ExpectingIcw4 => {
490                // We do not support any advanced operating modes controlled by ICW4.
491                // We require being put into x86 mode.
492                if data != 1 {
493                    // Linux sends an ICW4 of 3 during boot, then very quickly overwrites it
494                    if data == 3 {
495                        tracing::debug!(
496                            primary = self.primary,
497                            "got ICW4 of 3, this is expected for Linux boot but not any other time"
498                        );
499                    } else {
500                        tracelimit::error_ratelimited!(
501                            primary = self.primary,
502                            ?data,
503                            "unsupported ICW4"
504                        );
505                    }
506                };
507                self.init = InitStage::Initialized;
508            }
509        }
510    }
511}
512
513#[bitfield(u8)]
514/// Operation Command Word 2
515struct Ocw2 {
516    /// Interrupt level
517    #[bits(3)]
518    level: u8,
519
520    #[bits(2)]
521    _command: u8,
522
523    /// End of Interrupt
524    eoi: bool,
525
526    /// Selection
527    sl: bool,
528
529    /// Rotation
530    r: bool,
531}
532
533#[derive(Inspect)]
534#[bitfield(u8)]
535/// Operation Command Word 3
536struct Ocw3 {
537    /// Read Selector
538    ris: bool,
539
540    /// Read Register
541    rr: bool,
542
543    /// Polling
544    p: bool,
545
546    #[bits(2)]
547    _command: u8,
548
549    /// Special Mask Mode
550    smm: bool,
551
552    /// Enable Special Mask Mode
553    esmm: bool,
554
555    _reserved: bool,
556}
557
558mod save_restore {
559    use super::DualPic;
560    use super::IRQ_MASK;
561    use super::InitStage;
562    use super::Ocw3;
563    use super::Pic;
564    use thiserror::Error;
565    use vmcore::save_restore::RestoreError;
566    use vmcore::save_restore::SaveError;
567    use vmcore::save_restore::SaveRestore;
568
569    mod state {
570        use mesh::payload::Protobuf;
571        use vmcore::save_restore::SavedStateRoot;
572
573        #[derive(Protobuf, SavedStateRoot)]
574        #[mesh(package = "chipset.pic")]
575        pub struct SavedState {
576            #[mesh(1)]
577            pub(super) primary: SavedPic,
578            #[mesh(2)]
579            pub(super) secondary: SavedPic,
580        }
581
582        #[derive(Protobuf)]
583        #[mesh(package = "chipset.pic")]
584        pub struct SavedPic {
585            #[mesh(1)]
586            pub init: SavedInitStage,
587            #[mesh(2)]
588            pub icw2: u8,
589            #[mesh(3)]
590            pub imr: u8,
591            #[mesh(4)]
592            pub ocw3: u8,
593            #[mesh(5)]
594            pub isr: u8,
595            #[mesh(6)]
596            pub elcr: u8,
597            #[mesh(7)]
598            pub line_low_latch: u8,
599        }
600
601        #[derive(Protobuf)]
602        #[mesh(package = "chipset.pic")]
603        pub enum SavedInitStage {
604            #[mesh(1)]
605            Uninitialized,
606            #[mesh(2)]
607            ExpectingIcw2,
608            #[mesh(3)]
609            ExpectingIcw3,
610            #[mesh(4)]
611            ExpectingIcw4,
612            #[mesh(5)]
613            Initialized,
614        }
615    }
616
617    #[derive(Debug, Error)]
618    enum Error {
619        #[error("invalid icw2 value {0:#x}")]
620        InvalidIcw2(u8),
621    }
622
623    impl state::SavedPic {
624        fn restore(self, pic: &mut Pic) -> Result<(), Error> {
625            let Self {
626                init,
627                icw2,
628                imr,
629                ocw3,
630                isr,
631                elcr,
632                line_low_latch,
633            } = self;
634            pic.init = match init {
635                state::SavedInitStage::Uninitialized => InitStage::Uninitialized,
636                state::SavedInitStage::ExpectingIcw2 => InitStage::ExpectingIcw2,
637                state::SavedInitStage::ExpectingIcw3 => InitStage::ExpectingIcw3,
638                state::SavedInitStage::ExpectingIcw4 => InitStage::ExpectingIcw4,
639                state::SavedInitStage::Initialized => InitStage::Initialized,
640            };
641            if icw2 & IRQ_MASK != 0 {
642                return Err(Error::InvalidIcw2(icw2));
643            }
644            pic.icw2 = icw2;
645            pic.imr = imr;
646            pic.ocw3 = Ocw3(ocw3);
647            pic.isr = isr;
648            pic.elcr = elcr;
649            pic.line_low_latch = line_low_latch;
650            Ok(())
651        }
652
653        fn save(pic: &Pic) -> Self {
654            let &Pic {
655                init,
656                primary: _,
657                icw2,
658                imr,
659                ocw3,
660                isr,
661                elcr,
662                lines: _,
663                line_low_latch,
664            } = pic;
665            Self {
666                init: match init {
667                    InitStage::Uninitialized => state::SavedInitStage::Uninitialized,
668                    InitStage::ExpectingIcw2 => state::SavedInitStage::ExpectingIcw2,
669                    InitStage::ExpectingIcw3 => state::SavedInitStage::ExpectingIcw3,
670                    InitStage::ExpectingIcw4 => state::SavedInitStage::ExpectingIcw4,
671                    InitStage::Initialized => state::SavedInitStage::Initialized,
672                },
673                icw2,
674                imr,
675                ocw3: ocw3.0,
676                isr,
677                elcr,
678                line_low_latch,
679            }
680        }
681    }
682
683    impl SaveRestore for DualPic {
684        type SavedState = state::SavedState;
685
686        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
687            let Self {
688                ready: _,
689                stats: _,
690                port_io_regions: _,
691                pics: [primary, secondary],
692            } = &self;
693
694            Ok(state::SavedState {
695                primary: state::SavedPic::save(primary),
696                secondary: state::SavedPic::save(secondary),
697            })
698        }
699
700        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
701            let state::SavedState { primary, secondary } = state;
702            primary
703                .restore(&mut self.pics[0])
704                .map_err(|err| RestoreError::Other(err.into()))?;
705            secondary
706                .restore(&mut self.pics[1])
707                .map_err(|err| RestoreError::Other(err.into()))?;
708            self.sync_outputs();
709            Ok(())
710        }
711    }
712}
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717    use chipset_device::pio::ExternallyManagedPortIoIntercepts;
718    use chipset_device::pio::PortIoIntercept;
719    use vmcore::line_interrupt::test_helpers::TestLineInterruptTarget;
720
721    const IV_BASE: u8 = 0x30;
722
723    fn create_pic() -> (impl Fn() -> bool, DualPic) {
724        let ready = TestLineInterruptTarget::new_arc();
725        let mut pic = DualPic::new(
726            LineInterrupt::new_with_target("ready", ready.clone(), 0),
727            &mut ExternallyManagedPortIoIntercepts,
728        );
729
730        // Initialization sequence copied by logging what Linux Direct does:
731        pic.io_write(0x20, &[0x11]).unwrap();
732        pic.io_write(0x21, &[IV_BASE]).unwrap(); // Primary ICW2
733        pic.io_write(0x21, &[0x04]).unwrap();
734        pic.io_write(0x21, &[0x01]).unwrap();
735        pic.io_write(0xa0, &[0x11]).unwrap();
736        pic.io_write(0xa1, &[IV_BASE + 8]).unwrap(); // Secondary ICW2
737        pic.io_write(0xa1, &[0x02]).unwrap();
738        pic.io_write(0xa1, &[0x01]).unwrap();
739
740        (move || ready.is_high(0), pic)
741    }
742
743    fn send_eoi(pic: &mut DualPic, consts: PicTestConstants) {
744        let (v, _) = consts;
745        match v {
746            0..=7 => pic
747                .io_write(
748                    PRIMARY_PIC_COMMAND_PORT,
749                    &[Ocw2::new().with_eoi(true).with_sl(true).with_level(v).0],
750                )
751                .unwrap(),
752            8..=15 => {
753                pic.io_write(
754                    PRIMARY_PIC_COMMAND_PORT,
755                    &[Ocw2::new()
756                        .with_eoi(true)
757                        .with_sl(true)
758                        .with_level(PIC_CHAIN_COMMUNICATION_IRQ)
759                        .0],
760                )
761                .unwrap();
762                pic.io_write(
763                    SECONDARY_PIC_COMMAND_PORT,
764                    &[Ocw2::new().with_eoi(true).with_sl(true).with_level(v - 8).0],
765                )
766                .unwrap();
767            }
768            _ => unreachable!(),
769        }
770    }
771
772    #[test]
773    fn test_create_pic() {
774        let (ready, pic) = create_pic();
775        assert!(matches!(pic.pics[0].init, InitStage::Initialized));
776        assert!(matches!(pic.pics[1].init, InitStage::Initialized));
777        assert!(!ready());
778    }
779
780    // (Vector, pics index)
781    type PicTestConstants = (u8, usize);
782    const PRIMARY: PicTestConstants = (0, 0);
783    const SECONDARY: PicTestConstants = (8, 1);
784
785    #[test]
786    fn test_edge_interrupt_primary() {
787        test_edge_interrupt(PRIMARY);
788    }
789
790    #[test]
791    fn test_edge_interrupt_secondary() {
792        test_edge_interrupt(SECONDARY);
793    }
794
795    fn test_edge_interrupt(consts: PicTestConstants) {
796        let (ready, mut pic) = create_pic();
797        let (v, i) = consts;
798
799        pic.set_irq(v, true);
800        assert!(ready());
801        assert!(pic.pics[i].irr() == 0b1);
802        pic.set_irq(v, false);
803        assert!(pic.pics[i].irr() == 0b0);
804        let pending = pic.acknowledge_interrupt();
805        assert!(pending.is_none());
806
807        assert!(!ready());
808
809        pic.set_irq(v, true);
810        assert!(ready());
811        assert!(pic.pics[i].irr() == 0b1);
812
813        let pending = pic.acknowledge_interrupt();
814        assert_eq!(pending, Some(IV_BASE + v));
815        assert!(pic.pics[i].irr() == 0);
816        assert!(pic.pics[i].isr == 0b1);
817
818        send_eoi(&mut pic, consts);
819
820        assert!(pic.pics[i].irr() == 0);
821        assert!(pic.pics[i].isr == 0);
822        let pending = pic.acknowledge_interrupt();
823        assert!(pending.is_none());
824
825        assert!(!ready());
826
827        pic.set_irq(v, true);
828        assert!(pic.pics[i].irr() == 0b0);
829        pic.set_irq(v, false);
830        pic.set_irq(v, true);
831        assert!(pic.pics[i].irr() == 0b1);
832        assert!(ready());
833
834        pic.set_irq(v, false);
835        assert!(pic.pics[i].irr() == 0b0);
836    }
837
838    #[test]
839    fn test_level_interrupts_primary() {
840        test_level_interrupts(PRIMARY);
841    }
842
843    #[test]
844    fn test_level_interrupts_secondary() {
845        test_level_interrupts(SECONDARY);
846    }
847
848    fn test_level_interrupts(consts: PicTestConstants) {
849        let (ready, mut pic) = create_pic();
850        let (v, i) = consts;
851
852        pic.io_write(
853            match i {
854                0 => PRIMARY_PIC_ELCR_PORT,
855                1 => SECONDARY_PIC_ELCR_PORT,
856                _ => unreachable!(),
857            },
858            &[0b1],
859        )
860        .unwrap();
861
862        pic.set_irq(v, true);
863        assert!(ready());
864        assert!(pic.pics[i].irr() == 0b1);
865        pic.set_irq(v, false);
866        assert!(pic.pics[i].irr() == 0b0);
867        let pending = pic.acknowledge_interrupt();
868
869        // No spurious interrupt is possible with the current interface.
870        assert!(pending.is_none());
871
872        assert!(!ready());
873
874        pic.set_irq(v, true);
875        assert!(ready());
876        assert!(pic.pics[i].irr() == 0b1);
877
878        let pending = pic.acknowledge_interrupt();
879        assert_eq!(pending, Some(IV_BASE + v));
880        assert!(pic.pics[i].irr() == 0b1);
881        assert!(pic.pics[i].isr == 0b1);
882
883        send_eoi(&mut pic, consts);
884        assert!(pic.pics[i].irr() == 0b1);
885        assert!(pic.pics[i].isr == 0);
886
887        let pending = pic.acknowledge_interrupt();
888        assert_eq!(pending, Some(IV_BASE + v));
889        pic.set_irq(v, false);
890        send_eoi(&mut pic, consts);
891        assert!(pic.pics[i].irr() == 0);
892        let pending = pic.acknowledge_interrupt();
893        assert!(pending.is_none());
894    }
895
896    #[test]
897    fn test_multiple_edge_interrupt_primary() {
898        test_multiple_edge_interrupt(PRIMARY);
899    }
900
901    #[test]
902    fn test_multiple_edge_interrupt_secondary() {
903        test_multiple_edge_interrupt(SECONDARY);
904    }
905
906    fn test_multiple_edge_interrupt(consts: PicTestConstants) {
907        let (ready, mut pic) = create_pic();
908        let (v, i) = consts;
909
910        pic.set_irq(v, true);
911        assert!(ready());
912        assert!(pic.pics[i].irr() == 0b1);
913        pic.set_irq(v, false);
914        assert!(pic.pics[i].irr() == 0b0);
915
916        pic.set_irq(v + 1, true);
917        assert!(pic.pics[i].irr() == 0b10);
918        pic.set_irq(v + 1, false);
919        assert!(pic.pics[i].irr() == 0b00);
920
921        assert!(!ready());
922
923        pic.set_irq(v, true);
924        assert!(ready());
925        assert!(pic.pics[i].irr() == 0b1);
926        pic.set_irq(v + 1, true);
927        assert!(pic.pics[i].irr() == 0b11);
928
929        let pending = pic.acknowledge_interrupt();
930        assert_eq!(pending, Some(IV_BASE + v));
931        assert!(pic.pics[i].irr() == 0b10);
932        assert!(pic.pics[i].isr == 0b01);
933
934        send_eoi(&mut pic, consts);
935        assert!(pic.pics[i].irr() == 0b10);
936        assert!(pic.pics[i].isr == 0b00);
937        assert!(ready());
938        let pending = pic.acknowledge_interrupt();
939        assert_eq!(pending, Some(IV_BASE + 1 + v));
940        assert!(!ready());
941    }
942
943    #[test]
944    fn test_non_specific_eois() {
945        let (_, mut pic) = create_pic();
946
947        pic.set_irq(5, true);
948        assert_eq!(pic.acknowledge_interrupt(), Some(IV_BASE + 5));
949
950        pic.set_irq(3, true);
951        assert_eq!(pic.acknowledge_interrupt(), Some(IV_BASE + 3));
952
953        pic.set_irq(1, true);
954        assert_eq!(pic.acknowledge_interrupt(), Some(IV_BASE + 1));
955
956        assert_eq!(pic.pics[0].isr, 0b101010);
957
958        pic.io_write(
959            PRIMARY_PIC_COMMAND_PORT,
960            &[Ocw2::new().with_eoi(true).with_sl(false).0],
961        )
962        .unwrap();
963
964        assert_eq!(pic.pics[0].isr, 0b101000);
965    }
966}