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