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 mesh::MeshPayload;
9use mesh::payload::DefaultEncoding;
10use mesh::payload::FieldDecode;
11use mesh::payload::FieldEncode;
12use mesh::payload::inplace::InplaceOption;
13use mesh::resource::Resource;
14use pal_async::driver::SpawnDriver;
15use pal_async::task::Task;
16use pal_async::wait::PolledWait;
17use pal_event::Event;
18use std::fmt::Debug;
19use std::sync::Arc;
20use std::sync::OnceLock;
21
22/// An object representing an interrupt-like signal to notify the guest of
23/// device activity.
24///
25/// This is generally an edge-triggered interrupt, but it could also be a synic
26/// event or similar notification.
27///
28/// The interrupt can be backed by a [`pal_event::Event`] or a function. In the
29/// former case, the `Interrupt` can be sent across a mesh channel to remote
30/// processes.
31#[derive(Clone, Debug, MeshPayload)]
32pub struct Interrupt {
33    #[mesh(encoding = "InterruptEncoding")]
34    inner: Arc<InterruptInner>,
35}
36
37impl Default for Interrupt {
38    fn default() -> Self {
39        Self::null()
40    }
41}
42
43impl Interrupt {
44    /// An interrupt that does nothing.
45    ///
46    /// Note that [`Self::event`] will still return a valid event, which will be
47    /// lazily created on demand. This allows the interrupt to be used with APIs
48    /// that require an event, without actually delivering any notifications.
49    pub fn null() -> Self {
50        Self::from_target(NullEventTarget)
51    }
52
53    /// Creates an interrupt from an event.
54    ///
55    /// The event will be signaled when [`Self::deliver`] is called.
56    pub fn from_event(event: Event) -> Self {
57        let event = Arc::new(event);
58        Self {
59            inner: Arc::new(InterruptInner {
60                event: OnceLock::from(Some(event.clone())),
61                t: EventTarget(event),
62            }),
63        }
64    }
65
66    /// Creates an interrupt from a function.
67    ///
68    /// The function will be called when [`Self::deliver`] is called. This type of
69    /// interrupt cannot be sent to a remote process.
70    pub fn from_fn<F>(f: F) -> Self
71    where
72        F: 'static + Send + Sync + Fn(),
73    {
74        Self {
75            inner: Arc::new(InterruptInner {
76                event: OnceLock::new(),
77                t: FnTarget(f),
78            }),
79        }
80    }
81
82    /// Creates an interrupt from an [`InterruptTarget`] implementation.
83    pub fn from_target(target: impl InterruptTarget + 'static) -> Self {
84        Self {
85            inner: Arc::new(InterruptInner {
86                event: OnceLock::new(),
87                t: target,
88            }),
89        }
90    }
91
92    /// Delivers the interrupt.
93    pub fn deliver(&self) {
94        self.inner.t.deliver();
95    }
96
97    /// Gets a reference to the backing event, if there is one.
98    ///
99    /// This will attempt to lazily create the event via the target if one
100    /// has not already been cached.
101    pub fn event(&self) -> Option<&Event> {
102        self.inner.event().as_deref()
103    }
104
105    /// Returns an event that, when signaled, will deliver this interrupt.
106    ///
107    /// If [`Self::event`] returns an event, returns a clone of it and no
108    /// proxy is needed. Otherwise, creates an [`EventProxy`] that spawns an
109    /// async task to bridge a new event to [`Interrupt::deliver`]. The caller
110    /// must keep the returned `Option<EventProxy>` alive for as long as the
111    /// event is in use.
112    pub fn event_or_proxy(
113        &self,
114        driver: &impl SpawnDriver,
115    ) -> std::io::Result<(Event, Option<EventProxy>)> {
116        if let Some(event) = self.event() {
117            Ok((event.clone(), None))
118        } else {
119            let (proxy, event) = EventProxy::new(driver, self.clone())?;
120            Ok((event, Some(proxy)))
121        }
122    }
123}
124
125/// A trait for implementing interrupt delivery.
126///
127/// Interrupt targets provide the core behavior for delivering interrupts
128/// and optionally providing a backing OS event.
129pub trait InterruptTarget: Send + Sync {
130    /// Deliver the interrupt.
131    fn deliver(&self);
132
133    /// Called to lazily create an event-backed delivery path for this
134    /// interrupt. If the implementation can provide an event (e.g., by
135    /// allocating an irqfd route), it should do so here and return it.
136    ///
137    /// This will be called at most once per interrupt; the result is cached.
138    fn event(&self) -> Option<Arc<Event>> {
139        None
140    }
141}
142
143struct InterruptInner<T: ?Sized = dyn InterruptTarget> {
144    event: OnceLock<Option<Arc<Event>>>,
145    t: T,
146}
147
148impl Debug for InterruptInner {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        f.pad("InterruptInner")
151    }
152}
153
154impl InterruptInner {
155    fn event(&self) -> &Option<Arc<Event>> {
156        self.event.get_or_init(|| self.t.event())
157    }
158}
159
160/// Trivial target that wraps an event.
161struct EventTarget(Arc<Event>);
162
163impl InterruptTarget for EventTarget {
164    fn deliver(&self) {
165        self.0.signal();
166    }
167
168    fn event(&self) -> Option<Arc<Event>> {
169        Some(self.0.clone())
170    }
171}
172
173/// Target that wraps a function callback.
174struct FnTarget<F>(F);
175
176impl<F: Send + Sync + Fn()> InterruptTarget for FnTarget<F> {
177    fn deliver(&self) {
178        (self.0)()
179    }
180}
181
182/// Target for null interrupts that lazily creates an event on demand.
183struct NullEventTarget;
184
185impl InterruptTarget for NullEventTarget {
186    fn deliver(&self) {}
187
188    fn event(&self) -> Option<Arc<Event>> {
189        Some(Arc::new(Event::new()))
190    }
191}
192
193struct InterruptEncoding;
194
195type EventFieldEncoding = <Event as DefaultEncoding>::Encoding;
196
197impl FieldEncode<Arc<InterruptInner>, Resource> for InterruptEncoding {
198    fn write_field(
199        item: Arc<InterruptInner>,
200        writer: mesh::payload::protobuf::FieldWriter<'_, '_, Resource>,
201    ) {
202        if let Some(event) = item.event() {
203            EventFieldEncoding::write_field_in_sequence((**event).clone(), &mut writer.sequence());
204        } else {
205            tracing::warn!("encoding local-only interrupt");
206        }
207    }
208
209    fn compute_field_size(
210        item: &mut Arc<InterruptInner>,
211        sizer: mesh::payload::protobuf::FieldSizer<'_>,
212    ) {
213        if item.event().is_some() {
214            sizer.sequence().field().resource();
215        }
216    }
217
218    fn wrap_in_sequence() -> bool {
219        true
220    }
221}
222
223impl FieldDecode<'_, Arc<InterruptInner>, Resource> for InterruptEncoding {
224    fn read_field(
225        item: &mut InplaceOption<'_, Arc<InterruptInner>>,
226        reader: mesh::payload::protobuf::FieldReader<'_, '_, Resource>,
227    ) -> mesh::payload::Result<()> {
228        mesh::payload::inplace_none!(event: Event);
229        EventFieldEncoding::read_field_in_sequence(&mut event, reader)?;
230        let event = Arc::new(event.take().unwrap());
231        item.set(Arc::new(InterruptInner {
232            event: OnceLock::from(Some(event.clone())),
233            t: EventTarget(event),
234        }));
235        Ok(())
236    }
237
238    fn default_field(
239        _item: &mut InplaceOption<'_, Arc<InterruptInner>>,
240    ) -> mesh::payload::Result<()> {
241        Err(mesh::payload::Error::new(
242            "missing event in serialized interrupt",
243        ))
244    }
245
246    fn wrap_in_sequence() -> bool {
247        true
248    }
249}
250
251/// An async task that bridges an [`Event`] to an [`Interrupt`].
252///
253/// When the interrupt is not directly backed by an OS event (e.g., it uses
254/// a function callback for MSI-X), this wrapper creates a new event and
255/// spawns a task that waits on it and calls [`Interrupt::deliver`]. When
256/// the `EventProxy` is dropped, the task is cancelled.
257pub struct EventProxy {
258    _task: Task<()>,
259}
260
261impl EventProxy {
262    /// Create a new proxy: returns the proxy (which owns the async task)
263    /// and the [`Event`] that the caller should pass to the consumer.
264    pub fn new(driver: &impl SpawnDriver, interrupt: Interrupt) -> std::io::Result<(Self, Event)> {
265        let event = Event::new();
266        let wait = PolledWait::new(driver, event.clone())?;
267        let task = driver.spawn("interrupt-event-proxy", async move {
268            Self::run(wait, interrupt).await;
269        });
270        Ok((Self { _task: task }, event))
271    }
272
273    async fn run(mut wait: PolledWait<Event>, interrupt: Interrupt) {
274        loop {
275            wait.wait().await.expect("wait should not fail");
276            interrupt.deliver();
277        }
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use super::Interrupt;
284    use super::InterruptTarget;
285    use pal_async::DefaultDriver;
286    use pal_async::async_test;
287    use pal_event::Event;
288    use std::sync::Arc;
289    use std::sync::atomic::{AtomicUsize, Ordering};
290
291    #[test]
292    fn test_interrupt_event() {
293        let event = Event::new();
294        let interrupt = Interrupt::from_event(event.clone());
295        interrupt.deliver();
296        assert!(event.try_wait());
297    }
298
299    #[test]
300    fn test_interrupt_fn() {
301        let count = Arc::new(AtomicUsize::new(0));
302        let count2 = count.clone();
303        let interrupt = Interrupt::from_fn(move || {
304            count2.fetch_add(1, Ordering::SeqCst);
305        });
306        interrupt.deliver();
307        interrupt.deliver();
308        assert_eq!(count.load(Ordering::SeqCst), 2);
309    }
310
311    #[test]
312    fn test_interrupt_null_does_not_signal() {
313        let interrupt = Interrupt::null();
314        // deliver() should not panic on a null interrupt.
315        interrupt.deliver();
316    }
317
318    #[test]
319    fn test_event_backed_has_event() {
320        let event = Event::new();
321        let interrupt = Interrupt::from_event(event.clone());
322        assert!(interrupt.event().is_some());
323    }
324
325    #[test]
326    fn test_fn_backed_has_no_event() {
327        let interrupt = Interrupt::from_fn(|| {});
328        assert!(interrupt.event().is_none());
329    }
330
331    #[test]
332    fn test_null_has_event() {
333        let interrupt = Interrupt::null();
334        // Null interrupts lazily provide an event for APIs that require one.
335        assert!(interrupt.event().is_some());
336    }
337
338    #[test]
339    fn test_null_event_is_stable() {
340        let interrupt = Interrupt::null();
341        let e1 = std::ptr::from_ref::<Event>(interrupt.event().unwrap());
342        let e2 = std::ptr::from_ref::<Event>(interrupt.event().unwrap());
343        assert_eq!(
344            e1, e2,
345            "event() should return the same event on repeated calls"
346        );
347    }
348
349    #[test]
350    fn test_from_target() {
351        struct TestTarget {
352            count: Arc<AtomicUsize>,
353        }
354        impl InterruptTarget for TestTarget {
355            fn deliver(&self) {
356                self.count.fetch_add(1, Ordering::SeqCst);
357            }
358        }
359        let count = Arc::new(AtomicUsize::new(0));
360        let interrupt = Interrupt::from_target(TestTarget {
361            count: count.clone(),
362        });
363        interrupt.deliver();
364        assert_eq!(count.load(Ordering::SeqCst), 1);
365        assert!(interrupt.event().is_none());
366    }
367
368    #[test]
369    fn test_from_target_with_event() {
370        struct TestTarget(Arc<Event>);
371        impl InterruptTarget for TestTarget {
372            fn deliver(&self) {
373                self.0.signal();
374            }
375            fn event(&self) -> Option<Arc<Event>> {
376                Some(self.0.clone())
377            }
378        }
379        let event = Arc::new(Event::new());
380        let interrupt = Interrupt::from_target(TestTarget(event.clone()));
381        assert!(interrupt.event().is_some());
382        interrupt.deliver();
383        assert!(event.try_wait());
384    }
385
386    #[test]
387    fn test_clone_shares_state() {
388        let count = Arc::new(AtomicUsize::new(0));
389        let count2 = count.clone();
390        let interrupt = Interrupt::from_fn(move || {
391            count2.fetch_add(1, Ordering::SeqCst);
392        });
393        let cloned = interrupt.clone();
394        interrupt.deliver();
395        cloned.deliver();
396        assert_eq!(count.load(Ordering::SeqCst), 2);
397    }
398
399    #[test]
400    fn test_default_is_null() {
401        let interrupt = Interrupt::default();
402        // Should behave like null: deliver doesn't panic, event is available.
403        interrupt.deliver();
404        assert!(interrupt.event().is_some());
405    }
406
407    #[test]
408    fn test_mesh_round_trip_event_backed() {
409        let event = Event::new();
410        let interrupt = Interrupt::from_event(event);
411        let msg = mesh::payload::SerializedMessage::from_message(interrupt);
412        let decoded: Interrupt = msg.into_message().unwrap();
413        // The decoded interrupt should be event-backed.
414        assert!(decoded.event().is_some());
415        decoded.deliver();
416    }
417
418    #[async_test]
419    async fn test_event_or_proxy_event_backed(driver: DefaultDriver) {
420        let orig_event = Event::new();
421        let interrupt = Interrupt::from_event(orig_event.clone());
422        let (event, proxy) = interrupt.event_or_proxy(&driver).unwrap();
423        // Event-backed interrupt should return the same event and no proxy.
424        assert!(proxy.is_none());
425        event.signal();
426        assert!(orig_event.try_wait());
427    }
428
429    #[async_test]
430    async fn test_event_or_proxy_fn_backed(driver: DefaultDriver) {
431        let count = Arc::new(AtomicUsize::new(0));
432        let count2 = count.clone();
433        let interrupt = Interrupt::from_fn(move || {
434            count2.fetch_add(1, Ordering::SeqCst);
435        });
436        let (event, proxy) = interrupt.event_or_proxy(&driver).unwrap();
437        // Fn-backed interrupt requires a proxy.
438        assert!(proxy.is_some());
439        // Signal the proxy event and give the async task a moment to deliver.
440        event.signal();
441        // Poll until the proxy task delivers the interrupt.
442        for _ in 0..100 {
443            if count.load(Ordering::SeqCst) > 0 {
444                break;
445            }
446            pal_async::timer::PolledTimer::new(&driver)
447                .sleep(std::time::Duration::from_millis(10))
448                .await;
449        }
450        assert_eq!(count.load(Ordering::SeqCst), 1);
451    }
452}