Skip to main content

pci_core/
msi.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Traits for working with MSI interrupts.
5
6use crate::bus_range::AssignedBusRange;
7use pal_event::Event;
8use parking_lot::RwLock;
9use std::sync::Arc;
10use vmcore::irqfd::IrqFd;
11use vmcore::irqfd::IrqFdRoute;
12
13/// An object that can signal MSI interrupts.
14pub trait SignalMsi: Send + Sync {
15    /// Signals a message-signaled interrupt at the specified address with the specified data.
16    ///
17    /// `devid` is an optional device identity. Its meaning is layer-dependent:
18    /// at the device layer it is a BDF for multi-function devices (`None` for
19    /// single-function); at the ITS wrapper layer it is the fully composed ITS
20    /// device ID; backends that don't need it ignore it.
21    fn signal_msi(&self, devid: Option<u32>, address: u64, data: u32);
22}
23
24/// A kernel-mediated MSI interrupt route for a single vector.
25///
26/// Each route has an associated event. Signaling the event causes the
27/// hypervisor to inject the configured MSI into the guest without a
28/// userspace transition. This is used for device passthrough (VFIO)
29/// where the physical device signals the event on interrupt.
30pub struct MsiRoute {
31    inner: Box<dyn IrqFdRoute>,
32    default_bdf: DefaultBdf,
33}
34
35impl MsiRoute {
36    /// Returns the event that triggers interrupt injection when signaled.
37    ///
38    /// Pass this to VFIO `map_msix` or any other interrupt source.
39    pub fn event(&self) -> &Event {
40        self.inner.event()
41    }
42
43    /// Configures the MSI address and data for this route, using
44    /// the route's default BDF as the requester ID.
45    pub fn enable(&self, address: u64, data: u32) {
46        let resolved = resolve_default_bdf(&self.default_bdf);
47        self.inner.enable(address, data, Some(resolved))
48    }
49
50    /// Configures the MSI address and data for this route, using
51    /// an explicit segment-local BDF (`rid`) as the requester ID.
52    ///
53    /// Use this for multi-function devices whose functions span
54    /// multiple buses: the caller composes the full `(bus << 8) | devfn`
55    /// itself from whatever bus range it owns. The route's own
56    /// default `devfn` is bypassed.
57    ///
58    /// The bus portion of `rid` is validated against the route's
59    /// assigned bus range; if it falls outside the range the route
60    /// is left disabled and a ratelimited warning is emitted.
61    pub fn enable_with_rid(&self, rid: u16, address: u64, data: u32) {
62        let bus = (rid >> 8) as u8;
63        if !self.default_bdf.bus_range.contains_bus(bus) {
64            let (secondary, subordinate) = self.default_bdf.bus_range.bus_range();
65            tracelimit::warn_ratelimited!(
66                rid,
67                secondary,
68                subordinate,
69                "refusing to enable MSI route: rid bus outside assigned bus range"
70            );
71            self.inner.disable();
72            return;
73        }
74        self.inner.enable(address, data, Some(rid.into()))
75    }
76
77    /// Disables the MSI route. Interrupts that arrive while disabled
78    /// remain pending on the event and will be delivered when
79    /// [`enable`](Self::enable) is called, or can be drained via
80    /// [`consume_pending`](Self::consume_pending).
81    pub fn disable(&self) {
82        self.inner.disable()
83    }
84
85    /// Drains pending interrupt state and returns whether an interrupt
86    /// was pending while the route was masked.
87    pub fn consume_pending(&self) -> bool {
88        self.event().try_wait()
89    }
90}
91
92struct DisconnectedMsiTarget;
93
94impl SignalMsi for DisconnectedMsiTarget {
95    fn signal_msi(&self, _devid: Option<u32>, _address: u64, _data: u32) {
96        tracelimit::warn_ratelimited!("dropped MSI interrupt to disconnected target");
97    }
98}
99
100/// Default BDF source for MSI device identification.
101///
102/// [`MsiTarget::signal_msi`] uses this to compose the requester ID
103/// from the port's secondary bus combined with the configured `devfn`.
104#[derive(Clone, Debug)]
105struct DefaultBdf {
106    bus_range: AssignedBusRange,
107    devfn: u8,
108}
109
110/// Resolves a BDF from a [`DefaultBdf`] source.
111fn resolve_default_bdf(default: &DefaultBdf) -> u32 {
112    let (secondary, _) = default.bus_range.bus_range();
113    (secondary as u32) << 8 | default.devfn as u32
114}
115
116/// A connection between a device and an MSI target.
117#[derive(Debug)]
118pub struct MsiConnection {
119    target: MsiTarget,
120}
121
122/// An MSI target that can be used to signal MSI interrupts.
123#[derive(Clone)]
124pub struct MsiTarget {
125    inner: Arc<RwLock<MsiTargetInner>>,
126    default_bdf: DefaultBdf,
127}
128
129impl MsiTarget {
130    /// Returns a disconnected MSI target with a dummy BDF.
131    ///
132    /// Useful in tests and contexts where MSI delivery is not needed.
133    pub fn disconnected() -> Self {
134        Self {
135            inner: Arc::new(RwLock::new(MsiTargetInner {
136                signal_msi: Arc::new(DisconnectedMsiTarget),
137                irqfd: None,
138            })),
139            default_bdf: DefaultBdf {
140                bus_range: AssignedBusRange::new(),
141                devfn: 0,
142            },
143        }
144    }
145}
146
147impl std::fmt::Debug for MsiTarget {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        f.debug_struct("MsiTarget")
150            .field("default_bdf", &self.default_bdf)
151            .finish()
152    }
153}
154
155struct MsiTargetInner {
156    signal_msi: Arc<dyn SignalMsi>,
157    irqfd: Option<Arc<dyn IrqFd>>,
158}
159
160impl std::fmt::Debug for MsiTargetInner {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        let Self {
163            signal_msi: _,
164            irqfd,
165        } = self;
166        f.debug_struct("MsiTargetInner")
167            .field("has_irqfd", &irqfd.is_some())
168            .finish()
169    }
170}
171
172impl MsiConnection {
173    /// Creates a new disconnected MSI target connection.
174    ///
175    /// `bus_range` and `devfn` configure the default BDF identity
176    /// for this connection. When a device signals an MSI via
177    /// [`MsiTarget::signal_msi`], the BDF is resolved from the bus
178    /// range's secondary bus and the given `devfn`.
179    pub fn new(bus_range: AssignedBusRange, devfn: u8) -> Self {
180        Self {
181            target: MsiTarget {
182                inner: Arc::new(RwLock::new(MsiTargetInner {
183                    signal_msi: Arc::new(DisconnectedMsiTarget),
184                    irqfd: None,
185                })),
186                default_bdf: DefaultBdf { bus_range, devfn },
187            },
188        }
189    }
190
191    /// Updates the MSI target to which this connection signals interrupts.
192    pub fn connect(&self, signal_msi: Arc<dyn SignalMsi>) {
193        let mut inner = self.target.inner.write();
194        inner.signal_msi = signal_msi;
195    }
196
197    /// Sets the [`IrqFd`] for kernel-mediated MSI route allocation.
198    ///
199    /// When present, [`MsiTarget::new_route`] can create [`MsiRoute`]
200    /// instances for direct interrupt delivery.
201    pub fn connect_irqfd(&self, irqfd: Arc<dyn IrqFd>) {
202        let mut inner = self.target.inner.write();
203        inner.irqfd = Some(irqfd);
204    }
205
206    /// Returns the MSI target for this connection.
207    pub fn target(&self) -> &MsiTarget {
208        &self.target
209    }
210}
211
212impl MsiTarget {
213    /// Returns a new `MsiTarget` sharing the same connection and bus
214    /// range but with the given `devfn` in the default BDF.
215    ///
216    /// Use this to derive per-port targets: create one target per
217    /// bus range, then call `with_devfn(port_number)` to get a
218    /// target that resolves to `(bus << 8) | devfn`.
219    pub fn with_devfn(&self, devfn: u8) -> MsiTarget {
220        MsiTarget {
221            inner: self.inner.clone(),
222            default_bdf: DefaultBdf {
223                bus_range: self.default_bdf.bus_range.clone(),
224                devfn,
225            },
226        }
227    }
228
229    /// Returns a new `MsiTarget` sharing the same connection but with
230    /// a different bus range and devfn.
231    ///
232    /// Use this when a component (e.g. a PCIe switch) needs to derive
233    /// targets using a bus range it owns rather than the parent's.
234    pub fn with_bus_range(&self, bus_range: AssignedBusRange, devfn: u8) -> MsiTarget {
235        MsiTarget {
236            inner: self.inner.clone(),
237            default_bdf: DefaultBdf { bus_range, devfn },
238        }
239    }
240
241    /// Signals an MSI interrupt to this target, using this target's
242    /// default BDF as the requester ID.
243    pub fn signal_msi(&self, address: u64, data: u32) {
244        let resolved = resolve_default_bdf(&self.default_bdf);
245        let inner = self.inner.read();
246        inner.signal_msi.signal_msi(Some(resolved), address, data);
247    }
248
249    /// Signals an MSI interrupt to this target, using an explicit
250    /// segment-local BDF (`rid`) as the requester ID.
251    ///
252    /// Use this for multi-function devices whose functions span
253    /// multiple buses: the caller composes the full `(bus << 8) | devfn`
254    /// itself from whatever bus range it owns. This target's own
255    /// default `devfn` is bypassed.
256    ///
257    /// The bus portion of `rid` is validated against this target's
258    /// assigned bus range; if it falls outside the range the MSI is
259    /// dropped and a ratelimited warning is emitted.
260    pub fn signal_msi_with_rid(&self, rid: u16, address: u64, data: u32) {
261        let bus = (rid >> 8) as u8;
262        if !self.default_bdf.bus_range.contains_bus(bus) {
263            let (secondary, subordinate) = self.default_bdf.bus_range.bus_range();
264            tracelimit::warn_ratelimited!(
265                rid,
266                secondary,
267                subordinate,
268                "dropping MSI: rid bus outside assigned bus range"
269            );
270            return;
271        }
272        let inner = self.inner.read();
273        inner.signal_msi.signal_msi(Some(rid.into()), address, data);
274    }
275
276    /// Creates a new kernel-mediated MSI route for direct interrupt
277    /// delivery.
278    ///
279    /// The route inherits this target's default BDF source so that
280    /// [`MsiRoute::enable`] resolves the BDF the same way
281    /// [`signal_msi`](Self::signal_msi) does.
282    ///
283    /// Returns `None` if no [`IrqFd`] has been connected.
284    pub fn new_route(&self) -> Option<anyhow::Result<MsiRoute>> {
285        let inner = self.inner.read();
286        inner.irqfd.as_ref().map(|fd| {
287            Ok(MsiRoute {
288                inner: fd.new_irqfd_route()?,
289                default_bdf: self.default_bdf.clone(),
290            })
291        })
292    }
293
294    /// Returns the default BDF that will be used by
295    /// [`signal_msi`](Self::signal_msi) and [`MsiRoute::enable`].
296    pub fn default_bdf(&self) -> u32 {
297        resolve_default_bdf(&self.default_bdf)
298    }
299
300    /// Returns whether this target supports direct MSI routes.
301    pub fn supports_direct_msi(&self) -> bool {
302        let inner = self.inner.read();
303        inner.irqfd.is_some()
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::bus_range::AssignedBusRange;
311    use pal_event::Event;
312    use parking_lot::Mutex;
313    use std::collections::VecDeque;
314
315    /// A [`SignalMsi`] mock that records `(devid, address, data)`.
316    struct RecordingSignalMsi {
317        calls: Mutex<VecDeque<(Option<u32>, u64, u32)>>,
318    }
319
320    impl RecordingSignalMsi {
321        fn new() -> Arc<Self> {
322            Arc::new(Self {
323                calls: Mutex::new(VecDeque::new()),
324            })
325        }
326
327        fn pop(&self) -> Option<(Option<u32>, u64, u32)> {
328            self.calls.lock().pop_front()
329        }
330    }
331
332    impl SignalMsi for RecordingSignalMsi {
333        fn signal_msi(&self, devid: Option<u32>, address: u64, data: u32) {
334            self.calls.lock().push_back((devid, address, data));
335        }
336    }
337
338    #[derive(Debug, Clone, PartialEq)]
339    enum RouteCall {
340        Enable {
341            address: u64,
342            data: u32,
343            devid: Option<u32>,
344        },
345        Disable,
346    }
347
348    struct MockIrqFdRoute {
349        event: Event,
350        calls: Arc<Mutex<Vec<RouteCall>>>,
351    }
352
353    impl IrqFdRoute for MockIrqFdRoute {
354        fn event(&self) -> &Event {
355            &self.event
356        }
357
358        fn enable(&self, address: u64, data: u32, devid: Option<u32>) {
359            self.calls.lock().push(RouteCall::Enable {
360                address,
361                data,
362                devid,
363            });
364        }
365
366        fn disable(&self) {
367            self.calls.lock().push(RouteCall::Disable);
368        }
369    }
370
371    fn mock_irqfd(count: usize) -> (Arc<dyn IrqFd>, Vec<Arc<Mutex<Vec<RouteCall>>>>) {
372        let mut call_logs = Vec::new();
373        let route_params = Arc::new(Mutex::new(Vec::new()));
374        for _ in 0..count {
375            let calls = Arc::new(Mutex::new(Vec::new()));
376            call_logs.push(calls.clone());
377            route_params.lock().push(calls);
378        }
379
380        struct MockIrqFd {
381            routes: Mutex<Vec<Arc<Mutex<Vec<RouteCall>>>>>,
382        }
383        impl IrqFd for MockIrqFd {
384            fn new_irqfd_route(&self) -> anyhow::Result<Box<dyn IrqFdRoute>> {
385                let calls = self.routes.lock().remove(0);
386                Ok(Box::new(MockIrqFdRoute {
387                    event: Event::new(),
388                    calls,
389                }))
390            }
391        }
392
393        (
394            Arc::new(MockIrqFd {
395                routes: Mutex::new(call_logs.clone()),
396            }),
397            call_logs,
398        )
399    }
400
401    #[test]
402    fn signal_msi_resolves_default_bdf() {
403        let bus_range = AssignedBusRange::new();
404        bus_range.set_bus_range(5, 10);
405        let msi_conn = MsiConnection::new(bus_range, 0x18); // devfn = dev 3, fn 0
406        let recorder = RecordingSignalMsi::new();
407        msi_conn.connect(recorder.clone());
408
409        msi_conn.target().signal_msi(0xFEE0_0000, 42);
410
411        let (devid, addr, data) = recorder.pop().unwrap();
412        assert_eq!(devid, Some((5 << 8) | 0x18));
413        assert_eq!(addr, 0xFEE0_0000);
414        assert_eq!(data, 42);
415    }
416
417    #[test]
418    fn signal_msi_with_rid_accepts_bus_in_range() {
419        let bus_range = AssignedBusRange::new();
420        bus_range.set_bus_range(5, 10);
421        let msi_conn = MsiConnection::new(bus_range, 0);
422        let recorder = RecordingSignalMsi::new();
423        msi_conn.connect(recorder.clone());
424
425        // RID with bus=7, devfn=0x0A → within [5, 10]
426        let rid: u16 = (7 << 8) | 0x0A;
427        msi_conn.target().signal_msi_with_rid(rid, 0xABCD, 99);
428
429        let (devid, addr, data) = recorder.pop().unwrap();
430        assert_eq!(devid, Some(rid as u32));
431        assert_eq!(addr, 0xABCD);
432        assert_eq!(data, 99);
433    }
434
435    #[test]
436    fn signal_msi_with_rid_drops_bus_outside_range() {
437        let bus_range = AssignedBusRange::new();
438        bus_range.set_bus_range(5, 10);
439        let msi_conn = MsiConnection::new(bus_range, 0);
440        let recorder = RecordingSignalMsi::new();
441        msi_conn.connect(recorder.clone());
442
443        // bus=11, above subordinate=10 → dropped
444        let rid_above: u16 = 11 << 8;
445        msi_conn.target().signal_msi_with_rid(rid_above, 0xABCD, 1);
446        assert!(recorder.pop().is_none());
447
448        // bus=4, below secondary=5 → dropped
449        let rid_below: u16 = 4 << 8;
450        msi_conn.target().signal_msi_with_rid(rid_below, 0xABCD, 2);
451        assert!(recorder.pop().is_none());
452    }
453
454    #[test]
455    fn signal_msi_with_rid_accepts_boundary_buses() {
456        let bus_range = AssignedBusRange::new();
457        bus_range.set_bus_range(5, 10);
458        let msi_conn = MsiConnection::new(bus_range, 0);
459        let recorder = RecordingSignalMsi::new();
460        msi_conn.connect(recorder.clone());
461
462        // Exactly at secondary bus (5)
463        msi_conn.target().signal_msi_with_rid(5 << 8, 0x1000, 10);
464        assert!(recorder.pop().is_some());
465
466        // Exactly at subordinate bus (10)
467        msi_conn.target().signal_msi_with_rid(10 << 8, 0x2000, 20);
468        assert!(recorder.pop().is_some());
469    }
470
471    #[test]
472    fn route_enable_resolves_default_bdf() {
473        let bus_range = AssignedBusRange::new();
474        bus_range.set_bus_range(3, 8);
475        let (irqfd, calls) = mock_irqfd(1);
476        let msi_conn = MsiConnection::new(bus_range, 0x10); // devfn = dev 2, fn 0
477        msi_conn.connect_irqfd(irqfd);
478
479        let route = msi_conn.target().new_route().unwrap().unwrap();
480        route.enable(0xFEE0_0000, 55);
481
482        let log = calls[0].lock();
483        assert_eq!(log.len(), 1);
484        assert_eq!(
485            log[0],
486            RouteCall::Enable {
487                address: 0xFEE0_0000,
488                data: 55,
489                devid: Some((3 << 8) | 0x10),
490            }
491        );
492    }
493
494    #[test]
495    fn route_enable_with_rid_accepts_bus_in_range() {
496        let bus_range = AssignedBusRange::new();
497        bus_range.set_bus_range(5, 10);
498        let (irqfd, calls) = mock_irqfd(1);
499        let msi_conn = MsiConnection::new(bus_range, 0);
500        msi_conn.connect_irqfd(irqfd);
501
502        let route = msi_conn.target().new_route().unwrap().unwrap();
503        let rid: u16 = (7 << 8) | 0x0A;
504        route.enable_with_rid(rid, 0xBEEF, 77);
505
506        let log = calls[0].lock();
507        assert_eq!(log.len(), 1);
508        assert_eq!(
509            log[0],
510            RouteCall::Enable {
511                address: 0xBEEF,
512                data: 77,
513                devid: Some(rid as u32),
514            }
515        );
516    }
517
518    #[test]
519    fn route_enable_with_rid_disables_when_bus_outside_range() {
520        let bus_range = AssignedBusRange::new();
521        bus_range.set_bus_range(5, 10);
522        let (irqfd, calls) = mock_irqfd(1);
523        let msi_conn = MsiConnection::new(bus_range, 0);
524        msi_conn.connect_irqfd(irqfd);
525
526        let route = msi_conn.target().new_route().unwrap().unwrap();
527        // bus=11, above subordinate → should disable
528        let rid: u16 = 11 << 8;
529        route.enable_with_rid(rid, 0xBEEF, 77);
530
531        let log = calls[0].lock();
532        assert_eq!(log.len(), 1);
533        assert_eq!(log[0], RouteCall::Disable);
534    }
535
536    #[test]
537    fn with_devfn_derives_target_with_new_devfn() {
538        let bus_range = AssignedBusRange::new();
539        bus_range.set_bus_range(2, 5);
540        let msi_conn = MsiConnection::new(bus_range, 0);
541        let recorder = RecordingSignalMsi::new();
542        msi_conn.connect(recorder.clone());
543
544        let derived = msi_conn.target().with_devfn(0x18); // dev 3, fn 0
545        derived.signal_msi(0x1000, 1);
546
547        let (devid, _, _) = recorder.pop().unwrap();
548        assert_eq!(devid, Some((2 << 8) | 0x18));
549    }
550
551    #[test]
552    fn with_bus_range_derives_target_with_new_range() {
553        let parent_range = AssignedBusRange::new();
554        parent_range.set_bus_range(1, 20);
555        let msi_conn = MsiConnection::new(parent_range, 0);
556        let recorder = RecordingSignalMsi::new();
557        msi_conn.connect(recorder.clone());
558
559        let child_range = AssignedBusRange::new();
560        child_range.set_bus_range(10, 15);
561        let derived = msi_conn.target().with_bus_range(child_range, 0x08);
562        derived.signal_msi(0x2000, 2);
563
564        let (devid, _, _) = recorder.pop().unwrap();
565        // secondary=10, devfn=0x08 → BDF = (10 << 8) | 0x08
566        assert_eq!(devid, Some((10 << 8) | 0x08));
567
568        // Validation uses the child range, not the parent
569        derived.signal_msi_with_rid(16 << 8, 0x3000, 3);
570        assert!(recorder.pop().is_none()); // bus 16 > subordinate 15
571    }
572}