Skip to main content

vmcore/
interrupt.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Types to support delivering notifications to the guest.
5
6#![forbid(unsafe_code)]
7
8use crate::local_only::LocalOnly;
9use mesh::MeshPayload;
10use pal_async::driver::SpawnDriver;
11use pal_async::task::Task;
12use pal_async::wait::PolledWait;
13use pal_event::Event;
14use std::fmt::Debug;
15use std::sync::Arc;
16
17/// An object representing an interrupt-like signal to notify the guest of
18/// device activity.
19///
20/// This is generally an edge-triggered interrupt, but it could also be a synic
21/// event or similar notification.
22///
23/// The interrupt can be backed by a [`pal_event::Event`], a
24/// [`mesh::Cell<pal_event::Event>`], or a function. In the former two cases, the
25/// `Interrupt` can be sent across a mesh channel to remote processes.
26#[derive(Clone, Debug, MeshPayload)]
27pub struct Interrupt {
28    inner: InterruptInner,
29}
30
31impl Default for Interrupt {
32    fn default() -> Self {
33        Self::null()
34    }
35}
36
37impl Interrupt {
38    /// An interrupt that does nothing.
39    ///
40    /// N.B. If the caller requires the interrupt to have an underlying event, it is recommended to
41    ///      use [`Self::null_event`] instead.
42    pub fn null() -> Self {
43        // Create a dummy event.
44        Self::from_event(Event::new())
45    }
46
47    /// An interrupt that does nothing but is guaranteed to have an underlying event.
48    ///
49    /// N.B. Currently this does the same thing as [`Self::null`], but it allows [`Self::null`] to
50    ///      be optimized in the future, while callers that require an event can use this function.
51    pub fn null_event() -> Self {
52        Self::from_event(Event::new())
53    }
54
55    /// Creates an interrupt from an event.
56    ///
57    /// The event will be signaled when [`Self::deliver`] is called.
58    pub fn from_event(event: Event) -> Self {
59        Self {
60            inner: InterruptInner::Event(Arc::new(event)),
61        }
62    }
63
64    /// Creates an interrupt from a mesh cell containing an event.
65    ///
66    /// The current event will be signaled when [`Self::deliver`] is called. The event
67    /// can be transparently changed without interaction from the caller.
68    pub fn from_cell(cell: mesh::Cell<Event>) -> Self {
69        Self {
70            inner: InterruptInner::Cell(Arc::new(cell)),
71        }
72    }
73
74    /// Creates an interrupt from a function.
75    ///
76    /// The function will be called when [`Self::deliver`] is called. This type of
77    /// interrupt cannot be sent to a remote process.
78    pub fn from_fn<F>(f: F) -> Self
79    where
80        F: 'static + Send + Sync + Fn(),
81    {
82        Self {
83            inner: InterruptInner::Fn(LocalOnly(Arc::new(f))),
84        }
85    }
86
87    /// Delivers the interrupt.
88    pub fn deliver(&self) {
89        match &self.inner {
90            InterruptInner::Event(event) => event.signal(),
91            InterruptInner::Cell(cell) => cell.with(|event| event.signal()),
92            InterruptInner::Fn(LocalOnly(f)) => f(),
93        }
94    }
95
96    /// Gets a reference to the backing event, if there is one.
97    pub fn event(&self) -> Option<&Event> {
98        match &self.inner {
99            InterruptInner::Event(event) => Some(event.as_ref()),
100            _ => None,
101        }
102    }
103
104    /// Returns an event that, when signaled, will deliver this interrupt.
105    ///
106    /// If the interrupt is already event-backed, returns a clone of the
107    /// existing event and no proxy is needed. Otherwise, creates an
108    /// [`EventProxy`] that spawns an async task to bridge a new event to
109    /// [`Interrupt::deliver`]. The caller must keep the returned
110    /// `Option<EventProxy>` alive for as long as the event is in use.
111    pub fn event_or_proxy(
112        &self,
113        driver: &impl SpawnDriver,
114    ) -> std::io::Result<(Event, Option<EventProxy>)> {
115        if let Some(event) = self.event() {
116            Ok((event.clone(), None))
117        } else {
118            let (proxy, event) = EventProxy::new(driver, self.clone())?;
119            Ok((event, Some(proxy)))
120        }
121    }
122}
123
124#[derive(Clone, MeshPayload)]
125enum InterruptInner {
126    Event(Arc<Event>),
127    Cell(Arc<mesh::Cell<Event>>),
128    Fn(LocalOnly<Arc<dyn Send + Sync + Fn()>>),
129}
130
131impl Debug for InterruptInner {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        match self {
134            InterruptInner::Event(_) => f.pad("Event"),
135            InterruptInner::Cell(_) => f.pad("Cell"),
136            InterruptInner::Fn(_) => f.pad("Fn"),
137        }
138    }
139}
140
141/// An async task that bridges an [`Event`] to an [`Interrupt`].
142///
143/// When the interrupt is not directly backed by an OS event (e.g., it uses
144/// a function callback for MSI-X), this wrapper creates a new event and
145/// spawns a task that waits on it and calls [`Interrupt::deliver`]. When
146/// the `EventProxy` is dropped, the task is cancelled.
147pub struct EventProxy {
148    _task: Task<()>,
149}
150
151impl EventProxy {
152    /// Create a new proxy: returns the proxy (which owns the async task)
153    /// and the [`Event`] that the caller should pass to the consumer.
154    pub fn new(driver: &impl SpawnDriver, interrupt: Interrupt) -> std::io::Result<(Self, Event)> {
155        let event = Event::new();
156        let wait = PolledWait::new(driver, event.clone())?;
157        let task = driver.spawn("interrupt-event-proxy", async move {
158            Self::run(wait, interrupt).await;
159        });
160        Ok((Self { _task: task }, event))
161    }
162
163    async fn run(mut wait: PolledWait<Event>, interrupt: Interrupt) {
164        loop {
165            wait.wait().await.expect("wait should not fail");
166            interrupt.deliver();
167        }
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::Interrupt;
174    use pal_async::DefaultDriver;
175    use pal_async::async_test;
176    use std::sync::Arc;
177    use std::sync::atomic::{AtomicUsize, Ordering};
178
179    #[test]
180    fn test_interrupt_event() {
181        let event = pal_event::Event::new();
182        let interrupt = Interrupt::from_event(event.clone());
183        interrupt.deliver();
184        assert!(event.try_wait());
185    }
186
187    #[async_test]
188    async fn test_interrupt_cell() {
189        let mut event = pal_event::Event::new();
190        let (mut updater, cell) = mesh::cell(event.clone());
191        let interrupt = Interrupt::from_cell(cell);
192        interrupt.deliver();
193        assert!(event.try_wait());
194        event = pal_event::Event::new();
195        interrupt.deliver();
196        assert!(!event.try_wait());
197        updater.set(event.clone()).await;
198        interrupt.deliver();
199        assert!(event.try_wait());
200    }
201
202    #[async_test]
203    async fn test_event_or_proxy_event_backed(driver: DefaultDriver) {
204        let orig_event = pal_event::Event::new();
205        let interrupt = Interrupt::from_event(orig_event.clone());
206        let (event, proxy) = interrupt.event_or_proxy(&driver).unwrap();
207        // Event-backed interrupt should return the same event and no proxy.
208        assert!(proxy.is_none());
209        event.signal();
210        assert!(orig_event.try_wait());
211    }
212
213    #[async_test]
214    async fn test_event_or_proxy_fn_backed(driver: DefaultDriver) {
215        let count = Arc::new(AtomicUsize::new(0));
216        let count2 = count.clone();
217        let interrupt = Interrupt::from_fn(move || {
218            count2.fetch_add(1, Ordering::SeqCst);
219        });
220        let (event, proxy) = interrupt.event_or_proxy(&driver).unwrap();
221        // Fn-backed interrupt requires a proxy.
222        assert!(proxy.is_some());
223        // Signal the proxy event and give the async task a moment to deliver.
224        event.signal();
225        // Poll until the proxy task delivers the interrupt.
226        for _ in 0..100 {
227            if count.load(Ordering::SeqCst) > 0 {
228                break;
229            }
230            pal_async::timer::PolledTimer::new(&driver)
231                .sleep(std::time::Duration::from_millis(10))
232                .await;
233        }
234        assert_eq!(count.load(Ordering::SeqCst), 1);
235    }
236}