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