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_rid: DefaultRid,
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 the route's
44    /// default requester ID `(secondary_bus << 8) + rid_offset`.
45    ///
46    /// If the resolved bus falls outside the assigned bus range, the route is
47    /// left disabled and a ratelimited warning is emitted.
48    pub fn enable(&self, address: u64, data: u32) {
49        // `resolve_default_rid` emits the ratelimited warning when the
50        // resolved bus is out of range; just leave the route disabled here.
51        let Some(resolved) = resolve_default_rid(&self.default_rid) else {
52            self.inner.disable();
53            return;
54        };
55        self.inner.enable(address, data, Some(resolved))
56    }
57
58    /// Configures the MSI address and data for this route, using
59    /// an explicit segment-local BDF (`rid`) as the requester ID.
60    ///
61    /// Use this for multi-function devices whose functions span
62    /// multiple buses: the caller composes the full `(bus << 8) | devfn`
63    /// itself from whatever bus range it owns. The route's own
64    /// default `devfn` is bypassed.
65    ///
66    /// The bus portion of `rid` is validated against the route's
67    /// assigned bus range; if it falls outside the range the route
68    /// is left disabled and a ratelimited warning is emitted.
69    pub fn enable_with_rid(&self, rid: u16, address: u64, data: u32) {
70        let bus = (rid >> 8) as u8;
71        if !self.default_rid.bus_range.contains_bus(bus) {
72            let (secondary, subordinate) = self.default_rid.bus_range.bus_range();
73            tracelimit::warn_ratelimited!(
74                rid,
75                secondary,
76                subordinate,
77                "refusing to enable MSI route: rid bus outside assigned bus range"
78            );
79            self.inner.disable();
80            return;
81        }
82        self.inner.enable(address, data, Some(rid.into()))
83    }
84
85    /// Disables the MSI route. Interrupts that arrive while disabled
86    /// remain pending on the event and will be delivered when
87    /// [`enable`](Self::enable) is called, or can be drained via
88    /// [`consume_pending`](Self::consume_pending).
89    pub fn disable(&self) {
90        self.inner.disable()
91    }
92
93    /// Drains pending interrupt state and returns whether an interrupt
94    /// was pending while the route was masked.
95    pub fn consume_pending(&self) -> bool {
96        self.event().try_wait()
97    }
98}
99
100struct DisconnectedMsiTarget;
101
102impl SignalMsi for DisconnectedMsiTarget {
103    fn signal_msi(&self, _devid: Option<u32>, _address: u64, _data: u32) {
104        tracelimit::warn_ratelimited!("dropped MSI interrupt to disconnected target");
105    }
106}
107
108/// Default requester-ID source for MSI device identification.
109///
110/// [`MsiTarget::signal_msi`] composes the requester ID at signal time as
111/// `(secondary_bus << 8) + rid_offset`, reading the secondary bus from the
112/// live [`AssignedBusRange`]. For a single-function device `rid_offset` is
113/// just its devfn; for SR-IOV VFs it may carry into the bus byte to address
114/// functions on higher buses within the assigned range.
115#[derive(Clone, Debug)]
116struct DefaultRid {
117    bus_range: AssignedBusRange,
118    rid_offset: u16,
119}
120
121/// Resolves a requester ID from a [`DefaultRid`] source, composing it as
122/// `(secondary_bus << 8) + rid_offset` against the live bus range.
123///
124/// Returns `None` when the resulting bus falls outside the assigned range
125/// (the offset reaches past the subordinate bus), in which case a ratelimited
126/// warning is emitted and the caller should drop the MSI / disable the route.
127/// The offset is non-negative, so the bus is always at least the secondary
128/// bus; only the upper bound can be exceeded.
129fn resolve_default_rid(default: &DefaultRid) -> Option<u32> {
130    let (secondary, subordinate) = default.bus_range.bus_range();
131    let rid = ((secondary as u32) << 8) + default.rid_offset as u32;
132    if rid >> 8 > subordinate as u32 {
133        tracelimit::warn_ratelimited!(
134            rid,
135            secondary,
136            subordinate,
137            "dropping MSI: rid bus outside assigned bus range"
138        );
139        return None;
140    }
141    Some(rid)
142}
143
144/// A late-bound MSI backend slot.
145///
146/// A connection carries no device identity — it is purely the backend that
147/// MSIs are delivered to, filled in after construction via [`connect`].
148/// Identity is supplied when a target is derived via
149/// [`msi_target`](Self::msi_target), or when a
150/// [`DmaTarget`](crate::dma::DmaTarget) is built from it.
151///
152/// [`connect`]: Self::connect
153#[derive(Debug)]
154pub struct MsiConnection {
155    inner: Arc<RwLock<MsiTargetInner>>,
156}
157
158/// An MSI target that can be used to signal MSI interrupts.
159#[derive(Clone)]
160pub struct MsiTarget {
161    inner: Arc<RwLock<MsiTargetInner>>,
162    default_rid: DefaultRid,
163}
164
165impl MsiTarget {
166    /// Returns a disconnected MSI target with a dummy BDF.
167    ///
168    /// Useful in tests and contexts where MSI delivery is not needed.
169    pub fn disconnected() -> Self {
170        Self {
171            inner: Arc::new(RwLock::new(MsiTargetInner {
172                signal_msi: Arc::new(DisconnectedMsiTarget),
173                irqfd: None,
174            })),
175            default_rid: DefaultRid {
176                bus_range: AssignedBusRange::new(),
177                rid_offset: 0,
178            },
179        }
180    }
181}
182
183impl std::fmt::Debug for MsiTarget {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        f.debug_struct("MsiTarget")
186            .field("default_rid", &self.default_rid)
187            .finish()
188    }
189}
190
191struct MsiTargetInner {
192    signal_msi: Arc<dyn SignalMsi>,
193    irqfd: Option<Arc<dyn IrqFd>>,
194}
195
196impl std::fmt::Debug for MsiTargetInner {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        let Self {
199            signal_msi: _,
200            irqfd,
201        } = self;
202        f.debug_struct("MsiTargetInner")
203            .field("has_irqfd", &irqfd.is_some())
204            .finish()
205    }
206}
207
208impl MsiConnection {
209    /// Creates a new disconnected MSI connection.
210    ///
211    /// The connection is purely the late-bound MSI backend slot; it carries
212    /// no device identity. Callers stamp identity when they derive a target
213    /// via [`msi_target`](Self::msi_target), or by building a
214    /// [`DmaTarget`](crate::dma::DmaTarget) from it.
215    pub fn new() -> Self {
216        Self {
217            inner: Arc::new(RwLock::new(MsiTargetInner {
218                signal_msi: Arc::new(DisconnectedMsiTarget),
219                irqfd: None,
220            })),
221        }
222    }
223
224    /// Updates the MSI target to which this connection signals interrupts.
225    pub fn connect(&self, signal_msi: Arc<dyn SignalMsi>) {
226        let mut inner = self.inner.write();
227        inner.signal_msi = signal_msi;
228    }
229
230    /// Sets the [`IrqFd`] for kernel-mediated MSI route allocation.
231    ///
232    /// When present, [`MsiTarget::new_route`] can create [`MsiRoute`]
233    /// instances for direct interrupt delivery.
234    pub fn connect_irqfd(&self, irqfd: Arc<dyn IrqFd>) {
235        let mut inner = self.inner.write();
236        inner.irqfd = Some(irqfd);
237    }
238
239    /// Derives an MSI target with the given identity, sharing this
240    /// connection's (late-bound) backend slot.
241    pub fn msi_target(&self, bus_range: AssignedBusRange, devfn: u8) -> MsiTarget {
242        MsiTarget {
243            inner: self.inner.clone(),
244            default_rid: DefaultRid {
245                bus_range,
246                rid_offset: devfn as u16,
247            },
248        }
249    }
250
251    /// Derives an MSI target with no device identity (an empty bus range).
252    ///
253    /// Use for MSI emitters that don't need a meaningful requester ID, or
254    /// that re-anchor identity themselves (e.g. PCIe switches).
255    pub fn target(&self) -> MsiTarget {
256        self.msi_target(AssignedBusRange::new(), 0)
257    }
258}
259
260impl Default for MsiConnection {
261    fn default() -> Self {
262        Self::new()
263    }
264}
265
266impl MsiTarget {
267    /// Returns a new `MsiTarget` sharing the same connection and bus
268    /// range but with the given `devfn` in the default BDF.
269    ///
270    /// Use this to derive per-port targets: create one target per
271    /// bus range, then call `with_devfn(port_number)` to get a
272    /// target that resolves to `(bus << 8) | devfn`.
273    pub fn with_devfn(&self, devfn: u8) -> MsiTarget {
274        self.with_rid_offset(devfn as u16)
275    }
276
277    /// Returns a new `MsiTarget` sharing the same connection but with
278    /// a different bus range and devfn.
279    ///
280    /// Use this when a component (e.g. a PCIe switch) needs to derive
281    /// targets using a bus range it owns rather than the parent's.
282    pub fn with_bus_range(&self, bus_range: AssignedBusRange, devfn: u8) -> MsiTarget {
283        MsiTarget {
284            inner: self.inner.clone(),
285            default_rid: DefaultRid {
286                bus_range,
287                rid_offset: devfn as u16,
288            },
289        }
290    }
291
292    /// Returns a new `MsiTarget` sharing the same connection and bus range
293    /// but with the requester-ID offset set so the target resolves to the
294    /// given absolute `rid`.
295    ///
296    /// The offset is computed against the *current* secondary bus, so call
297    /// this only once the bus range is assigned. For targets derived before
298    /// the bus is programmed (e.g. SR-IOV VFs), use
299    /// [`with_rid_offset`](Self::with_rid_offset) instead.
300    ///
301    /// The resulting bus is validated against the assigned bus range when an
302    /// MSI is signaled (see [`signal_msi`](Self::signal_msi)), not here.
303    pub fn with_rid(&self, rid: u16) -> MsiTarget {
304        let (secondary, _) = self.default_rid.bus_range.bus_range();
305        self.with_rid_offset(rid.wrapping_sub((secondary as u16) << 8))
306    }
307
308    /// Returns a new `MsiTarget` sharing the same connection and bus range
309    /// but with the requester-ID offset set to `rid_offset`.
310    ///
311    /// The RID is resolved at signal time as `(secondary_bus << 8) +
312    /// rid_offset`, so the target tracks the live bus assignment. This is the
313    /// primitive for SR-IOV VFs, which are constructed before the PF's bus is
314    /// programmed: pass the VF's RID offset (e.g. VF Offset + index × VF
315    /// Stride) and it resolves correctly once the bus range is assigned.
316    pub fn with_rid_offset(&self, rid_offset: u16) -> MsiTarget {
317        MsiTarget {
318            inner: self.inner.clone(),
319            default_rid: DefaultRid {
320                bus_range: self.default_rid.bus_range.clone(),
321                rid_offset,
322            },
323        }
324    }
325
326    /// Signals an MSI interrupt to this target, using this target's
327    /// default BDF as the requester ID.
328    pub fn signal_msi(&self, address: u64, data: u32) {
329        let Some(resolved) = resolve_default_rid(&self.default_rid) else {
330            return;
331        };
332        let inner = self.inner.read();
333        inner.signal_msi.signal_msi(Some(resolved), address, data);
334    }
335
336    /// Signals an MSI interrupt to this target, using an explicit
337    /// segment-local BDF (`rid`) as the requester ID.
338    ///
339    /// Use this for multi-function devices whose functions span
340    /// multiple buses: the caller composes the full `(bus << 8) | devfn`
341    /// itself from whatever bus range it owns. This target's own
342    /// default `devfn` is bypassed.
343    ///
344    /// The bus portion of `rid` is validated against this target's
345    /// assigned bus range; if it falls outside the range the MSI is
346    /// dropped and a ratelimited warning is emitted.
347    pub fn signal_msi_with_rid(&self, rid: u16, address: u64, data: u32) {
348        let bus = (rid >> 8) as u8;
349        if !self.default_rid.bus_range.contains_bus(bus) {
350            let (secondary, subordinate) = self.default_rid.bus_range.bus_range();
351            tracelimit::warn_ratelimited!(
352                rid,
353                secondary,
354                subordinate,
355                "dropping MSI: rid bus outside assigned bus range"
356            );
357            return;
358        }
359        let inner = self.inner.read();
360        inner.signal_msi.signal_msi(Some(rid.into()), address, data);
361    }
362
363    /// Creates a new kernel-mediated MSI route for direct interrupt
364    /// delivery.
365    ///
366    /// The route inherits this target's default BDF source so that
367    /// [`MsiRoute::enable`] resolves the BDF the same way
368    /// [`signal_msi`](Self::signal_msi) does.
369    ///
370    /// Returns `None` if no [`IrqFd`] has been connected.
371    pub fn new_route(&self) -> Option<anyhow::Result<MsiRoute>> {
372        let inner = self.inner.read();
373        inner.irqfd.as_ref().map(|fd| {
374            Ok(MsiRoute {
375                inner: fd.new_irqfd_route()?,
376                default_rid: self.default_rid.clone(),
377            })
378        })
379    }
380
381    /// Returns whether this target supports direct MSI routes.
382    pub fn supports_direct_msi(&self) -> bool {
383        let inner = self.inner.read();
384        inner.irqfd.is_some()
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use crate::bus_range::AssignedBusRange;
392    use pal_event::Event;
393    use parking_lot::Mutex;
394    use std::collections::VecDeque;
395
396    /// A [`SignalMsi`] mock that records `(devid, address, data)`.
397    struct RecordingSignalMsi {
398        calls: Mutex<VecDeque<(Option<u32>, u64, u32)>>,
399    }
400
401    impl RecordingSignalMsi {
402        fn new() -> Arc<Self> {
403            Arc::new(Self {
404                calls: Mutex::new(VecDeque::new()),
405            })
406        }
407
408        fn pop(&self) -> Option<(Option<u32>, u64, u32)> {
409            self.calls.lock().pop_front()
410        }
411    }
412
413    impl SignalMsi for RecordingSignalMsi {
414        fn signal_msi(&self, devid: Option<u32>, address: u64, data: u32) {
415            self.calls.lock().push_back((devid, address, data));
416        }
417    }
418
419    #[derive(Debug, Clone, PartialEq)]
420    enum RouteCall {
421        Enable {
422            address: u64,
423            data: u32,
424            devid: Option<u32>,
425        },
426        Disable,
427    }
428
429    struct MockIrqFdRoute {
430        event: Event,
431        calls: Arc<Mutex<Vec<RouteCall>>>,
432    }
433
434    impl IrqFdRoute for MockIrqFdRoute {
435        fn event(&self) -> &Event {
436            &self.event
437        }
438
439        fn enable(&self, address: u64, data: u32, devid: Option<u32>) {
440            self.calls.lock().push(RouteCall::Enable {
441                address,
442                data,
443                devid,
444            });
445        }
446
447        fn disable(&self) {
448            self.calls.lock().push(RouteCall::Disable);
449        }
450    }
451
452    fn mock_irqfd(count: usize) -> (Arc<dyn IrqFd>, Vec<Arc<Mutex<Vec<RouteCall>>>>) {
453        let mut call_logs = Vec::new();
454        let route_params = Arc::new(Mutex::new(Vec::new()));
455        for _ in 0..count {
456            let calls = Arc::new(Mutex::new(Vec::new()));
457            call_logs.push(calls.clone());
458            route_params.lock().push(calls);
459        }
460
461        struct MockIrqFd {
462            routes: Mutex<Vec<Arc<Mutex<Vec<RouteCall>>>>>,
463        }
464        impl IrqFd for MockIrqFd {
465            fn new_irqfd_route(&self) -> anyhow::Result<Box<dyn IrqFdRoute>> {
466                let calls = self.routes.lock().remove(0);
467                Ok(Box::new(MockIrqFdRoute {
468                    event: Event::new(),
469                    calls,
470                }))
471            }
472        }
473
474        (
475            Arc::new(MockIrqFd {
476                routes: Mutex::new(call_logs.clone()),
477            }),
478            call_logs,
479        )
480    }
481
482    #[test]
483    fn signal_msi_resolves_default_rid() {
484        let bus_range = AssignedBusRange::new();
485        bus_range.set_bus_range(5, 10);
486        let msi_conn = MsiConnection::new();
487        let recorder = RecordingSignalMsi::new();
488        msi_conn.connect(recorder.clone());
489
490        msi_conn
491            .msi_target(bus_range, 0x18)
492            .signal_msi(0xFEE0_0000, 42);
493
494        let (devid, addr, data) = recorder.pop().unwrap();
495        assert_eq!(devid, Some((5 << 8) | 0x18));
496        assert_eq!(addr, 0xFEE0_0000);
497        assert_eq!(data, 42);
498    }
499
500    #[test]
501    fn signal_msi_with_rid_accepts_bus_in_range() {
502        let bus_range = AssignedBusRange::new();
503        bus_range.set_bus_range(5, 10);
504        let msi_conn = MsiConnection::new();
505        let recorder = RecordingSignalMsi::new();
506        msi_conn.connect(recorder.clone());
507
508        // RID with bus=7, devfn=0x0A → within [5, 10]
509        let rid: u16 = (7 << 8) | 0x0A;
510        msi_conn
511            .msi_target(bus_range, 0)
512            .signal_msi_with_rid(rid, 0xABCD, 99);
513
514        let (devid, addr, data) = recorder.pop().unwrap();
515        assert_eq!(devid, Some(rid as u32));
516        assert_eq!(addr, 0xABCD);
517        assert_eq!(data, 99);
518    }
519
520    #[test]
521    fn signal_msi_with_rid_drops_bus_outside_range() {
522        let bus_range = AssignedBusRange::new();
523        bus_range.set_bus_range(5, 10);
524        let msi_conn = MsiConnection::new();
525        let recorder = RecordingSignalMsi::new();
526        msi_conn.connect(recorder.clone());
527
528        // bus=11, above subordinate=10 → dropped
529        let rid_above: u16 = 11 << 8;
530        msi_conn
531            .msi_target(bus_range.clone(), 0)
532            .signal_msi_with_rid(rid_above, 0xABCD, 1);
533        assert!(recorder.pop().is_none());
534
535        // bus=4, below secondary=5 → dropped
536        let rid_below: u16 = 4 << 8;
537        msi_conn
538            .msi_target(bus_range, 0)
539            .signal_msi_with_rid(rid_below, 0xABCD, 2);
540        assert!(recorder.pop().is_none());
541    }
542
543    #[test]
544    fn signal_msi_with_rid_accepts_boundary_buses() {
545        let bus_range = AssignedBusRange::new();
546        bus_range.set_bus_range(5, 10);
547        let msi_conn = MsiConnection::new();
548        let recorder = RecordingSignalMsi::new();
549        msi_conn.connect(recorder.clone());
550
551        // Exactly at secondary bus (5)
552        msi_conn
553            .msi_target(bus_range.clone(), 0)
554            .signal_msi_with_rid(5 << 8, 0x1000, 10);
555        assert!(recorder.pop().is_some());
556
557        // Exactly at subordinate bus (10)
558        msi_conn
559            .msi_target(bus_range, 0)
560            .signal_msi_with_rid(10 << 8, 0x2000, 20);
561        assert!(recorder.pop().is_some());
562    }
563
564    #[test]
565    fn route_enable_resolves_default_rid() {
566        let bus_range = AssignedBusRange::new();
567        bus_range.set_bus_range(3, 8);
568        let (irqfd, calls) = mock_irqfd(1);
569        let msi_conn = MsiConnection::new();
570        msi_conn.connect_irqfd(irqfd);
571
572        let route = msi_conn
573            .msi_target(bus_range, 0x10)
574            .new_route()
575            .unwrap()
576            .unwrap();
577        route.enable(0xFEE0_0000, 55);
578
579        let log = calls[0].lock();
580        assert_eq!(log.len(), 1);
581        assert_eq!(
582            log[0],
583            RouteCall::Enable {
584                address: 0xFEE0_0000,
585                data: 55,
586                devid: Some((3 << 8) | 0x10),
587            }
588        );
589    }
590
591    #[test]
592    fn route_enable_with_rid_accepts_bus_in_range() {
593        let bus_range = AssignedBusRange::new();
594        bus_range.set_bus_range(5, 10);
595        let (irqfd, calls) = mock_irqfd(1);
596        let msi_conn = MsiConnection::new();
597        msi_conn.connect_irqfd(irqfd);
598
599        let route = msi_conn
600            .msi_target(bus_range, 0)
601            .new_route()
602            .unwrap()
603            .unwrap();
604        let rid: u16 = (7 << 8) | 0x0A;
605        route.enable_with_rid(rid, 0xBEEF, 77);
606
607        let log = calls[0].lock();
608        assert_eq!(log.len(), 1);
609        assert_eq!(
610            log[0],
611            RouteCall::Enable {
612                address: 0xBEEF,
613                data: 77,
614                devid: Some(rid as u32),
615            }
616        );
617    }
618
619    #[test]
620    fn route_enable_with_rid_disables_when_bus_outside_range() {
621        let bus_range = AssignedBusRange::new();
622        bus_range.set_bus_range(5, 10);
623        let (irqfd, calls) = mock_irqfd(1);
624        let msi_conn = MsiConnection::new();
625        msi_conn.connect_irqfd(irqfd);
626
627        let route = msi_conn
628            .msi_target(bus_range, 0)
629            .new_route()
630            .unwrap()
631            .unwrap();
632        // bus=11, above subordinate → should disable
633        let rid: u16 = 11 << 8;
634        route.enable_with_rid(rid, 0xBEEF, 77);
635
636        let log = calls[0].lock();
637        assert_eq!(log.len(), 1);
638        assert_eq!(log[0], RouteCall::Disable);
639    }
640
641    #[test]
642    fn with_devfn_derives_target_with_new_devfn() {
643        let bus_range = AssignedBusRange::new();
644        bus_range.set_bus_range(2, 5);
645        let msi_conn = MsiConnection::new();
646        let recorder = RecordingSignalMsi::new();
647        msi_conn.connect(recorder.clone());
648
649        let derived = msi_conn.msi_target(bus_range, 0).with_devfn(0x18); // dev 3, fn 0
650        derived.signal_msi(0x1000, 1);
651
652        let (devid, _, _) = recorder.pop().unwrap();
653        assert_eq!(devid, Some((2 << 8) | 0x18));
654    }
655
656    #[test]
657    fn with_bus_range_derives_target_with_new_range() {
658        let parent_range = AssignedBusRange::new();
659        parent_range.set_bus_range(1, 20);
660        let msi_conn = MsiConnection::new();
661        let recorder = RecordingSignalMsi::new();
662        msi_conn.connect(recorder.clone());
663
664        let child_range = AssignedBusRange::new();
665        child_range.set_bus_range(10, 15);
666        let derived = msi_conn
667            .msi_target(parent_range, 0)
668            .with_bus_range(child_range, 0x08);
669        derived.signal_msi(0x2000, 2);
670
671        let (devid, _, _) = recorder.pop().unwrap();
672        // secondary=10, devfn=0x08 → BDF = (10 << 8) | 0x08
673        assert_eq!(devid, Some((10 << 8) | 0x08));
674
675        // Validation uses the child range, not the parent
676        derived.signal_msi_with_rid(16 << 8, 0x3000, 3);
677        assert!(recorder.pop().is_none()); // bus 16 > subordinate 15
678    }
679
680    #[test]
681    fn with_rid_signal_msi_accepts_bus_in_range() {
682        let bus_range = AssignedBusRange::new();
683        bus_range.set_bus_range(5, 10);
684        let msi_conn = MsiConnection::new();
685        let recorder = RecordingSignalMsi::new();
686        msi_conn.connect(recorder.clone());
687
688        // RID with bus=7 (within [5, 10]), devfn=0x0A
689        let rid: u16 = (7 << 8) | 0x0A;
690        let derived = msi_conn.msi_target(bus_range, 0).with_rid(rid);
691        derived.signal_msi(0x1000, 7);
692
693        let (devid, addr, data) = recorder.pop().unwrap();
694        assert_eq!(devid, Some(rid as u32));
695        assert_eq!(addr, 0x1000);
696        assert_eq!(data, 7);
697    }
698
699    #[test]
700    fn with_rid_signal_msi_drops_bus_outside_range() {
701        let bus_range = AssignedBusRange::new();
702        bus_range.set_bus_range(5, 10);
703        let msi_conn = MsiConnection::new();
704        let recorder = RecordingSignalMsi::new();
705        msi_conn.connect(recorder.clone());
706
707        // bus=11 > subordinate=10 → dropped
708        let derived_above = msi_conn.msi_target(bus_range.clone(), 0).with_rid(11 << 8);
709        derived_above.signal_msi(0x1000, 1);
710        assert!(recorder.pop().is_none());
711
712        // bus=4 < secondary=5 → dropped
713        let derived_below = msi_conn.msi_target(bus_range, 0).with_rid(4 << 8);
714        derived_below.signal_msi(0x2000, 2);
715        assert!(recorder.pop().is_none());
716    }
717
718    #[test]
719    fn with_rid_route_enable_disables_when_bus_outside_range() {
720        let bus_range = AssignedBusRange::new();
721        bus_range.set_bus_range(5, 10);
722        let (irqfd, calls) = mock_irqfd(1);
723        let msi_conn = MsiConnection::new();
724        msi_conn.connect_irqfd(irqfd);
725
726        // Derive a target whose override bus (11) is outside [5, 10], then
727        // enable a route from it: the route must be disabled, not enabled.
728        let derived = msi_conn.msi_target(bus_range, 0).with_rid(11 << 8);
729        let route = derived.new_route().unwrap().unwrap();
730        route.enable(0xBEEF, 77);
731
732        let log = calls[0].lock();
733        assert_eq!(log.len(), 1);
734        assert_eq!(log[0], RouteCall::Disable);
735    }
736}