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