Skip to main content

pci_core/capabilities/
msix.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! MSI-X Capability.
5
6use super::PciCapability;
7use crate::msi::MsiRoute;
8use crate::msi::MsiTarget;
9use crate::spec::caps::CapabilityId;
10use crate::spec::caps::msix::MsixCapabilityHeader;
11use crate::spec::caps::msix::MsixTableEntryIdx;
12use chipset_device::pci::ByteEnabledDwordRead;
13use chipset_device::pci::ByteEnabledDwordWrite;
14use inspect::Inspect;
15use inspect::InspectMut;
16use pal_event::Event;
17use parking_lot::Mutex;
18use std::fmt::Debug;
19use std::sync::Arc;
20use vmcore::interrupt::Interrupt;
21use vmcore::interrupt::InterruptTarget;
22
23#[derive(Debug, Inspect)]
24struct MsiTableLocation {
25    #[inspect(hex)]
26    // MSI-X table offsets are, per spec, no larger than 32 bits.
27    offset: u32,
28    bar: u8,
29}
30
31impl MsiTableLocation {
32    fn new(bar: u8, offset: u32) -> Self {
33        assert!(bar < 6);
34        assert!(offset & 7 == 0);
35        Self { offset, bar }
36    }
37
38    fn read_u32(&self) -> u32 {
39        self.offset | self.bar as u32
40    }
41}
42
43#[derive(Inspect)]
44struct MsixCapability {
45    count: u16,
46    #[inspect(with = "|x| inspect::adhoc(|req| x.lock().inspect_mut(req))")]
47    state: Arc<Mutex<MsixState>>,
48    config_table_location: MsiTableLocation,
49    pending_bits_location: MsiTableLocation,
50}
51
52impl PciCapability for MsixCapability {
53    fn label(&self) -> &str {
54        "msi-x"
55    }
56
57    fn capability_id(&self) -> CapabilityId {
58        CapabilityId::MSIX
59    }
60
61    fn len(&self) -> usize {
62        12
63    }
64
65    fn read(&self, offset: u16, mut value: ByteEnabledDwordRead<'_>) {
66        match MsixCapabilityHeader(offset) {
67            MsixCapabilityHeader::CONTROL_CAPS => {
68                value.set_low_high(
69                    CapabilityId::MSIX.0.into(),
70                    (self.count - 1) | if self.state.lock().enabled { 0x8000 } else { 0 },
71                );
72            }
73            MsixCapabilityHeader::OFFSET_TABLE => value.set(self.config_table_location.read_u32()),
74            MsixCapabilityHeader::OFFSET_PBA => value.set(self.pending_bits_location.read_u32()),
75            _ => panic!("Unreachable read offset {}", offset),
76        }
77    }
78
79    fn write(&mut self, offset: u16, val: ByteEnabledDwordWrite) {
80        match MsixCapabilityHeader(offset) {
81            MsixCapabilityHeader::CONTROL_CAPS => {
82                const MSIX_ENABLE_BIT_MASK: u32 = 0x8000_0000;
83                if val.valid_mask() & MSIX_ENABLE_BIT_MASK != 0 {
84                    let mut state = self.state.lock();
85                    let was_enabled = state.enabled;
86                    let new_enabled = (val.extract() & MSIX_ENABLE_BIT_MASK) != 0;
87
88                    state.enabled = new_enabled;
89                    if was_enabled && !new_enabled {
90                        for entry in &mut state.vectors {
91                            if entry.is_enabled(true) {
92                                entry.msi.disable();
93                            }
94                        }
95                    } else if new_enabled && !was_enabled {
96                        for entry in &mut state.vectors {
97                            if entry.is_enabled(true) {
98                                entry.msi.enable(
99                                    entry.state.address,
100                                    entry.state.data,
101                                    entry.state.is_pending,
102                                );
103                                entry.state.is_pending = false;
104                            }
105                        }
106                    }
107                }
108            }
109            MsixCapabilityHeader::OFFSET_TABLE | MsixCapabilityHeader::OFFSET_PBA => {
110                tracelimit::warn_ratelimited!(
111                    "Unexpected write offset {:?}",
112                    MsixCapabilityHeader(offset)
113                )
114            }
115            _ => panic!("Unreachable write offset {}", offset),
116        }
117    }
118
119    fn reset(&mut self) {
120        let mut state = self.state.lock();
121        state.enabled = false;
122        for vector in &mut state.vectors {
123            vector.msi.disable();
124            vector.state = EntryState::new();
125        }
126    }
127}
128
129#[derive(Clone, Inspect, Debug)]
130pub(crate) struct MsiInterrupt(#[inspect(flatten)] Arc<Mutex<MsiInterruptInner>>);
131
132#[derive(Inspect)]
133struct MsiInterruptInner {
134    #[inspect(skip)]
135    target: MsiTarget,
136    /// Optional kernel-mediated route for direct interrupt delivery.
137    /// When present, `enable()`/`disable()` automatically program the
138    /// kernel's MSI routing alongside the userspace `MsiTarget` path.
139    #[inspect(skip)]
140    route: Option<MsiRoute>,
141    pending: bool,
142    enabled: bool,
143    address: u64,
144    data: u32,
145}
146
147impl Debug for MsiInterruptInner {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        f.debug_struct("MsiInterruptInner")
150            .field("pending", &self.pending)
151            .field("enabled", &self.enabled)
152            .field("address", &self.address)
153            .field("data", &self.data)
154            .field("has_route", &self.route.is_some())
155            .finish()
156    }
157}
158
159impl MsiInterruptInner {
160    fn signal_msi(&self) {
161        self.target.signal_msi(self.address, self.data);
162    }
163
164    fn enable_route(&self, route: &MsiRoute) {
165        route.enable(self.address, self.data);
166    }
167}
168
169impl MsiInterrupt {
170    pub fn new(target: MsiTarget) -> Self {
171        Self(Arc::new(Mutex::new(MsiInterruptInner {
172            target,
173            route: None,
174            pending: false,
175            enabled: false,
176            address: 0,
177            data: 0,
178        })))
179    }
180
181    pub fn enable(&self, address: u64, data: u32, set_pending: bool) {
182        let mut state = self.0.lock();
183        state.pending |= set_pending;
184        state.address = address;
185        state.data = data;
186        state.enabled = true;
187
188        // Program the kernel route if present.
189        if let Some(route) = &state.route {
190            state.enable_route(route);
191        }
192
193        if state.pending {
194            state.signal_msi();
195            state.pending = false;
196        }
197    }
198
199    pub fn disable(&self) {
200        let mut state = self.0.lock();
201        state.enabled = false;
202        if let Some(route) = &state.route {
203            route.disable();
204        }
205    }
206
207    pub fn drain_pending(&self) -> bool {
208        let mut state = self.0.lock();
209        if let Some(route) = &state.route {
210            state.pending |= route.consume_pending();
211        }
212        let was_pending = state.pending;
213        state.pending = false;
214        was_pending
215    }
216
217    pub fn interrupt(&self) -> Interrupt {
218        Interrupt::from_target(MsiInterruptTarget(self.0.clone()))
219    }
220}
221
222/// [`InterruptTarget`] for MSI-X vectors that lazily allocates an irqfd
223/// route when [`event()`](InterruptTarget::event) is first called.
224struct MsiInterruptTarget(Arc<Mutex<MsiInterruptInner>>);
225
226impl InterruptTarget for MsiInterruptTarget {
227    fn deliver(&self) {
228        let mut state = self.0.lock();
229        if state.enabled {
230            state.signal_msi();
231        } else {
232            state.pending = true;
233        }
234    }
235
236    fn event(&self) -> Option<Arc<Event>> {
237        let mut state = self.0.lock();
238        if let Some(route) = &state.route {
239            return Some(Arc::new(route.event().clone()));
240        }
241        let route = match state.target.new_route() {
242            Some(Ok(route)) => route,
243            Some(Err(e)) => {
244                tracelimit::warn_ratelimited!(error = ?e, "failed to allocate MSI route");
245                return None;
246            }
247            None => return None,
248        };
249        if state.enabled {
250            state.enable_route(&route);
251        } else {
252            route.disable();
253        }
254        let event = Arc::new(route.event().clone());
255        state.route = Some(route);
256        Some(event)
257    }
258}
259
260struct MsixMessageTableEntry {
261    msi: MsiInterrupt,
262    state: EntryState,
263}
264
265impl InspectMut for MsixMessageTableEntry {
266    fn inspect_mut(&mut self, req: inspect::Request<'_>) {
267        req.respond()
268            .hex("address", self.state.address)
269            .hex("data", self.state.data)
270            .hex("control", self.state.control)
271            .field("enabled", self.state.control & 1 == 0)
272            .field("is_pending", self.check_is_pending(true));
273    }
274}
275
276#[derive(Debug)]
277struct EntryState {
278    address: u64,
279    data: u32,
280    control: u32,
281    is_pending: bool,
282}
283
284impl EntryState {
285    fn new() -> Self {
286        Self {
287            address: 0,
288            data: 0,
289            control: 1,
290            is_pending: false,
291        }
292    }
293}
294
295impl MsixMessageTableEntry {
296    fn new(msi: MsiInterrupt) -> Self {
297        Self {
298            msi,
299            state: EntryState::new(),
300        }
301    }
302
303    fn read_u32(&self, offset: u64) -> u32 {
304        match MsixTableEntryIdx(offset) {
305            MsixTableEntryIdx::MSG_ADDR_LO => self.state.address as u32,
306            MsixTableEntryIdx::MSG_ADDR_HI => (self.state.address >> 32) as u32,
307            MsixTableEntryIdx::MSG_DATA => self.state.data,
308            MsixTableEntryIdx::VECTOR_CTL => self.state.control,
309            _ => panic!("Unexpected read offset {}", offset),
310        }
311    }
312
313    fn write_u32(&mut self, offset: u64, val: u32) {
314        match MsixTableEntryIdx(offset) {
315            MsixTableEntryIdx::MSG_ADDR_LO => {
316                self.state.address = (self.state.address & 0xffffffff00000000) | val as u64
317            }
318            MsixTableEntryIdx::MSG_ADDR_HI => {
319                self.state.address = (val as u64) << 32 | self.state.address & 0xffffffff
320            }
321            MsixTableEntryIdx::MSG_DATA => self.state.data = val,
322            MsixTableEntryIdx::VECTOR_CTL => self.state.control = val,
323            _ => panic!("Unexpected write offset {}", offset),
324        }
325    }
326
327    fn is_enabled(&self, global_enabled: bool) -> bool {
328        global_enabled && self.state.control & 1 == 0
329    }
330
331    fn check_is_pending(&mut self, global_enabled: bool) -> bool {
332        if !self.state.is_pending && !self.is_enabled(global_enabled) {
333            self.state.is_pending = self.msi.drain_pending();
334        }
335        self.state.is_pending
336    }
337}
338
339#[derive(InspectMut)]
340struct MsixState {
341    enabled: bool,
342    #[inspect(mut, with = "inspect_entries")]
343    vectors: Vec<MsixMessageTableEntry>,
344}
345
346fn inspect_entries(entries: &mut [MsixMessageTableEntry]) -> impl '_ + InspectMut {
347    inspect::adhoc_mut(|req| {
348        let mut resp = req.respond();
349        for (i, entry) in entries.iter_mut().enumerate() {
350            resp.field_mut(&i.to_string(), entry);
351        }
352    })
353}
354
355/// Emulator for the hardware-level interface required to configure and trigger
356/// MSI-X interrupts on a PCI device.
357#[derive(Clone)]
358pub struct MsixEmulator {
359    state: Arc<Mutex<MsixState>>,
360    // PBA offsets, per spec, are no larger than 32 bits.
361    pending_bits_offset: u32,
362    pending_bits_dword_count: u16,
363}
364
365impl MsixEmulator {
366    /// Create a new [`MsixEmulator`] instance, along with with its associated
367    /// [`PciCapability`] structure.
368    ///
369    /// This implementation of MSI-X expects a dedicated BAR to store the vector
370    /// and pending tables.
371    ///
372    /// * * *
373    ///
374    /// DEVNOTE: This current implementation of MSI-X isn't particularly
375    /// "flexible" with respect to the various ways the PCI spec allows MSI-X to
376    /// be implemented. e.g: it uses a shared BAR for the table and BPA, with
377    /// fixed offsets into the BAR for both of those tables. It would be nice to
378    /// re-visit this code and make it more flexible.
379    pub fn new(bar: u8, count: u16, msi_target: &MsiTarget) -> (Self, impl PciCapability + use<>) {
380        let state = MsixState {
381            enabled: false,
382            vectors: (0..count)
383                .map(|_| MsixMessageTableEntry::new(MsiInterrupt::new(msi_target.clone())))
384                .collect(),
385        };
386        let state = Arc::new(Mutex::new(state));
387        let pending_bits_offset = count as u32 * 16;
388        (
389            Self {
390                state: state.clone(),
391                pending_bits_offset,
392                pending_bits_dword_count: count.div_ceil(32),
393            },
394            MsixCapability {
395                count,
396                state,
397                config_table_location: MsiTableLocation::new(bar, 0),
398                pending_bits_location: MsiTableLocation::new(bar, pending_bits_offset),
399            },
400        )
401    }
402
403    /// Return the total length of the MSI-X BAR
404    /// (Actually, the notion that there is an "MSI-X BAR" is an issue to fix sometime.
405    /// MSI-X tables are often in the same bar as other things.)
406    pub fn bar_len(&self) -> u64 {
407        self.pending_bits_offset as u64 + self.pending_bits_dword_count as u64 * 4
408    }
409
410    /// Read a `u32` from the MSI-X BAR at the given offset.
411    pub fn read_u32(&self, offset: u64) -> u32 {
412        let mut state = self.state.lock();
413        let state: &mut MsixState = &mut state;
414        if offset < self.pending_bits_offset as u64 {
415            let index = offset / 16;
416            if let Some(entry) = state.vectors.get(index as usize) {
417                return entry.read_u32(offset & 0xf);
418            }
419        } else {
420            let dword = (offset - self.pending_bits_offset as u64) / 4;
421            let start = dword as usize * 32;
422            if start < state.vectors.len() {
423                let end = (start + 32).min(state.vectors.len());
424                let mut val = 0u32;
425                for (i, entry) in state.vectors[start..end].iter_mut().enumerate() {
426                    if entry.check_is_pending(state.enabled) {
427                        val |= 1 << i;
428                    }
429                }
430                return val;
431            }
432        }
433        tracelimit::warn_ratelimited!(offset, "Unexpected read offset");
434        0
435    }
436
437    /// Write a `u32` to the MSI-X BAR at the given offset.
438    pub fn write_u32(&mut self, offset: u64, val: u32) {
439        let mut state = self.state.lock();
440        if offset < self.pending_bits_offset as u64 {
441            let index = offset / 16;
442            let global = state.enabled;
443            if let Some(entry) = state.vectors.get_mut(index as usize) {
444                let was_enabled = entry.is_enabled(global);
445                entry.write_u32(offset & 0xf, val);
446                let is_enabled = entry.is_enabled(global);
447                if is_enabled && !was_enabled {
448                    // Vector just unmasked.
449                    entry.msi.enable(
450                        entry.state.address,
451                        entry.state.data,
452                        entry.state.is_pending,
453                    );
454                    entry.state.is_pending = false;
455                } else if was_enabled && !is_enabled {
456                    // Vector just masked.
457                    entry.msi.disable();
458                } else if is_enabled {
459                    // Still enabled. addr/data may have changed — enable()
460                    // will update the route if present.
461                    entry
462                        .msi
463                        .enable(entry.state.address, entry.state.data, false);
464                }
465                return;
466            }
467        } else if offset - (self.pending_bits_offset as u64)
468            < self.pending_bits_dword_count as u64 * 4
469        {
470            return;
471        }
472        tracelimit::warn_ratelimited!(offset, "Unexpected write offset");
473    }
474
475    /// Return an [`Interrupt`] associated with the particular MSI-X vector, or
476    /// `None` if the index is out of bounds.
477    pub fn interrupt(&self, index: u16) -> Option<Interrupt> {
478        Some(
479            self.state
480                .lock()
481                .vectors
482                .get_mut(index as usize)?
483                .msi
484                .interrupt(),
485        )
486    }
487
488    #[cfg(test)]
489    fn clear_pending_bit(&self, index: u8) {
490        let mut state = self.state.lock();
491        state.vectors[index as usize].state.is_pending = false;
492    }
493
494    /// Sets the pending bit for the given vector index.
495    ///
496    /// Used by device passthrough (e.g., VFIO with irqfd) to record that an
497    /// interrupt arrived while the vector was masked, so PBA reads return
498    /// the correct pending state.
499    pub fn set_pending_bit(&self, index: u16) {
500        let mut state = self.state.lock();
501        if let Some(entry) = state.vectors.get_mut(index as usize) {
502            entry.state.is_pending = true;
503        } else {
504            tracelimit::warn_ratelimited!(
505                index,
506                count = state.vectors.len(),
507                "set_pending_bit: vector index out of range"
508            );
509        }
510    }
511}
512
513mod save_restore {
514    use super::*;
515    use thiserror::Error;
516    use vmcore::save_restore::RestoreError;
517    use vmcore::save_restore::SaveError;
518    use vmcore::save_restore::SaveRestore;
519
520    mod state {
521        use mesh::payload::Protobuf;
522        use vmcore::save_restore::SavedStateRoot;
523
524        #[derive(Debug, Protobuf)]
525        #[mesh(package = "pci.caps.msix")]
526        pub struct SavedMsixMessageTableEntryState {
527            #[mesh(1)]
528            pub address: u64,
529            #[mesh(2)]
530            pub data: u32,
531            #[mesh(3)]
532            pub control: u32,
533            #[mesh(4)]
534            pub is_pending: bool,
535        }
536
537        #[derive(Debug, Protobuf, SavedStateRoot)]
538        #[mesh(package = "pci.caps.msix")]
539        pub struct SavedState {
540            #[mesh(2)]
541            pub enabled: bool,
542            #[mesh(3)]
543            pub vectors: Vec<SavedMsixMessageTableEntryState>,
544        }
545    }
546
547    #[derive(Debug, Error)]
548    enum MsixRestoreError {
549        #[error("mismatched vector lengths: current:{0}, saved:{1}")]
550        MismatchedTableLengths(usize, usize),
551    }
552
553    impl SaveRestore for MsixCapability {
554        type SavedState = state::SavedState;
555
556        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
557            let state = self.state.lock();
558            let saved_state = state::SavedState {
559                enabled: state.enabled,
560                vectors: {
561                    state
562                        .vectors
563                        .iter()
564                        .map(|vec| {
565                            let EntryState {
566                                address,
567                                data,
568                                control,
569                                is_pending,
570                            } = vec.state;
571
572                            state::SavedMsixMessageTableEntryState {
573                                address,
574                                data,
575                                control,
576                                is_pending,
577                            }
578                        })
579                        .collect()
580                },
581            };
582            Ok(saved_state)
583        }
584
585        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
586            let state::SavedState { enabled, vectors } = state;
587
588            let mut state = self.state.lock();
589            state.enabled = enabled;
590
591            if vectors.len() != state.vectors.len() {
592                return Err(RestoreError::InvalidSavedState(
593                    MsixRestoreError::MismatchedTableLengths(vectors.len(), state.vectors.len())
594                        .into(),
595                ));
596            }
597
598            for (new_vec, vec) in vectors.into_iter().zip(state.vectors.iter_mut()) {
599                vec.state = EntryState {
600                    address: new_vec.address,
601                    data: new_vec.data,
602                    control: new_vec.control,
603                    is_pending: new_vec.is_pending,
604                }
605            }
606
607            Ok(())
608        }
609    }
610}
611
612#[cfg(test)]
613mod tests {
614    use super::*;
615    use crate::msi::MsiConnection;
616    use crate::test_helpers::TestPciInterruptController;
617    use crate::test_helpers::read_cap_u32;
618    use crate::test_helpers::write_cap_u32;
619
620    #[test]
621    fn msix_check() {
622        let msi_conn = MsiConnection::new();
623        let (mut msix, mut cap) = MsixEmulator::new(2, 64, &msi_conn.target());
624        let msi_controller = TestPciInterruptController::new();
625        msi_conn.connect(msi_controller.signal_msi());
626        // check capabilities
627        assert_eq!(read_cap_u32(&cap, 0), 0x3f0011);
628        assert_eq!(read_cap_u32(&cap, 4), 2);
629        assert_eq!(read_cap_u32(&cap, 8), 0x402);
630        write_cap_u32(&mut cap, 0, 0xffffffff);
631        assert_eq!(read_cap_u32(&cap, 0), 0x803f0011);
632        // check BAR
633        // Vector[0]
634        assert_eq!(msix.read_u32(0), 0);
635        assert_eq!(msix.read_u32(4), 0);
636        assert_eq!(msix.read_u32(8), 0);
637        assert_eq!(msix.read_u32(12), 1);
638        msix.write_u32(0, 0x12345678);
639        msix.write_u32(4, 0x9abcdef0);
640        msix.write_u32(8, 0x123);
641        msix.write_u32(12, 0x456);
642        assert_eq!(msix.read_u32(0), 0x12345678);
643        assert_eq!(msix.read_u32(4), 0x9abcdef0);
644        assert_eq!(msix.read_u32(8), 0x123);
645        assert_eq!(msix.read_u32(12), 0x456);
646        // Vector[63]
647        assert_eq!(msix.read_u32(0x3f0), 0);
648        assert_eq!(msix.read_u32(0x3f4), 0);
649        assert_eq!(msix.read_u32(0x3f8), 0);
650        assert_eq!(msix.read_u32(0x3fc), 1);
651        msix.write_u32(0x3f0, 0x12345678);
652        msix.write_u32(0x3f4, 0x9abcdef0);
653        msix.write_u32(0x3f8, 0x123);
654        msix.write_u32(0x3fc, 0x456);
655        assert_eq!(msix.read_u32(0x3f0), 0x12345678);
656        assert_eq!(msix.read_u32(0x3f4), 0x9abcdef0);
657        assert_eq!(msix.read_u32(0x3f8), 0x123);
658        assert_eq!(msix.read_u32(0x3fc), 0x456);
659        // Pending Bit Array
660        assert_eq!(msix.read_u32(0x400), 0);
661        assert_eq!(msix.read_u32(0x404), 0);
662        msix.set_pending_bit(1);
663        assert_eq!(msix.read_u32(0x400), 2);
664        assert_eq!(msix.read_u32(0x404), 0);
665        msix.set_pending_bit(33);
666        assert_eq!(msix.read_u32(0x400), 2);
667        assert_eq!(msix.read_u32(0x404), 2);
668        msix.set_pending_bit(63);
669        msix.set_pending_bit(31);
670        assert_eq!(msix.read_u32(0x400), 0x80000002);
671        assert_eq!(msix.read_u32(0x404), 0x80000002);
672        msix.clear_pending_bit(1);
673        assert_eq!(msix.read_u32(0x400), 0x80000000);
674        assert_eq!(msix.read_u32(0x404), 0x80000002);
675    }
676
677    use pal_event::Event;
678    use parking_lot::Mutex;
679
680    /// Record of a call made to a mock route.
681    #[derive(Debug, Clone, PartialEq)]
682    enum RouteCall {
683        SetMsi { address: u64, data: u32 },
684        ClearMsi,
685    }
686
687    /// Mock IrqFdRoute implementation that records calls.
688    struct MockIrqFdRoute {
689        event: Event,
690        calls: Arc<Mutex<Vec<RouteCall>>>,
691    }
692
693    impl vmcore::irqfd::IrqFdRoute for MockIrqFdRoute {
694        fn event(&self) -> &Event {
695            &self.event
696        }
697
698        fn enable(&self, address: u64, data: u32, _devid: Option<u32>) {
699            self.calls.lock().push(RouteCall::SetMsi { address, data });
700        }
701
702        fn disable(&self) {
703            self.calls.lock().push(RouteCall::ClearMsi);
704        }
705    }
706
707    /// Build a mock IrqFd and shared call logs.
708    fn mock_irqfd(
709        count: usize,
710    ) -> (
711        Arc<dyn vmcore::irqfd::IrqFd>,
712        Vec<Arc<Mutex<Vec<RouteCall>>>>,
713    ) {
714        let mut call_logs = Vec::new();
715        let route_params = Arc::new(Mutex::new(Vec::new()));
716        for _ in 0..count {
717            let calls = Arc::new(Mutex::new(Vec::new()));
718            call_logs.push(calls.clone());
719            route_params.lock().push(calls);
720        }
721
722        struct MockIrqFd {
723            routes: Mutex<Vec<Arc<Mutex<Vec<RouteCall>>>>>,
724        }
725        impl vmcore::irqfd::IrqFd for MockIrqFd {
726            fn new_irqfd_route(&self) -> anyhow::Result<Box<dyn vmcore::irqfd::IrqFdRoute>> {
727                let calls = self.routes.lock().remove(0);
728                Ok(Box::new(MockIrqFdRoute {
729                    event: Event::new(),
730                    calls,
731                }))
732            }
733        }
734
735        (
736            Arc::new(MockIrqFd {
737                routes: Mutex::new(call_logs.clone()),
738            }),
739            call_logs,
740        )
741    }
742
743    #[test]
744    fn route_set_msi_on_unmask() {
745        let (irqfd, calls) = mock_irqfd(2);
746        let msi_conn = MsiConnection::new();
747        msi_conn.connect_irqfd(irqfd);
748        let (mut msix, mut cap) = MsixEmulator::new(2, 2, &msi_conn.target());
749        let msi_controller = TestPciInterruptController::new();
750        msi_conn.connect(msi_controller.signal_msi());
751
752        // Trigger lazy irqfd route creation for all vectors.
753        for i in 0..2 {
754            msix.interrupt(i).unwrap().event();
755        }
756
757        // Enable MSI-X globally.
758        write_cap_u32(&mut cap, 0, 0x80000000);
759
760        // Program vector 0 addr/data (still masked — control starts at 1).
761        msix.write_u32(0, 0xFEE00000); // addr_lo
762        msix.write_u32(4, 0); // addr_hi
763        msix.write_u32(8, 0x42); // data
764
765        // No set_msi yet because vector is still masked.
766        assert!(
767            !calls[0]
768                .lock()
769                .iter()
770                .any(|c| matches!(c, RouteCall::SetMsi { .. }))
771        );
772
773        // Unmask vector 0 (write control = 0).
774        calls[0].lock().clear();
775        msix.write_u32(12, 0);
776
777        // Should have called set_msi to re-establish the route.
778        let log = calls[0].lock().clone();
779        assert!(log.contains(&RouteCall::SetMsi {
780            address: 0xFEE00000,
781            data: 0x42
782        }));
783    }
784
785    #[test]
786    fn route_mask_on_vector_mask() {
787        let (irqfd, calls) = mock_irqfd(2);
788        let msi_conn = MsiConnection::new();
789        msi_conn.connect_irqfd(irqfd);
790        let (mut msix, mut cap) = MsixEmulator::new(2, 2, &msi_conn.target());
791        let msi_controller = TestPciInterruptController::new();
792        msi_conn.connect(msi_controller.signal_msi());
793
794        // Trigger lazy irqfd route creation for all vectors.
795        for i in 0..2 {
796            msix.interrupt(i).unwrap().event();
797        }
798
799        // Enable MSI-X, program and unmask vector 0.
800        write_cap_u32(&mut cap, 0, 0x80000000);
801        msix.write_u32(0, 0xFEE00000);
802        msix.write_u32(8, 0x42);
803        msix.write_u32(12, 0); // unmask
804
805        calls[0].lock().clear();
806
807        // Mask vector 0 (write control = 1).
808        msix.write_u32(12, 1);
809
810        let log = calls[0].lock().clone();
811        assert!(log.contains(&RouteCall::ClearMsi));
812    }
813
814    #[test]
815    fn route_global_disable_masks_all() {
816        let (irqfd, calls) = mock_irqfd(2);
817        let msi_conn = MsiConnection::new();
818        msi_conn.connect_irqfd(irqfd);
819        let (mut msix, mut cap) = MsixEmulator::new(2, 2, &msi_conn.target());
820        let msi_controller = TestPciInterruptController::new();
821        msi_conn.connect(msi_controller.signal_msi());
822
823        // Trigger lazy irqfd route creation for all vectors.
824        for i in 0..2 {
825            msix.interrupt(i).unwrap().event();
826        }
827
828        // Enable, program, and unmask both vectors.
829        write_cap_u32(&mut cap, 0, 0x80000000);
830        for v in 0..2u64 {
831            msix.write_u32(v * 16, 0xFEE00000);
832            msix.write_u32(v * 16 + 8, (v + 1) as u32);
833            msix.write_u32(v * 16 + 12, 0); // unmask
834        }
835        calls[0].lock().clear();
836        calls[1].lock().clear();
837
838        // Disable MSI-X globally.
839        write_cap_u32(&mut cap, 0, 0);
840
841        // Both vectors should have been disabled.
842        assert!(calls[0].lock().contains(&RouteCall::ClearMsi));
843        assert!(calls[1].lock().contains(&RouteCall::ClearMsi));
844    }
845
846    #[test]
847    fn route_consume_pending_on_pba_read() {
848        let (irqfd, _calls) = mock_irqfd(2);
849        let msi_conn = MsiConnection::new();
850        msi_conn.connect_irqfd(irqfd);
851        let (msix, mut cap) = MsixEmulator::new(2, 2, &msi_conn.target());
852        let msi_controller = TestPciInterruptController::new();
853        msi_conn.connect(msi_controller.signal_msi());
854
855        // Trigger lazy irqfd route creation for all vectors.
856        let events: Vec<_> = (0..2)
857            .map(|i| msix.interrupt(i).unwrap().event().unwrap().clone())
858            .collect();
859
860        // Enable MSI-X but leave vectors masked (control = 1 by default).
861        write_cap_u32(&mut cap, 0, 0x80000000);
862
863        // Simulate a pending interrupt on vector 0 by signaling the event.
864        events[0].signal();
865
866        // PBA is at offset = vector_count * 16 = 32.
867        let pba = msix.read_u32(32);
868
869        // consume_pending drains the event; PBA bit 0 should be set.
870        assert_eq!(pba & 1, 1);
871    }
872
873    #[test]
874    fn route_set_msi_on_addr_data_change_while_unmasked() {
875        let (irqfd, calls) = mock_irqfd(1);
876        let msi_conn = MsiConnection::new();
877        msi_conn.connect_irqfd(irqfd);
878        let (mut msix, mut cap) = MsixEmulator::new(2, 1, &msi_conn.target());
879        let msi_controller = TestPciInterruptController::new();
880        msi_conn.connect(msi_controller.signal_msi());
881
882        // Trigger lazy irqfd route creation.
883        msix.interrupt(0).unwrap().event();
884
885        // Enable, program, unmask.
886        write_cap_u32(&mut cap, 0, 0x80000000);
887        msix.write_u32(0, 0xFEE00000);
888        msix.write_u32(8, 0x42);
889        msix.write_u32(12, 0);
890        calls[0].lock().clear();
891
892        // Change data while still unmasked.
893        msix.write_u32(8, 0x99);
894
895        let log = calls[0].lock().clone();
896        assert!(log.contains(&RouteCall::SetMsi {
897            address: 0xFEE00000,
898            data: 0x99
899        }));
900    }
901}