Skip to main content

virt_mshv/
irqfd.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! irqfd support for the mshv hypervisor backend.
5//!
6//! This module implements [`IrqFd`] and [`IrqFdRoute`] for mshv, allowing
7//! eventfds to be registered with the mshv kernel module for direct MSI
8//! injection into the guest without a userspace transition.
9
10// UNSAFETY: Calling mshv ioctls for irqfd and MSI routing.
11#![expect(unsafe_code)]
12
13use crate::MshvPartitionInner;
14use anyhow::Context;
15use headervec::HeaderVec;
16use mshv_bindings::MSHV_IRQFD_BIT_DEASSIGN;
17use mshv_bindings::mshv_user_irq_entry;
18use mshv_bindings::mshv_user_irqfd;
19use pal_event::Event;
20use parking_lot::Mutex;
21use std::os::fd::AsFd;
22use std::os::fd::AsRawFd;
23use std::sync::Arc;
24use virt::irqfd::IrqFd;
25use virt::irqfd::IrqFdRoute;
26
27pub(crate) const NUM_GSIS: usize = 2048;
28
29/// MSI routing state for a single GSI.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub(crate) enum GsiState {
32    /// GSI slot is not allocated.
33    Unallocated,
34    /// GSI is allocated but has no active routing.
35    Disabled,
36    /// GSI is allocated with an active MSI route.
37    Enabled(MsiRoute),
38}
39
40/// An MSI routing entry (address + data) for a GSI.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub(crate) struct MsiRoute {
43    address_lo: u32,
44    address_hi: u32,
45    data: u32,
46}
47
48impl MshvPartitionInner {
49    /// Allocates an unused GSI.
50    fn alloc_gsi(&self) -> Option<u32> {
51        let mut states = self.gsi_states.lock();
52        let gsi = states
53            .iter()
54            .position(|state| matches!(state, GsiState::Unallocated))?;
55        states[gsi] = GsiState::Disabled;
56        Some(gsi as u32)
57    }
58
59    /// Frees an allocated GSI.
60    fn free_gsi(&self, gsi: u32) {
61        self.gsi_states.lock()[gsi as usize] = GsiState::Unallocated;
62    }
63
64    /// Sets the MSI routing for a GSI and pushes the full routing table to the
65    /// kernel. Rolls back the in-memory state on ioctl failure.
66    fn set_gsi_route(&self, gsi: u32, route: Option<MsiRoute>) -> anyhow::Result<()> {
67        let mut states = self.gsi_states.lock();
68        let state = &mut states[gsi as usize];
69        anyhow::ensure!(
70            !matches!(state, GsiState::Unallocated),
71            "cannot set route for unallocated GSI {gsi}"
72        );
73        let new_state = match route {
74            Some(r) => GsiState::Enabled(r),
75            None => GsiState::Disabled,
76        };
77        if *state == new_state {
78            return Ok(());
79        }
80        let old_state = *state;
81        *state = new_state;
82
83        if let Err(e) = Self::push_routing_table(&self.vmfd, &states) {
84            // Roll back to keep in-memory state consistent with the kernel.
85            states[gsi as usize] = old_state;
86            return Err(e);
87        }
88        Ok(())
89    }
90
91    /// Rebuilds and pushes the full routing table to the kernel.
92    fn push_routing_table(
93        vmfd: &mshv_ioctls::VmFd,
94        states: &[GsiState; NUM_GSIS],
95    ) -> anyhow::Result<()> {
96        let entries: Vec<mshv_user_irq_entry> = states
97            .iter()
98            .enumerate()
99            .filter_map(|(gsi, state)| match state {
100                GsiState::Enabled(route) => Some(mshv_user_irq_entry {
101                    gsi: gsi as u32,
102                    address_lo: route.address_lo,
103                    address_hi: route.address_hi,
104                    data: route.data,
105                }),
106                _ => None,
107            })
108            .collect();
109
110        set_msi_routing_ioctl(vmfd, &entries).context("failed to set MSI routing")
111    }
112
113    /// Registers an eventfd as an irqfd for the given GSI.
114    ///
115    /// # Safety
116    /// The caller must ensure that `event` outlives the irqfd registration
117    /// (i.e., until `unregister_irqfd` is called). The kernel holds a
118    /// reference to the underlying eventfd file descriptor.
119    unsafe fn register_irqfd(&self, event: &Event, gsi: u32) -> anyhow::Result<()> {
120        let irqfd_arg = mshv_user_irqfd {
121            fd: event.as_fd().as_raw_fd(),
122            resamplefd: 0,
123            gsi,
124            flags: 0,
125        };
126        // SAFETY: `self.vmfd` is valid because it is owned by
127        // `MshvPartitionInner` which outlives this call. The `irqfd_arg`
128        // struct is properly initialized on the stack. The caller guarantees
129        // `event` will outlive the registration.
130        let ret = unsafe {
131            libc::ioctl(
132                self.vmfd.as_raw_fd(),
133                mshv_ioctls::MSHV_IRQFD() as _,
134                std::ptr::from_ref(&irqfd_arg),
135            )
136        };
137        if ret < 0 {
138            return Err(std::io::Error::last_os_error()).context("MSHV_IRQFD register failed");
139        }
140        Ok(())
141    }
142
143    /// Unregisters an eventfd from an irqfd for the given GSI.
144    ///
145    /// # Safety
146    /// Must be called with the same `event` that was passed to
147    /// `register_irqfd`. After this call returns successfully, the kernel
148    /// no longer holds a reference to the eventfd.
149    unsafe fn unregister_irqfd(&self, event: &Event, gsi: u32) -> anyhow::Result<()> {
150        let irqfd_arg = mshv_user_irqfd {
151            fd: event.as_fd().as_raw_fd(),
152            resamplefd: 0,
153            gsi,
154            flags: 1 << MSHV_IRQFD_BIT_DEASSIGN,
155        };
156        // SAFETY: `self.vmfd` is valid because it is owned by
157        // `MshvPartitionInner` which outlives this call. The caller guarantees
158        // this is the same event passed to `register_irqfd`.
159        let ret = unsafe {
160            libc::ioctl(
161                self.vmfd.as_raw_fd(),
162                mshv_ioctls::MSHV_IRQFD() as _,
163                std::ptr::from_ref(&irqfd_arg),
164            )
165        };
166        if ret < 0 {
167            return Err(std::io::Error::last_os_error()).context("MSHV_IRQFD unregister failed");
168        }
169        Ok(())
170    }
171}
172
173/// irqfd routing interface for an mshv partition.
174///
175/// Wraps `Arc<MshvPartitionInner>` to implement the [`IrqFd`] trait.
176/// Routes created via [`IrqFd::new_irqfd_route`] hold their own
177/// `Arc<MshvPartitionInner>` reference for GSI management.
178pub(crate) struct MshvIrqFd {
179    partition: Arc<MshvPartitionInner>,
180}
181
182impl MshvIrqFd {
183    pub fn new(partition: Arc<MshvPartitionInner>) -> Self {
184        Self { partition }
185    }
186}
187
188impl IrqFd for MshvIrqFd {
189    fn new_irqfd_route(&self) -> anyhow::Result<Box<dyn IrqFdRoute>> {
190        let gsi = self
191            .partition
192            .alloc_gsi()
193            .context("no free GSIs available for irqfd")?;
194
195        // Defer the MSHV_IRQFD registration until `enable()` has installed the
196        // MSI routing for this GSI. On aarch64 the kernel maps a passthrough
197        // device interrupt to the guest vector at irqfd-assign (add-producer)
198        // time and does not retarget on a later routing change, so the routing
199        // must be in place *before* the irqfd is armed. Arming lazily also
200        // works for x86_64 (enable() arms after setting the route).
201        let event = Event::new();
202        Ok(Box::new(MshvIrqFdRoute {
203            partition: self.partition.clone(),
204            gsi,
205            event,
206            armed: Mutex::new(false),
207        }))
208    }
209}
210
211/// A registered irqfd route for a single GSI.
212///
213/// When dropped, unregisters the irqfd and frees the GSI.
214struct MshvIrqFdRoute {
215    partition: Arc<MshvPartitionInner>,
216    gsi: u32,
217    event: Event,
218    /// Whether the irqfd is currently armed (registered with the kernel).
219    /// Serializes route updates and arm/disarm ioctls to prevent races.
220    armed: Mutex<bool>,
221}
222
223impl MshvIrqFdRoute {
224    fn disarm(&self) {
225        let mut armed = self.armed.lock();
226        if *armed {
227            // SAFETY: `self.event` is the same event passed to `register_irqfd`.
228            if let Err(e) = unsafe { self.partition.unregister_irqfd(&self.event, self.gsi) } {
229                tracelimit::warn_ratelimited!(error = ?e, gsi = self.gsi, "failed to unregister irqfd");
230                return;
231            }
232            *armed = false;
233        }
234    }
235}
236
237impl IrqFdRoute for MshvIrqFdRoute {
238    fn event(&self) -> &Event {
239        &self.event
240    }
241
242    fn enable(&self, address: u64, data: u32, _devid: Option<u32>) {
243        let mut armed = self.armed.lock();
244        let route = MsiRoute {
245            address_lo: address as u32,
246            address_hi: (address >> 32) as u32,
247            data,
248        };
249        if let Err(e) = self.partition.set_gsi_route(self.gsi, Some(route)) {
250            tracelimit::warn_ratelimited!(error = ?e, gsi = self.gsi, "failed to set GSI route");
251            return;
252        }
253        if !*armed {
254            // SAFETY: `self.event` is owned by this struct and will outlive
255            // the registration (unregistered in `disarm` or `Drop`).
256            if let Err(e) = unsafe { self.partition.register_irqfd(&self.event, self.gsi) } {
257                tracelimit::warn_ratelimited!(error = ?e, gsi = self.gsi, "failed to register irqfd");
258                return;
259            }
260            *armed = true;
261        }
262    }
263
264    fn disable(&self) {
265        // Just disarm the irqfd. The routing entry is inert without an
266        // armed eventfd, so there's no need to remove it and trigger an
267        // expensive MSHV_SET_MSI_ROUTING ioctl. On re-enable, if the
268        // address/data haven't changed, set_gsi_route will be a no-op.
269        self.disarm();
270    }
271}
272
273impl Drop for MshvIrqFdRoute {
274    fn drop(&mut self) {
275        self.disarm();
276
277        self.partition
278            .set_gsi_route(self.gsi, None)
279            .expect("failed to clear GSI route on drop");
280
281        self.partition.free_gsi(self.gsi);
282    }
283}
284
285/// Header for the MSI routing ioctl buffer, matching the layout of
286/// `mshv_user_irq_table` but implementing `Copy` (unlike the bindgen type
287/// which contains an `__IncompleteArrayField`).
288#[repr(C)]
289#[derive(Debug, Copy, Clone)]
290struct MsiRoutingHeader {
291    nr: u32,
292    rsvd: u32,
293}
294
295/// Pushes the full MSI routing table to the mshv kernel module.
296///
297/// This constructs the variable-length `mshv_user_irq_table` struct and calls
298/// the `MSHV_SET_MSI_ROUTING` ioctl.
299fn set_msi_routing_ioctl(
300    vmfd: &mshv_ioctls::VmFd,
301    entries: &[mshv_user_irq_entry],
302) -> anyhow::Result<()> {
303    let mut buf = HeaderVec::<MsiRoutingHeader, mshv_user_irq_entry, 0>::new(MsiRoutingHeader {
304        nr: entries.len() as u32,
305        rsvd: 0,
306    });
307    buf.extend_tail_from_slice(entries);
308
309    // SAFETY: `vmfd` is valid (owned by `MshvPartitionInner`). `buf.as_ptr()`
310    // points to a properly aligned buffer matching the layout of
311    // `mshv_user_irq_table`: a header with `nr` and `rsvd` fields followed
312    // by `nr` contiguous `mshv_user_irq_entry` values.
313    let ret = unsafe {
314        libc::ioctl(
315            vmfd.as_raw_fd(),
316            mshv_ioctls::MSHV_SET_MSI_ROUTING() as _,
317            buf.as_ptr(),
318        )
319    };
320    if ret < 0 {
321        return Err(std::io::Error::last_os_error()).context("MSHV_SET_MSI_ROUTING ioctl failed");
322    }
323
324    Ok(())
325}