|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +//! Traits for irqfd-based interrupt delivery. |
| 5 | +//! |
| 6 | +//! irqfd allows a hypervisor to directly inject an MSI into a guest when an |
| 7 | +//! event is signaled, without involving userspace in the interrupt delivery |
| 8 | +//! path. This is used for device passthrough (e.g., VFIO) where the physical |
| 9 | +//! device signals an event and the hypervisor injects the corresponding MSI |
| 10 | +//! into the guest VM. |
| 11 | +
|
| 12 | +use pal_event::Event; |
| 13 | + |
| 14 | +/// Trait for partitions that support irqfd-based interrupt delivery. |
| 15 | +/// |
| 16 | +/// An irqfd associates an event with a GSI (Global System Interrupt), and a |
| 17 | +/// GSI routing table maps GSIs to MSI addresses and data values. When the |
| 18 | +/// event is signaled, the kernel looks up the GSI routing and injects the |
| 19 | +/// configured MSI into the guest without a usermode transition. |
| 20 | +pub trait IrqFd: Send + Sync { |
| 21 | + /// Creates a new irqfd route. |
| 22 | + /// |
| 23 | + /// Allocates a GSI, creates an event, and registers the event with the |
| 24 | + /// hypervisor so that signaling it injects the configured MSI into the |
| 25 | + /// guest. |
| 26 | + /// |
| 27 | + /// The caller retrieves the event via [`IrqFdRoute::event`] to pass to |
| 28 | + /// VFIO or other interrupt sources. |
| 29 | + /// |
| 30 | + /// When the route is dropped, the irqfd is unregistered and the GSI is |
| 31 | + /// freed. |
| 32 | + fn new_irqfd_route(&self) -> anyhow::Result<Box<dyn IrqFdRoute>>; |
| 33 | +} |
| 34 | + |
| 35 | +/// A handle to a registered irqfd route. |
| 36 | +/// |
| 37 | +/// Each route represents a single GSI with an associated event. When the |
| 38 | +/// event is signaled (e.g., by VFIO on a device interrupt), the kernel injects |
| 39 | +/// the MSI configured via [`set_msi`](IrqFdRoute::set_msi) into the guest. |
| 40 | +/// |
| 41 | +/// Dropping this handle unregisters the irqfd and frees the GSI. |
| 42 | +pub trait IrqFdRoute: Send + Sync { |
| 43 | + /// Returns the event that triggers interrupt injection when signaled. |
| 44 | + /// |
| 45 | + /// Pass this to VFIO `map_msix` or any other interrupt source. On Linux, |
| 46 | + /// this is an eventfd created by the implementation. On WHP (future), this |
| 47 | + /// is the event handle returned by `WHvCreateTrigger`. |
| 48 | + fn event(&self) -> &Event; |
| 49 | + |
| 50 | + /// Sets the MSI routing for this irqfd's GSI. |
| 51 | + /// |
| 52 | + /// `address` and `data` are the x86 MSI address and data values that the |
| 53 | + /// kernel will use when injecting the interrupt into the guest. |
| 54 | + fn set_msi(&self, address: u64, data: u32) -> anyhow::Result<()>; |
| 55 | + |
| 56 | + /// Clears the MSI routing for this irqfd's GSI. |
| 57 | + /// |
| 58 | + /// The irqfd remains registered but interrupt delivery is disabled until |
| 59 | + /// a new route is configured via [`set_msi`](IrqFdRoute::set_msi). |
| 60 | + fn clear_msi(&self) -> anyhow::Result<()>; |
| 61 | + |
| 62 | + /// Masks the route. |
| 63 | + /// |
| 64 | + /// While masked, interrupts arriving on the event are not injected into |
| 65 | + /// the guest. The caller should use [`consume_pending`](IrqFdRoute::consume_pending) |
| 66 | + /// to check whether an interrupt arrived while masked and store the |
| 67 | + /// result in the MSI-X PBA. On unmask, the caller should deliver any |
| 68 | + /// pending interrupt from the PBA before re-enabling the route. |
| 69 | + fn mask(&self) -> anyhow::Result<()>; |
| 70 | + |
| 71 | + /// Unmasks the route and re-enables interrupt injection. |
| 72 | + fn unmask(&self) -> anyhow::Result<()>; |
| 73 | + |
| 74 | + /// Drains the pending interrupt state and returns whether an interrupt |
| 75 | + /// was pending. |
| 76 | + /// |
| 77 | + /// This atomically reads and clears the event's counter. The caller |
| 78 | + /// should store the result in the MSI-X PBA (Pending Bit Array). |
| 79 | + /// Repeated calls after the first drain will return `false` until a |
| 80 | + /// new interrupt arrives, so the caller must persist the pending state |
| 81 | + /// externally (e.g., in the MSI-X emulator's PBA bits). |
| 82 | + fn consume_pending(&self) -> bool { |
| 83 | + self.event().try_wait() |
| 84 | + } |
| 85 | +} |
0 commit comments