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::MsiInterrupt;
8use crate::msi::RegisterMsi;
9use crate::spec::caps::CapabilityId;
10use crate::spec::caps::msix::MsixCapabilityHeader;
11use crate::spec::caps::msix::MsixTableEntryIdx;
12use inspect::Inspect;
13use inspect::InspectMut;
14use parking_lot::Mutex;
15use std::fmt::Debug;
16use std::sync::Arc;
17use vmcore::interrupt::Interrupt;
18
19#[derive(Debug, Inspect)]
20struct MsiTableLocation {
21    #[inspect(hex)]
22    offset: u32,
23    bar: u8,
24}
25
26impl MsiTableLocation {
27    fn new(bar: u8, offset: u32) -> Self {
28        assert!(bar < 6);
29        assert!(offset & 7 == 0);
30        Self { offset, bar }
31    }
32
33    fn read_u32(&self) -> u32 {
34        self.offset | self.bar as u32
35    }
36}
37
38#[derive(Inspect)]
39struct MsixCapability {
40    count: u16,
41    #[inspect(with = "|x| inspect::adhoc(|req| x.lock().inspect_mut(req))")]
42    state: Arc<Mutex<MsixState>>,
43    config_table_location: MsiTableLocation,
44    pending_bits_location: MsiTableLocation,
45}
46
47impl PciCapability for MsixCapability {
48    fn label(&self) -> &str {
49        "msi-x"
50    }
51
52    fn len(&self) -> usize {
53        12
54    }
55
56    fn read_u32(&self, offset: u16) -> u32 {
57        match MsixCapabilityHeader(offset) {
58            MsixCapabilityHeader::CONTROL_CAPS => {
59                CapabilityId::MSIX.0 as u32
60                    | ((self.count as u32 - 1) | if self.state.lock().enabled { 0x8000 } else { 0 })
61                        << 16
62            }
63            MsixCapabilityHeader::OFFSET_TABLE => self.config_table_location.read_u32(),
64            MsixCapabilityHeader::OFFSET_PBA => self.pending_bits_location.read_u32(),
65            _ => panic!("Unreachable read offset {}", offset),
66        }
67    }
68
69    fn write_u32(&mut self, offset: u16, val: u32) {
70        match MsixCapabilityHeader(offset) {
71            MsixCapabilityHeader::CONTROL_CAPS => {
72                let enabled = val & 0x80000000 != 0;
73                let mut state = self.state.lock();
74                let was_enabled = state.enabled;
75                state.enabled = enabled;
76                if was_enabled && !enabled {
77                    for entry in &mut state.vectors {
78                        if entry.is_enabled(true) {
79                            entry.msi.disable();
80                        }
81                    }
82                } else if enabled && !was_enabled {
83                    for entry in &mut state.vectors {
84                        if entry.is_enabled(true) {
85                            entry.msi.enable(
86                                entry.state.address,
87                                entry.state.data,
88                                entry.state.is_pending,
89                            );
90                            entry.state.is_pending = false;
91                        }
92                    }
93                }
94            }
95            MsixCapabilityHeader::OFFSET_TABLE | MsixCapabilityHeader::OFFSET_PBA => {
96                tracelimit::warn_ratelimited!(
97                    "Unexpected write offset {:?}",
98                    MsixCapabilityHeader(offset)
99                )
100            }
101            _ => panic!("Unreachable write offset {}", offset),
102        }
103    }
104
105    fn reset(&mut self) {
106        let mut state = self.state.lock();
107        state.enabled = false;
108        for vector in &mut state.vectors {
109            vector.state = EntryState::new();
110        }
111    }
112}
113
114struct MsixMessageTableEntry {
115    msi: MsiInterrupt,
116    state: EntryState,
117}
118
119impl InspectMut for MsixMessageTableEntry {
120    fn inspect_mut(&mut self, req: inspect::Request<'_>) {
121        req.respond()
122            .hex("address", self.state.address)
123            .hex("data", self.state.data)
124            .hex("control", self.state.control)
125            .field("enabled", self.state.control & 1 == 0)
126            .field("is_pending", self.check_is_pending(true));
127    }
128}
129
130#[derive(Debug)]
131struct EntryState {
132    address: u64,
133    data: u32,
134    control: u32,
135    is_pending: bool,
136}
137
138impl EntryState {
139    fn new() -> Self {
140        Self {
141            address: 0,
142            data: 0,
143            control: 1,
144            is_pending: false,
145        }
146    }
147}
148
149impl MsixMessageTableEntry {
150    fn new(msi: MsiInterrupt) -> Self {
151        Self {
152            msi,
153            state: EntryState::new(),
154        }
155    }
156
157    fn read_u32(&self, offset: u16) -> u32 {
158        match MsixTableEntryIdx(offset) {
159            MsixTableEntryIdx::MSG_ADDR_LO => self.state.address as u32,
160            MsixTableEntryIdx::MSG_ADDR_HI => (self.state.address >> 32) as u32,
161            MsixTableEntryIdx::MSG_DATA => self.state.data,
162            MsixTableEntryIdx::VECTOR_CTL => self.state.control,
163            _ => panic!("Unexpected read offset {}", offset),
164        }
165    }
166
167    fn write_u32(&mut self, offset: u16, val: u32) {
168        match MsixTableEntryIdx(offset) {
169            MsixTableEntryIdx::MSG_ADDR_LO => {
170                self.state.address = (self.state.address & 0xffffffff00000000) | val as u64
171            }
172            MsixTableEntryIdx::MSG_ADDR_HI => {
173                self.state.address = (val as u64) << 32 | self.state.address & 0xffffffff
174            }
175            MsixTableEntryIdx::MSG_DATA => self.state.data = val,
176            MsixTableEntryIdx::VECTOR_CTL => self.state.control = val,
177            _ => panic!("Unexpected write offset {}", offset),
178        }
179    }
180
181    fn is_enabled(&self, global_enabled: bool) -> bool {
182        global_enabled && self.state.control & 1 == 0
183    }
184
185    fn check_is_pending(&mut self, global_enabled: bool) -> bool {
186        if !self.state.is_pending && !self.is_enabled(global_enabled) {
187            self.state.is_pending = self.msi.drain_pending();
188        }
189        self.state.is_pending
190    }
191}
192
193#[derive(InspectMut)]
194struct MsixState {
195    enabled: bool,
196    #[inspect(mut, with = "inspect_entries")]
197    vectors: Vec<MsixMessageTableEntry>,
198}
199
200fn inspect_entries(entries: &mut [MsixMessageTableEntry]) -> impl '_ + InspectMut {
201    inspect::adhoc_mut(|req| {
202        let mut resp = req.respond();
203        for (i, entry) in entries.iter_mut().enumerate() {
204            resp.field_mut(&i.to_string(), entry);
205        }
206    })
207}
208
209/// Emulator for the hardware-level interface required to configure and trigger
210/// MSI-X interrupts on a PCI device.
211#[derive(Clone)]
212pub struct MsixEmulator {
213    state: Arc<Mutex<MsixState>>,
214    pending_bits_offset: u16,
215    pending_bits_dword_count: u16,
216}
217
218impl MsixEmulator {
219    /// Create a new [`MsixEmulator`] instance, along with with its associated
220    /// [`PciCapability`] structure.
221    ///
222    /// This implementation of MSI-X expects a dedicated BAR to store the vector
223    /// and pending tables.
224    ///
225    /// * * *
226    ///
227    /// DEVNOTE: This current implementation of MSI-X isn't particularly
228    /// "flexible" with respect to the various ways the PCI spec allows MSI-X to
229    /// be implemented. e.g: it uses a shared BAR for the table and BPA, with
230    /// fixed offsets into the BAR for both of those tables. It would be nice to
231    /// re-visit this code and make it more flexible.
232    pub fn new(
233        bar: u8,
234        count: u16,
235        register_msi: &mut dyn RegisterMsi,
236    ) -> (Self, impl PciCapability + use<>) {
237        let state = MsixState {
238            enabled: false,
239            vectors: (0..count)
240                .map(|_| MsixMessageTableEntry::new(register_msi.new_msi()))
241                .collect(),
242        };
243        let state = Arc::new(Mutex::new(state));
244        let pending_bits_offset = count * 16;
245        (
246            Self {
247                state: state.clone(),
248                pending_bits_offset,
249                pending_bits_dword_count: count.div_ceil(32),
250            },
251            MsixCapability {
252                count,
253                state,
254                config_table_location: MsiTableLocation::new(bar, 0),
255                pending_bits_location: MsiTableLocation::new(bar, pending_bits_offset.into()),
256            },
257        )
258    }
259
260    /// Return the total length of the MSI-X BAR
261    pub fn bar_len(&self) -> u64 {
262        (self.pending_bits_offset + self.pending_bits_dword_count * 4).into()
263    }
264
265    /// Read a `u32` from the MSI-X BAR at the given offset.
266    pub fn read_u32(&self, offset: u16) -> u32 {
267        let mut state = self.state.lock();
268        let state: &mut MsixState = &mut state;
269        if offset < self.pending_bits_offset {
270            let index = offset / 16;
271            if let Some(entry) = state.vectors.get(index as usize) {
272                return entry.read_u32(offset & 0xf);
273            }
274        } else {
275            let dword = (offset - self.pending_bits_offset) / 4;
276            let start = dword as usize * 32;
277            if start < state.vectors.len() {
278                let end = (start + 32).min(state.vectors.len());
279                let mut val = 0u32;
280                for (i, entry) in state.vectors[start..end].iter_mut().enumerate() {
281                    if entry.check_is_pending(state.enabled) {
282                        val |= 1 << i;
283                    }
284                }
285                return val;
286            }
287        }
288        tracelimit::warn_ratelimited!(offset, "Unexpected read offset");
289        0
290    }
291
292    /// Write a `u32` to the MSI-X BAR at the given offset.
293    pub fn write_u32(&mut self, offset: u16, val: u32) {
294        let mut state = self.state.lock();
295        if offset < self.pending_bits_offset {
296            let index = offset / 16;
297            let global = state.enabled;
298            if let Some(entry) = state.vectors.get_mut(index as usize) {
299                let was_enabled = entry.is_enabled(global);
300                entry.write_u32(offset & 0xf, val);
301                let is_enabled = entry.is_enabled(global);
302                if is_enabled && !was_enabled {
303                    entry.msi.enable(
304                        entry.state.address,
305                        entry.state.data,
306                        entry.state.is_pending,
307                    );
308                    entry.state.is_pending = false;
309                } else if was_enabled && !is_enabled {
310                    entry.msi.disable();
311                }
312                return;
313            }
314        } else if offset - self.pending_bits_offset < self.pending_bits_dword_count * 4 {
315            return;
316        }
317        tracelimit::warn_ratelimited!(offset, "Unexpected write offset");
318    }
319
320    /// Return an [`Interrupt`] associated with the particular MSI-X vector, or
321    /// `None` if the index is out of bounds.
322    pub fn interrupt(&self, index: u16) -> Option<Interrupt> {
323        Some(
324            self.state
325                .lock()
326                .vectors
327                .get_mut(index as usize)?
328                .msi
329                .interrupt(),
330        )
331    }
332
333    #[cfg(test)]
334    fn clear_pending_bit(&self, index: u8) {
335        let mut state = self.state.lock();
336        state.vectors[index as usize].state.is_pending = false;
337    }
338
339    #[cfg(test)]
340    fn set_pending_bit(&self, index: u8) {
341        let mut state = self.state.lock();
342        state.vectors[index as usize].state.is_pending = true;
343    }
344}
345
346mod save_restore {
347    use super::*;
348    use thiserror::Error;
349    use vmcore::save_restore::RestoreError;
350    use vmcore::save_restore::SaveError;
351    use vmcore::save_restore::SaveRestore;
352
353    mod state {
354        use mesh::payload::Protobuf;
355        use vmcore::save_restore::SavedStateRoot;
356
357        #[derive(Debug, Protobuf)]
358        #[mesh(package = "pci.caps.msix")]
359        pub struct SavedMsixMessageTableEntryState {
360            #[mesh(1)]
361            pub address: u64,
362            #[mesh(2)]
363            pub data: u32,
364            #[mesh(3)]
365            pub control: u32,
366            #[mesh(4)]
367            pub is_pending: bool,
368        }
369
370        #[derive(Debug, Protobuf, SavedStateRoot)]
371        #[mesh(package = "pci.caps.msix")]
372        pub struct SavedState {
373            #[mesh(2)]
374            pub enabled: bool,
375            #[mesh(3)]
376            pub vectors: Vec<SavedMsixMessageTableEntryState>,
377        }
378    }
379
380    #[derive(Debug, Error)]
381    enum MsixRestoreError {
382        #[error("mismatched vector lengths: current:{0}, saved:{1}")]
383        MismatchedTableLengths(usize, usize),
384    }
385
386    impl SaveRestore for MsixCapability {
387        type SavedState = state::SavedState;
388
389        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
390            let state = self.state.lock();
391            let saved_state = state::SavedState {
392                enabled: state.enabled,
393                vectors: {
394                    state
395                        .vectors
396                        .iter()
397                        .map(|vec| {
398                            let EntryState {
399                                address,
400                                data,
401                                control,
402                                is_pending,
403                            } = vec.state;
404
405                            state::SavedMsixMessageTableEntryState {
406                                address,
407                                data,
408                                control,
409                                is_pending,
410                            }
411                        })
412                        .collect()
413                },
414            };
415            Ok(saved_state)
416        }
417
418        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
419            let state::SavedState { enabled, vectors } = state;
420
421            let mut state = self.state.lock();
422            state.enabled = enabled;
423
424            if vectors.len() != state.vectors.len() {
425                return Err(RestoreError::InvalidSavedState(
426                    MsixRestoreError::MismatchedTableLengths(vectors.len(), state.vectors.len())
427                        .into(),
428                ));
429            }
430
431            for (new_vec, vec) in vectors.into_iter().zip(state.vectors.iter_mut()) {
432                vec.state = EntryState {
433                    address: new_vec.address,
434                    data: new_vec.data,
435                    control: new_vec.control,
436                    is_pending: new_vec.is_pending,
437                }
438            }
439
440            Ok(())
441        }
442    }
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use crate::msi::MsiInterruptSet;
449    use crate::test_helpers::TestPciInterruptController;
450
451    #[test]
452    fn msix_check() {
453        let mut set = MsiInterruptSet::new();
454        let (mut msix, mut cap) = MsixEmulator::new(2, 64, &mut set);
455        let msi_controller = TestPciInterruptController::new();
456        set.connect(&msi_controller);
457        // check capabilities
458        assert_eq!(cap.read_u32(0), 0x3f0011);
459        assert_eq!(cap.read_u32(4), 2);
460        assert_eq!(cap.read_u32(8), 0x402);
461        cap.write_u32(0, 0xffffffff);
462        assert_eq!(cap.read_u32(0), 0x803f0011);
463        // check BAR
464        // Vector[0]
465        assert_eq!(msix.read_u32(0), 0);
466        assert_eq!(msix.read_u32(4), 0);
467        assert_eq!(msix.read_u32(8), 0);
468        assert_eq!(msix.read_u32(12), 1);
469        msix.write_u32(0, 0x12345678);
470        msix.write_u32(4, 0x9abcdef0);
471        msix.write_u32(8, 0x123);
472        msix.write_u32(12, 0x456);
473        assert_eq!(msix.read_u32(0), 0x12345678);
474        assert_eq!(msix.read_u32(4), 0x9abcdef0);
475        assert_eq!(msix.read_u32(8), 0x123);
476        assert_eq!(msix.read_u32(12), 0x456);
477        // Vector[63]
478        assert_eq!(msix.read_u32(0x3f0), 0);
479        assert_eq!(msix.read_u32(0x3f4), 0);
480        assert_eq!(msix.read_u32(0x3f8), 0);
481        assert_eq!(msix.read_u32(0x3fc), 1);
482        msix.write_u32(0x3f0, 0x12345678);
483        msix.write_u32(0x3f4, 0x9abcdef0);
484        msix.write_u32(0x3f8, 0x123);
485        msix.write_u32(0x3fc, 0x456);
486        assert_eq!(msix.read_u32(0x3f0), 0x12345678);
487        assert_eq!(msix.read_u32(0x3f4), 0x9abcdef0);
488        assert_eq!(msix.read_u32(0x3f8), 0x123);
489        assert_eq!(msix.read_u32(0x3fc), 0x456);
490        // Pending Bit Array
491        assert_eq!(msix.read_u32(0x400), 0);
492        assert_eq!(msix.read_u32(0x404), 0);
493        msix.set_pending_bit(1);
494        assert_eq!(msix.read_u32(0x400), 2);
495        assert_eq!(msix.read_u32(0x404), 0);
496        msix.set_pending_bit(33);
497        assert_eq!(msix.read_u32(0x400), 2);
498        assert_eq!(msix.read_u32(0x404), 2);
499        msix.set_pending_bit(63);
500        msix.set_pending_bit(31);
501        assert_eq!(msix.read_u32(0x400), 0x80000002);
502        assert_eq!(msix.read_u32(0x404), 0x80000002);
503        msix.clear_pending_bit(1);
504        assert_eq!(msix.read_u32(0x400), 0x80000000);
505        assert_eq!(msix.read_u32(0x404), 0x80000002);
506    }
507}