Skip to main content

chipset/ioapic/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! IO-APIC emulator.
5//!
6//! Currently this supports IO-APIC version 0x11, from the PIIX4 era. This
7//! version does not support an EOI register (unlike version 0x20 and newer).
8
9use self::spec::IO_APIC_VERSION;
10use self::spec::IOAPIC_DEVICE_MMIO_REGION_SIZE;
11use self::spec::IndexRegister;
12use self::spec::IoApicId;
13use self::spec::IoApicVersion;
14use self::spec::REDIRECTION_WRITE_MASK;
15use self::spec::RedirectionEntry;
16use crate::ioapic::spec::IOAPIC_DEVICE_MMIO_REGION_MASK;
17use crate::ioapic::spec::Register;
18use chipset_device::ChipsetDevice;
19use chipset_device::interrupt::HandleEoi;
20use chipset_device::interrupt::LineInterruptTarget;
21use chipset_device::io::IoError;
22use chipset_device::io::IoResult;
23use chipset_device::mmio::MmioIntercept;
24use chipset_resources::ioapic::IoApicRouting;
25use inspect::Inspect;
26use inspect::InspectMut;
27use inspect_counters::Counter;
28use std::fmt::Debug;
29use std::ops::RangeInclusive;
30use vmcore::device_state::ChangeDeviceState;
31use x86defs::apic::DeliveryMode;
32use x86defs::msi::MsiAddress;
33use x86defs::msi::MsiData;
34
35pub mod resolver;
36
37pub const IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS: u64 =
38    acpi_spec::hyperv::IOAPIC_BASE_ADDRESS as u64;
39
40mod spec {
41    use bitfield_struct::bitfield;
42    use inspect::Inspect;
43    use open_enum::open_enum;
44
45    /// The version reported by the Intel 82093AA.
46    pub const IO_APIC_VERSION: u8 = 0x11;
47
48    open_enum! {
49        pub enum Register: u64 {
50            INDEX = 0,
51            DATA = 0x10,
52        }
53    }
54
55    open_enum! {
56        #[derive(Inspect)]
57        #[inspect(debug)]
58        pub enum IndexRegister: u8 {
59            ID = 0,
60            VERSION = 1,
61            ARBITRATION_ID = 2,
62            REDIRECTION_TABLE_START = 0x10,
63        }
64    }
65
66    pub const IOAPIC_DEVICE_MMIO_REGION_SIZE: u64 = 0x20;
67    pub const IOAPIC_DEVICE_MMIO_REGION_MASK: u64 = IOAPIC_DEVICE_MMIO_REGION_SIZE - 1;
68
69    #[derive(Inspect)]
70    #[bitfield(u64)]
71    #[rustfmt::skip]
72    pub struct RedirectionEntry {
73        #[bits(8)] pub vector: u8,
74        #[bits(3)] pub delivery_mode: u8,
75        #[bits(1)] pub destination_mode_logical: bool,
76        #[bits(1)] pub delivery_status: bool,
77        #[bits(1)] pub active_low: bool,
78        #[bits(1)] pub remote_irr: bool,
79        #[bits(1)] pub trigger_mode_level: bool,
80        #[bits(1)] pub masked: bool,
81        #[bits(15)] _unused: u32,
82        #[bits(16)] _unused2: u32,
83        #[bits(8)] pub extended_destination: u8,
84        #[bits(8)] pub destination: u8,
85    }
86
87    /// The bits of the redirection entry that can be set by the guest.
88    pub const REDIRECTION_WRITE_MASK: u64 = RedirectionEntry::new()
89        .with_vector(0xff)
90        .with_delivery_mode(0x7)
91        .with_destination_mode_logical(true)
92        .with_active_low(true)
93        .with_trigger_mode_level(true)
94        .with_masked(true)
95        .with_destination(0xff)
96        .with_extended_destination(0xff)
97        .0;
98
99    #[bitfield(u32)]
100    pub struct IoApicId {
101        #[bits(24)]
102        _reserved: u32,
103        #[bits(4)]
104        pub id: u8,
105        #[bits(4)]
106        _reserved2: u32,
107    }
108
109    #[bitfield(u32)]
110    pub struct IoApicVersion {
111        pub version: u8,
112        _reserved: u8,
113        pub max_entry: u8,
114        _reserved2: u8,
115    }
116}
117
118impl RedirectionEntry {
119    fn init() -> Self {
120        Self::new()
121            .with_destination_mode_logical(true)
122            .with_masked(true)
123    }
124
125    /// Converts the entry to MSI format.
126    fn as_msi(&self) -> Option<(u64, u32)> {
127        if self.masked() {
128            return None;
129        }
130
131        // Copy the redirection entry's bits to the MSI as defined in the Intel
132        // ICHx datasheets.
133        let address = MsiAddress::new()
134            .with_address(x86defs::msi::MSI_ADDRESS)
135            .with_destination(self.destination())
136            .with_extended_destination(self.extended_destination())
137            .with_destination_mode_logical(self.destination_mode_logical())
138            .with_redirection_hint(self.delivery_mode() == DeliveryMode::LOWEST_PRIORITY.0);
139
140        let data = MsiData::new()
141            .with_assert(true)
142            .with_destination_mode_logical(self.destination_mode_logical())
143            .with_delivery_mode(self.delivery_mode())
144            .with_trigger_mode_level(self.trigger_mode_level())
145            .with_vector(self.vector());
146
147        Some((u32::from(address).into(), data.into()))
148    }
149}
150
151#[derive(Debug, Inspect)]
152struct IrqEntry {
153    #[inspect(flatten)]
154    redirection: RedirectionEntry,
155    line_level: bool,
156    #[inspect(skip)]
157    registered_request: Option<(u64, u32)>,
158}
159
160impl IrqEntry {
161    fn assert(&mut self, routing: &dyn IoApicRouting, stats: &mut IoApicStats, n: u8) {
162        let old_level = std::mem::replace(&mut self.line_level, true);
163        self.evaluate(routing, stats, n, !old_level);
164    }
165
166    fn deassert(&mut self) {
167        self.line_level = false;
168        // No need to evaluate; this interrupt definitely doesn't need to be
169        // delivered now.
170    }
171
172    fn eoi(&mut self, vector: u32, routing: &dyn IoApicRouting, stats: &mut IoApicStats, n: u8) {
173        if self.redirection.vector() as u32 == vector {
174            // Clear remote IRR to allow further interrupts (if level
175            // triggered).
176            self.redirection.set_remote_irr(false);
177            self.evaluate(routing, stats, n, false);
178        }
179    }
180
181    fn evaluate(
182        &mut self,
183        routing: &dyn IoApicRouting,
184        stats: &mut IoApicStats,
185        n: u8,
186        edge: bool,
187    ) {
188        // Only some delivery modes support level trigger mode.
189        let is_level = self.redirection.trigger_mode_level()
190            && matches!(
191                DeliveryMode(self.redirection.delivery_mode()),
192                DeliveryMode::FIXED | DeliveryMode::LOWEST_PRIORITY
193            );
194
195        // Masked edge-triggered interrupts are lost. Masked level-triggered
196        // interrupts are reevaluated when the interrupt is unmasked.
197        if self.redirection.masked()
198            || (is_level && (!self.line_level || self.redirection.remote_irr()))
199            || (!is_level && !edge)
200        {
201            return;
202        }
203
204        // Remote IRR tracks whether a level-triggered interrupt has been EOIed yet.
205        self.redirection.set_remote_irr(is_level);
206        stats.interrupts.increment();
207        stats.interrupts_per_irq[n as usize].increment();
208        routing.assert(n);
209    }
210}
211
212#[derive(Debug, InspectMut)]
213pub struct IoApicDevice {
214    // Static configuration
215    #[inspect(skip)]
216    valid_lines: [RangeInclusive<u32>; 1],
217
218    // Runtime glue
219    #[inspect(skip)]
220    routing: Box<dyn IoApicRouting>,
221
222    // Runtime book-keeping
223    stats: IoApicStats,
224
225    // Volatile state
226    #[inspect(with = r#"|x| inspect::iter_by_index(x.iter())"#)]
227    irqs: Box<[IrqEntry]>,
228    id: u8,
229    index: IndexRegister,
230}
231
232#[derive(Debug, Inspect)]
233struct IoApicStats {
234    #[inspect(iter_by_index)]
235    interrupts_per_irq: Vec<Counter>,
236    interrupts: Counter,
237}
238
239impl IoApicDevice {
240    pub fn new(num_entries: u8, routing: Box<dyn IoApicRouting>) -> Self {
241        let irqs = (0..num_entries)
242            .map(|_| IrqEntry {
243                redirection: RedirectionEntry::init(),
244                line_level: false,
245                registered_request: None,
246            })
247            .collect();
248
249        IoApicDevice {
250            valid_lines: [0..=num_entries as u32 - 1],
251            routing,
252
253            id: 0,
254            irqs,
255            index: IndexRegister::ID,
256            stats: IoApicStats {
257                interrupts_per_irq: (0..num_entries).map(|_| Counter::new()).collect(),
258                interrupts: Counter::new(),
259            },
260        }
261    }
262
263    fn read_redirection_register(&self, index: u8) -> u32 {
264        let mut value = self
265            .irqs
266            .get(index as usize / 2)
267            .map_or(0, |irq| irq.redirection.into());
268        if index & 1 == 1 {
269            value >>= 32;
270        }
271        value as u32
272    }
273
274    fn write_redirection_register(&mut self, index: u8, val: u32) {
275        let n = index as usize / 2;
276        if let Some(irq) = self.irqs.get_mut(n) {
277            let mut val = val as u64;
278            let mut redirection = u64::from(irq.redirection);
279            if index & 1 == 1 {
280                redirection &= 0xffffffff;
281                val <<= 32;
282            } else {
283                redirection &= !0xffffffff;
284            }
285            redirection |= val & REDIRECTION_WRITE_MASK;
286            irq.redirection = redirection.into();
287
288            tracing::trace!(n, entry = ?irq.redirection, "new redirection entry");
289
290            let request = irq.redirection.as_msi();
291            if request != irq.registered_request {
292                self.routing.set_route(n as u8, request);
293                irq.registered_request = request;
294            }
295
296            // Reevaluate in case this unmasked a level-triggered interrupt.
297            irq.evaluate(self.routing.as_ref(), &mut self.stats, n as u8, false);
298        }
299    }
300
301    fn read_register(&self, index: IndexRegister) -> u32 {
302        match index {
303            IndexRegister::ID => IoApicId::new().with_id(self.id).into(),
304            IndexRegister::VERSION => IoApicVersion::new()
305                .with_version(IO_APIC_VERSION)
306                .with_max_entry((self.irqs.len() - 1) as u8)
307                .into(),
308            IndexRegister::ARBITRATION_ID => 0,
309            _ if self.index >= IndexRegister::REDIRECTION_TABLE_START => {
310                self.read_redirection_register(index.0 - IndexRegister::REDIRECTION_TABLE_START.0)
311            }
312            _ => {
313                tracelimit::warn_ratelimited!(?index, "unsupported register index read");
314                !0
315            }
316        }
317    }
318
319    fn write_register(&mut self, index: IndexRegister, val: u32) {
320        match index {
321            IndexRegister::ID => self.id = IoApicId::from(val).id(),
322            IndexRegister::VERSION | IndexRegister::ARBITRATION_ID => {
323                tracing::debug!(?index, val, "ignoring write to read-only register");
324            }
325            _ if self.index >= IndexRegister::REDIRECTION_TABLE_START => {
326                self.write_redirection_register(
327                    index.0 - IndexRegister::REDIRECTION_TABLE_START.0,
328                    val,
329                );
330            }
331            _ => {
332                tracelimit::warn_ratelimited!(?index, "unsupported register index write");
333            }
334        }
335    }
336}
337
338impl LineInterruptTarget for IoApicDevice {
339    fn set_irq(&mut self, n: u32, high: bool) {
340        if let Some(irq) = self.irqs.get_mut(n as usize) {
341            if high {
342                irq.assert(self.routing.as_ref(), &mut self.stats, n as u8);
343            } else {
344                irq.deassert();
345            }
346        }
347    }
348
349    fn valid_lines(&self) -> &[RangeInclusive<u32>] {
350        &self.valid_lines
351    }
352}
353
354impl HandleEoi for IoApicDevice {
355    /// reSearch query: `IoApicEmulator::NotifyEoi`
356    fn handle_eoi(&mut self, irq_to_end: u32) {
357        for (index, irq) in self.irqs.iter_mut().enumerate() {
358            irq.eoi(
359                irq_to_end,
360                self.routing.as_ref(),
361                &mut self.stats,
362                index as u8,
363            );
364        }
365    }
366}
367
368impl ChangeDeviceState for IoApicDevice {
369    fn start(&mut self) {}
370
371    async fn stop(&mut self) {}
372
373    async fn reset(&mut self) {
374        let Self {
375            valid_lines: _,
376            routing: _,
377            irqs,
378            id,
379            index,
380            stats: _,
381        } = self;
382        *id = 0;
383        *index = IndexRegister::ID;
384        for (n, irq) in irqs.iter_mut().enumerate() {
385            irq.redirection = RedirectionEntry::init();
386            self.routing.set_route(n as u8, None);
387            irq.registered_request = None;
388        }
389    }
390}
391
392impl ChipsetDevice for IoApicDevice {
393    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
394        Some(self)
395    }
396
397    fn supports_line_interrupt_target(&mut self) -> Option<&mut dyn LineInterruptTarget> {
398        Some(self)
399    }
400
401    fn supports_handle_eoi(&mut self) -> Option<&mut dyn HandleEoi> {
402        Some(self)
403    }
404}
405
406mod save_restore {
407    use super::IoApicDevice;
408    use super::spec::IndexRegister;
409    use super::spec::RedirectionEntry;
410    use thiserror::Error;
411    use vmcore::save_restore::RestoreError;
412    use vmcore::save_restore::SaveError;
413    use vmcore::save_restore::SaveRestore;
414
415    mod state {
416        use mesh::payload::Protobuf;
417        use vmcore::save_restore::SavedStateRoot;
418
419        #[derive(Clone, Debug, Default, Protobuf, SavedStateRoot)]
420        #[mesh(package = "chipset.ioapic")]
421        pub struct SavedState {
422            #[mesh(1)]
423            pub(super) id: u8,
424            #[mesh(2)]
425            pub(super) index: u8,
426            #[mesh(3)]
427            pub(super) redirection_entries: Vec<u64>,
428            // Preserve edge-detector history, not ownership of device outputs.
429            #[mesh(4)]
430            pub(super) line_levels: Vec<bool>,
431        }
432    }
433
434    #[derive(Error, Debug)]
435    #[error("wrong number of redirection entries")]
436    struct WrongNumberOfRedirectionEntries;
437
438    #[derive(Error, Debug)]
439    #[error("wrong number of line levels")]
440    struct WrongNumberOfLineLevels;
441
442    impl SaveRestore for IoApicDevice {
443        type SavedState = state::SavedState;
444
445        fn save(&mut self) -> Result<state::SavedState, SaveError> {
446            let Self {
447                valid_lines: _,
448                routing: _,
449                irqs,
450                id,
451                index,
452                stats: _,
453            } = &self;
454
455            Ok(state::SavedState {
456                redirection_entries: irqs.iter().map(|irq| irq.redirection.into()).collect(),
457                line_levels: irqs.iter().map(|irq| irq.line_level).collect(),
458                index: index.0,
459                id: *id,
460            })
461        }
462
463        fn restore(&mut self, state: state::SavedState) -> Result<(), RestoreError> {
464            let state::SavedState {
465                redirection_entries,
466                line_levels,
467                id,
468                index,
469            } = state;
470            if redirection_entries.len() != self.irqs.len() {
471                return Err(RestoreError::Other(WrongNumberOfRedirectionEntries.into()));
472            }
473            let line_levels = if line_levels.is_empty() {
474                vec![false; self.irqs.len()]
475            } else if line_levels.len() == self.irqs.len() {
476                line_levels
477            } else {
478                return Err(RestoreError::Other(WrongNumberOfLineLevels.into()));
479            };
480            for (n, ((state, line_level), irq)) in redirection_entries
481                .into_iter()
482                .zip(line_levels)
483                .zip(self.irqs.iter_mut())
484                .enumerate()
485            {
486                irq.redirection = RedirectionEntry::from(state);
487                irq.line_level = line_level;
488                let request = irq.redirection.as_msi();
489                self.routing.set_route(n as u8, request);
490                irq.registered_request = request;
491            }
492            self.id = id;
493            self.index = IndexRegister(index);
494            Ok(())
495        }
496    }
497
498    #[cfg(test)]
499    mod tests {
500        use super::*;
501        use chipset_device::interrupt::HandleEoi;
502        use chipset_device::interrupt::LineInterruptTarget;
503        use chipset_resources::ioapic::IoApicRouting;
504        use parking_lot::Mutex;
505        use std::sync::Arc;
506        use test_with_tracing::test;
507
508        const ENTRY_COUNT: u8 = 2;
509        const IRQ: u8 = 1;
510        const VECTOR: u8 = 0x30;
511
512        #[derive(Clone, Default)]
513        struct RecordingRouting {
514            assertions: Arc<Mutex<Vec<u8>>>,
515        }
516
517        impl IoApicRouting for RecordingRouting {
518            fn assert(&self, irq: u8) {
519                self.assertions.lock().push(irq);
520            }
521
522            fn set_route(&self, _irq: u8, _request: Option<(u64, u32)>) {}
523        }
524
525        fn device() -> (IoApicDevice, RecordingRouting) {
526            let routing = RecordingRouting::default();
527            (
528                IoApicDevice::new(ENTRY_COUNT, Box::new(routing.clone())),
529                routing,
530            )
531        }
532
533        fn set_entry(device: &mut IoApicDevice, level: bool, masked: bool) {
534            let entry = RedirectionEntry::new()
535                .with_vector(VECTOR)
536                .with_trigger_mode_level(level)
537                .with_masked(masked);
538            device.write_redirection_register(IRQ * 2, u64::from(entry) as u32);
539        }
540
541        #[test]
542        fn restore_preserves_edge_history() {
543            let (mut source, _) = device();
544            set_entry(&mut source, false, false);
545            source.set_irq(IRQ.into(), true);
546            let state = source.save().expect("save succeeds");
547
548            let (mut restored, routing) = device();
549            restored.restore(state).expect("restore succeeds");
550            restored.set_irq(IRQ.into(), true);
551            assert!(routing.assertions.lock().is_empty());
552
553            restored.set_irq(IRQ.into(), false);
554            restored.set_irq(IRQ.into(), true);
555            assert_eq!(*routing.assertions.lock(), [IRQ]);
556        }
557
558        #[test]
559        fn restore_allows_new_rising_edge() {
560            let (mut source, _) = device();
561            set_entry(&mut source, false, false);
562            let state = source.save().expect("save succeeds");
563
564            let (mut restored, routing) = device();
565            set_entry(&mut restored, false, false);
566            restored.set_irq(IRQ.into(), true);
567            routing.assertions.lock().clear();
568
569            restored.restore(state).expect("restore succeeds");
570            assert!(routing.assertions.lock().is_empty());
571            restored.set_irq(IRQ.into(), true);
572            assert_eq!(*routing.assertions.lock(), [IRQ]);
573        }
574
575        #[test]
576        fn restore_preserves_asserted_level_until_unmasked() {
577            let (mut source, _) = device();
578            set_entry(&mut source, true, true);
579            source.set_irq(IRQ.into(), true);
580            let state = source.save().expect("save succeeds");
581
582            let (mut restored, routing) = device();
583            restored.restore(state).expect("restore succeeds");
584            assert!(routing.assertions.lock().is_empty());
585            set_entry(&mut restored, true, false);
586
587            assert_eq!(*routing.assertions.lock(), [IRQ]);
588        }
589
590        #[test]
591        fn restore_preserves_remote_irr_until_eoi() {
592            let (mut source, _) = device();
593            set_entry(&mut source, true, false);
594            source.set_irq(IRQ.into(), true);
595            let state = source.save().expect("save succeeds");
596
597            let (mut restored, routing) = device();
598            restored.restore(state).expect("restore succeeds");
599            restored.set_irq(IRQ.into(), true);
600            assert!(routing.assertions.lock().is_empty());
601
602            restored.handle_eoi(VECTOR.into());
603            assert_eq!(*routing.assertions.lock(), [IRQ]);
604            restored.set_irq(IRQ.into(), false);
605            restored.handle_eoi(VECTOR.into());
606            assert_eq!(*routing.assertions.lock(), [IRQ]);
607        }
608
609        #[test]
610        fn restore_does_not_evaluate_level_interrupts() {
611            let (mut source, _) = device();
612            set_entry(&mut source, true, true);
613            source.set_irq(IRQ.into(), true);
614            let mut state = source.save().expect("save succeeds");
615            state.redirection_entries[IRQ as usize] =
616                RedirectionEntry::from(state.redirection_entries[IRQ as usize])
617                    .with_masked(false)
618                    .into();
619
620            let (mut restored, routing) = device();
621            restored.restore(state).expect("restore succeeds");
622            assert!(routing.assertions.lock().is_empty());
623
624            restored.set_irq(IRQ.into(), true);
625            assert_eq!(*routing.assertions.lock(), [IRQ]);
626        }
627
628        #[test]
629        fn restore_defaults_absent_line_levels_to_deasserted() {
630            let (mut source, _) = device();
631            set_entry(&mut source, true, true);
632            source.set_irq(IRQ.into(), true);
633            let mut state = source.save().expect("save succeeds");
634            state.line_levels.clear();
635
636            let (mut restored, routing) = device();
637            restored.restore(state).expect("restore succeeds");
638            set_entry(&mut restored, true, false);
639
640            assert!(routing.assertions.lock().is_empty());
641        }
642
643        #[test]
644        fn restore_rejects_wrong_number_of_line_levels() {
645            let (mut source, _) = device();
646            let mut state = source.save().expect("save succeeds");
647            state.line_levels.pop();
648
649            let (mut restored, routing) = device();
650            set_entry(&mut restored, false, false);
651            let before = restored.save().expect("save succeeds");
652            let error = restored.restore(state).expect_err("restore must fail");
653
654            assert_eq!(error.to_string(), "wrong number of line levels");
655            let after = restored.save().expect("save succeeds");
656            assert_eq!(after.redirection_entries, before.redirection_entries);
657            assert_eq!(after.line_levels, before.line_levels);
658            assert!(routing.assertions.lock().is_empty());
659        }
660    }
661}
662
663impl MmioIntercept for IoApicDevice {
664    fn mmio_read(&mut self, address: u64, data: &mut [u8]) -> IoResult {
665        assert_eq!(
666            address & !IOAPIC_DEVICE_MMIO_REGION_MASK,
667            IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS
668        );
669
670        let v = match Register(address & IOAPIC_DEVICE_MMIO_REGION_MASK) {
671            Register::INDEX => self.index.0.into(),
672            Register::DATA => self.read_register(self.index),
673            _ => return IoResult::Err(IoError::InvalidRegister),
674        };
675
676        // Allow any size read.
677        let n = data.len().min(size_of_val(&v));
678        data.copy_from_slice(&v.to_ne_bytes()[..n]);
679        IoResult::Ok
680    }
681
682    fn mmio_write(&mut self, address: u64, data: &[u8]) -> IoResult {
683        assert_eq!(
684            address & !IOAPIC_DEVICE_MMIO_REGION_MASK,
685            IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS
686        );
687
688        match Register(address & IOAPIC_DEVICE_MMIO_REGION_MASK) {
689            Register::INDEX => self.index = IndexRegister(data[0]),
690            Register::DATA => {
691                // Only allow 4-byte writes.
692                let Ok(data) = data.try_into() else {
693                    return IoResult::Err(IoError::InvalidAccessSize);
694                };
695                let data = u32::from_ne_bytes(data);
696                self.write_register(self.index, data);
697            }
698            _ => return IoResult::Err(IoError::InvalidRegister),
699        }
700
701        IoResult::Ok
702    }
703
704    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
705        &[(
706            "mmio",
707            IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS
708                ..=IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS + IOAPIC_DEVICE_MMIO_REGION_SIZE - 1,
709        )]
710    }
711}
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716
717    /// A redirection entry in the VT-d/AMD remappable interrupt format must be
718    /// emitted as a remappable-format MSI, carrying the interrupt-remap index
719    /// as the message handle. The remappable RTE deliberately overlays the
720    /// index onto the legacy destination fields, so the ordinary address
721    /// computation already produces the correct message with no special-casing.
722    #[test]
723    fn remappable_rte_emits_remappable_msi() {
724        // Remappable RTE: format bit (48) set, index[14:0] in bits 63:49 and
725        // index[15] in bit 11, vector in bits 7:0. Use an index with bit 15
726        // set to also exercise the bit-11 path.
727        let index: u16 = 0x9234;
728        let vector = 0x30u8;
729        let bits = u64::from(vector)
730            | (1 << 48)
731            | ((u64::from(index) & 0x7fff) << 49)
732            | (((u64::from(index) >> 15) & 1) << 11);
733        let rte = RedirectionEntry::from(bits);
734
735        let (address, data) = rte.as_msi().expect("unmasked entry yields an MSI");
736
737        // Remappable format (address bit 4), no subhandle (SHV, bit 3), and the
738        // index recoverable as the handle = {addr[2], addr[19:5]}.
739        assert_eq!((address >> 4) & 1, 1, "remappable format bit set");
740        assert_eq!((address >> 3) & 1, 0, "SHV clear (no subhandle)");
741        let handle = (((address >> 2) & 1) << 15) | ((address >> 5) & 0x7fff);
742        assert_eq!(handle as u16, index, "handle carries the IRTE index");
743        assert_eq!(data as u8, vector, "vector preserved for EOI matching");
744    }
745
746    /// A compatibility-format entry (format bit clear) is emitted as a
747    /// compatibility-format MSI (address bit 4 clear).
748    #[test]
749    fn compat_rte_emits_compat_msi() {
750        let rte = RedirectionEntry::new()
751            .with_vector(0x30)
752            .with_destination(0x04);
753        let (address, _data) = rte.as_msi().expect("unmasked entry yields an MSI");
754        assert_eq!(
755            (address >> 4) & 1,
756            0,
757            "compatibility format (not remappable)"
758        );
759    }
760
761    /// A masked entry produces no MSI regardless of format.
762    #[test]
763    fn masked_rte_emits_nothing() {
764        let rte = RedirectionEntry::new().with_masked(true).with_vector(0x30);
765        assert_eq!(rte.as_msi(), None);
766    }
767}