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        let event = Event::new();
196        // SAFETY: `event` is moved into `MshvIrqFdRoute` below, which keeps
197        // it alive until `Drop` calls `unregister_irqfd`.
198        if let Err(e) = unsafe { self.partition.register_irqfd(&event, gsi) } {
199            self.partition.free_gsi(gsi);
200            return Err(e);
201        }
202
203        Ok(Box::new(MshvIrqFdRoute {
204            partition: self.partition.clone(),
205            gsi,
206            event,
207            last_route: Mutex::new(None),
208        }))
209    }
210}
211
212/// A registered irqfd route for a single GSI.
213///
214/// When dropped, unregisters the irqfd and frees the GSI.
215struct MshvIrqFdRoute {
216    partition: Arc<MshvPartitionInner>,
217    gsi: u32,
218    event: Event,
219    /// The last MSI route configured via `set_msi`, used to restore routing
220    /// on `unmask`.
221    last_route: Mutex<Option<MsiRoute>>,
222}
223
224impl IrqFdRoute for MshvIrqFdRoute {
225    fn event(&self) -> &Event {
226        &self.event
227    }
228
229    fn set_msi(&self, address: u64, data: u32) -> anyhow::Result<()> {
230        let route = MsiRoute {
231            address_lo: address as u32,
232            address_hi: (address >> 32) as u32,
233            data,
234        };
235        self.partition.set_gsi_route(self.gsi, Some(route))?;
236        *self.last_route.lock() = Some(route);
237        Ok(())
238    }
239
240    fn clear_msi(&self) -> anyhow::Result<()> {
241        self.partition.set_gsi_route(self.gsi, None)?;
242        *self.last_route.lock() = None;
243        Ok(())
244    }
245
246    fn mask(&self) -> anyhow::Result<()> {
247        // Disable the GSI route so the kernel stops injecting interrupts.
248        // The eventfd remains registered — any signals while masked can be
249        // consumed via consume_pending(). The last route is preserved so it
250        // can be restored on unmask.
251        self.partition.set_gsi_route(self.gsi, None)
252    }
253
254    fn unmask(&self) -> anyhow::Result<()> {
255        // Restore the previously configured MSI route.
256        let route = *self.last_route.lock();
257        if let Some(route) = route {
258            self.partition.set_gsi_route(self.gsi, Some(route))?;
259        }
260        Ok(())
261    }
262}
263
264impl Drop for MshvIrqFdRoute {
265    fn drop(&mut self) {
266        self.partition
267            .set_gsi_route(self.gsi, None)
268            .expect("failed to clear GSI route on drop");
269
270        // SAFETY: `self.event` is the same event passed to `register_irqfd`
271        // and is about to be dropped, so this is the last use.
272        unsafe {
273            self.partition
274                .unregister_irqfd(&self.event, self.gsi)
275                .expect("failed to unregister irqfd on drop");
276        }
277
278        self.partition.free_gsi(self.gsi);
279    }
280}
281
282/// Header for the MSI routing ioctl buffer, matching the layout of
283/// `mshv_user_irq_table` but implementing `Copy` (unlike the bindgen type
284/// which contains an `__IncompleteArrayField`).
285#[repr(C)]
286#[derive(Debug, Copy, Clone)]
287struct MsiRoutingHeader {
288    nr: u32,
289    rsvd: u32,
290}
291
292/// Pushes the full MSI routing table to the mshv kernel module.
293///
294/// This constructs the variable-length `mshv_user_irq_table` struct and calls
295/// the `MSHV_SET_MSI_ROUTING` ioctl.
296fn set_msi_routing_ioctl(
297    vmfd: &mshv_ioctls::VmFd,
298    entries: &[mshv_user_irq_entry],
299) -> anyhow::Result<()> {
300    let mut buf = HeaderVec::<MsiRoutingHeader, mshv_user_irq_entry, 0>::new(MsiRoutingHeader {
301        nr: entries.len() as u32,
302        rsvd: 0,
303    });
304    buf.extend_tail_from_slice(entries);
305
306    // SAFETY: `vmfd` is valid (owned by `MshvPartitionInner`). `buf.as_ptr()`
307    // points to a properly aligned buffer matching the layout of
308    // `mshv_user_irq_table`: a header with `nr` and `rsvd` fields followed
309    // by `nr` contiguous `mshv_user_irq_entry` values.
310    let ret = unsafe {
311        libc::ioctl(
312            vmfd.as_raw_fd(),
313            mshv_ioctls::MSHV_SET_MSI_ROUTING() as _,
314            buf.as_ptr(),
315        )
316    };
317    if ret < 0 {
318        return Err(std::io::Error::last_os_error()).context("MSHV_SET_MSI_ROUTING ioctl failed");
319    }
320
321    Ok(())
322}