chipset/
pic.rs

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