1pub 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
28const PIC_CHAIN_COMMUNICATION_IRQ: u8 = 2;
30
31const IRQ_MASK: u8 = 0b111;
33
34const SPURIOUS_IRQ: u8 = 7;
36
37#[derive(InspectMut)]
38pub struct DualPic {
39 ready: LineInterrupt,
41 #[inspect(iter_by_index)]
42 port_io_regions: [Box<dyn ControlPortIoIntercept>; 3],
43
44 stats: DualPicStats,
46
47 #[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 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 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 &[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 self.sync_outputs();
180 IoResult::Ok
181 }
182
183 fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
184 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#[derive(Debug, Copy, Clone, Inspect)]
225struct Pic {
226 init: InitStage,
228
229 #[inspect(skip)]
231 primary: bool,
232
233 #[inspect(hex)]
236 icw2: u8,
237
238 #[inspect(binary)]
240 imr: u8,
241
242 ocw3: Ocw3,
244
245 #[inspect(binary)]
247 isr: u8,
248
249 #[inspect(binary)]
251 elcr: u8,
252
253 #[inspect(binary)]
255 lines: u8,
256
257 #[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 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 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 let highest_isr = self.isr.isolate_lowest_one();
325 let higher_not_isr = highest_isr.wrapping_sub(1);
326 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 self.line_low_latch &= !bit;
351 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 (false, SPURIOUS_IRQ)
359 };
360
361 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 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 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) => {} }
435 } else {
436 self.ocw3 = Ocw3(data);
437 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; }
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 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 if data != 1 {
487 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)]
508struct Ocw2 {
510 #[bits(3)]
512 level: u8,
513
514 #[bits(2)]
515 _command: u8,
516
517 eoi: bool,
519
520 sl: bool,
522
523 r: bool,
525}
526
527#[derive(Inspect)]
528#[bitfield(u8)]
529struct Ocw3 {
531 ris: bool,
533
534 rr: bool,
536
537 p: bool,
539
540 #[bits(2)]
541 _command: u8,
542
543 smm: bool,
545
546 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 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 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 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 pic.io_write(0x20, &[0x11]).unwrap();
852 pic.io_write(0x21, &[IV_BASE]).unwrap(); 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(); 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(); 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(); 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(); 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 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 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}