vmcore/
vm_task.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Infrastructure for spawning tasks and issuing async IO related to VM
5//! activity.
6
7#![warn(missing_docs)]
8
9use inspect::Inspect;
10use pal_async::driver::Driver;
11use pal_async::task::Spawn;
12use pal_async::task::TaskMetadata;
13use std::fmt::Debug;
14use std::pin::Pin;
15use std::sync::Arc;
16
17/// A source for [`VmTaskDriver`]s.
18///
19/// This is used to create device-specific drivers that implement [`Driver`] and
20/// [`Spawn`]. These drivers' behavior can be customized based on the needs of
21/// the device.
22///
23/// The backend for these drivers is customizable for different environments.
24#[derive(Clone)]
25pub struct VmTaskDriverSource {
26    backend: Arc<dyn DynVmBackend>,
27}
28
29impl VmTaskDriverSource {
30    /// Returns a new task driver source backed by `backend`.
31    pub fn new(backend: impl 'static + BuildVmTaskDriver) -> Self {
32        Self {
33            backend: Arc::new(backend),
34        }
35    }
36
37    /// Returns a VM task driver with default parameters.
38    ///
39    /// Use this when you don't care where your task runs.
40    pub fn simple(&self) -> VmTaskDriver {
41        // Don't provide a name, since backends won't do anything with it for
42        // default settings.
43        self.builder().build("")
44    }
45
46    /// Returns a builder for a custom VM task driver.
47    pub fn builder(&self) -> VmTaskDriverBuilder<'_> {
48        VmTaskDriverBuilder {
49            backend: self.backend.as_ref(),
50            run_on_target: false,
51            target_vp: None,
52        }
53    }
54}
55
56/// Trait implemented by backends for [`VmTaskDriverSource`].
57pub trait BuildVmTaskDriver: Send + Sync {
58    /// The associated driver type.
59    type Driver: TargetedDriver;
60
61    /// Builds a new driver that can drive IO and spawn tasks.
62    fn build(&self, name: String, target_vp: Option<u32>, run_on_target: bool) -> Self::Driver;
63}
64
65/// Trait implemented by drivers built with [`BuildVmTaskDriver`].
66pub trait TargetedDriver: 'static + Send + Sync + Inspect {
67    /// Returns the implementation to use for spawning tasks.
68    fn spawner(&self) -> &dyn Spawn;
69    /// Returns the implementation to use for driving IO.
70    fn driver(&self) -> &dyn Driver;
71    /// Retargets the driver to the specified virtual processor.
72    fn retarget_vp(&self, target_vp: u32);
73    /// Returns whether a driver's target VP is ready for tasks and IO.
74    ///
75    /// A driver must be operable even if this is false, but the tasks and IO
76    /// may run on a different target VP.
77    fn is_target_vp_ready(&self) -> bool {
78        true
79    }
80    /// Waits for this driver's target VP to be ready for tasks and IO.
81    fn wait_target_vp_ready(&self) -> impl std::future::Future<Output = ()> + Send {
82        std::future::ready(())
83    }
84}
85
86trait DynTargetedDriver: 'static + Send + Sync + Inspect {
87    fn spawner(&self) -> &dyn Spawn;
88    fn driver(&self) -> &dyn Driver;
89    fn retarget_vp(&self, target_vp: u32);
90    fn is_ready(&self) -> bool;
91    fn wait_ready(&self) -> Pin<Box<dyn '_ + std::future::Future<Output = ()> + Send>>;
92}
93
94impl<T: TargetedDriver> DynTargetedDriver for T {
95    fn spawner(&self) -> &dyn Spawn {
96        self.spawner()
97    }
98
99    fn driver(&self) -> &dyn Driver {
100        self.driver()
101    }
102
103    fn retarget_vp(&self, target_vp: u32) {
104        self.retarget_vp(target_vp)
105    }
106
107    fn is_ready(&self) -> bool {
108        self.is_target_vp_ready()
109    }
110
111    fn wait_ready(&self) -> Pin<Box<dyn '_ + std::future::Future<Output = ()> + Send>> {
112        Box::pin(self.wait_target_vp_ready())
113    }
114}
115
116trait DynVmBackend: Send + Sync {
117    fn build(
118        &self,
119        name: String,
120        target_vp: Option<u32>,
121        run_on_target: bool,
122    ) -> Arc<dyn DynTargetedDriver>;
123}
124
125impl<T: BuildVmTaskDriver> DynVmBackend for T {
126    fn build(
127        &self,
128        name: String,
129        target_vp: Option<u32>,
130        run_on_target: bool,
131    ) -> Arc<dyn DynTargetedDriver> {
132        Arc::new(self.build(name, target_vp, run_on_target))
133    }
134}
135
136/// A builder returned by [`VmTaskDriverSource::builder`].
137pub struct VmTaskDriverBuilder<'a> {
138    backend: &'a dyn DynVmBackend,
139    run_on_target: bool,
140    target_vp: Option<u32>,
141}
142
143impl VmTaskDriverBuilder<'_> {
144    /// A hint to the backend specifies whether the driver should spawned tasks
145    /// that always on a thread handling the target VP.
146    ///
147    /// If `false` (the default), then when spawned tasks are awoken, they may
148    /// run on any executor (such as the current one). If `true`, the backend
149    /// will run them on the same thread that would drive async IO.
150    ///
151    /// Some devices will want to override the default to reduce jitter or
152    /// ensure that IO is issued from the correct processor.
153    pub fn run_on_target(&mut self, run_on_target: bool) -> &mut Self {
154        self.run_on_target = run_on_target;
155        self
156    }
157
158    /// A hint to the backend specifying the guest VP associated with spawned
159    /// tasks and IO.
160    ///
161    /// Backends can use this to ensure that spawned tasks and async IO will run
162    /// near or on the target VP.
163    pub fn target_vp(&mut self, target_vp: u32) -> &mut Self {
164        self.target_vp = Some(target_vp);
165        self
166    }
167
168    /// Builds a VM task driver.
169    ///
170    /// `name` is used by some backends to identify a spawned thread. It is
171    /// ignored by other backends.
172    pub fn build(&self, name: impl Into<String>) -> VmTaskDriver {
173        VmTaskDriver {
174            inner: self
175                .backend
176                .build(name.into(), self.target_vp, self.run_on_target),
177        }
178    }
179}
180
181/// A driver returned by [`VmTaskDriverSource`].
182///
183/// This can be used to spawn tasks (via [`Spawn`]) and issue async IO (via [`Driver`]).
184#[derive(Clone, Inspect)]
185pub struct VmTaskDriver {
186    #[inspect(flatten)]
187    inner: Arc<dyn DynTargetedDriver>,
188}
189
190impl VmTaskDriver {
191    /// Updates the target VP for the task.
192    pub fn retarget_vp(&self, target_vp: u32) {
193        self.inner.retarget_vp(target_vp)
194    }
195
196    /// Returns whether the target VP is ready for tasks and IO.
197    ///
198    /// A driver must be operable even if this is false, but the tasks and IO
199    /// may run on a different target VP.
200    pub fn is_target_vp_ready(&self) -> bool {
201        self.inner.is_ready()
202    }
203
204    /// Waits for the target VP to be ready for tasks and IO.
205    pub async fn wait_target_vp_ready(&self) {
206        self.inner.wait_ready().await
207    }
208}
209
210impl Driver for VmTaskDriver {
211    fn new_dyn_timer(&self) -> pal_async::driver::PollImpl<dyn pal_async::timer::PollTimer> {
212        self.inner.driver().new_dyn_timer()
213    }
214
215    #[cfg(unix)]
216    fn new_dyn_fd_ready(
217        &self,
218        fd: std::os::fd::RawFd,
219    ) -> std::io::Result<pal_async::driver::PollImpl<dyn pal_async::fd::PollFdReady>> {
220        self.inner.driver().new_dyn_fd_ready(fd)
221    }
222
223    #[cfg(unix)]
224    fn new_dyn_socket_ready(
225        &self,
226        socket: std::os::fd::RawFd,
227    ) -> std::io::Result<pal_async::driver::PollImpl<dyn pal_async::socket::PollSocketReady>> {
228        self.inner.driver().new_dyn_socket_ready(socket)
229    }
230
231    #[cfg(windows)]
232    fn new_dyn_socket_ready(
233        &self,
234        socket: std::os::windows::io::RawSocket,
235    ) -> std::io::Result<pal_async::driver::PollImpl<dyn pal_async::socket::PollSocketReady>> {
236        self.inner.driver().new_dyn_socket_ready(socket)
237    }
238
239    #[cfg(unix)]
240    fn new_dyn_wait(
241        &self,
242        fd: std::os::fd::RawFd,
243        read_size: usize,
244    ) -> std::io::Result<pal_async::driver::PollImpl<dyn pal_async::wait::PollWait>> {
245        self.inner.driver().new_dyn_wait(fd, read_size)
246    }
247
248    #[cfg(windows)]
249    fn new_dyn_wait(
250        &self,
251        handle: std::os::windows::io::RawHandle,
252    ) -> std::io::Result<pal_async::driver::PollImpl<dyn pal_async::wait::PollWait>> {
253        self.inner.driver().new_dyn_wait(handle)
254    }
255
256    #[cfg(windows)]
257    unsafe fn new_dyn_overlapped_file(
258        &self,
259        handle: std::os::windows::io::RawHandle,
260    ) -> std::io::Result<
261        pal_async::driver::PollImpl<dyn pal_async::windows::overlapped::IoOverlapped>,
262    > {
263        // SAFETY: passthru from caller
264        unsafe { self.inner.driver().new_dyn_overlapped_file(handle) }
265    }
266}
267
268impl Spawn for VmTaskDriver {
269    fn scheduler(&self, metadata: &TaskMetadata) -> Arc<dyn pal_async::task::Schedule> {
270        self.inner.spawner().scheduler(metadata)
271    }
272}
273
274/// A backend that spawns all tasks and IO on a single driver.
275#[derive(Debug)]
276pub struct SingleDriverBackend<T>(T);
277
278impl<T: Driver + Spawn + Clone> SingleDriverBackend<T> {
279    /// Returns a new driver backend that spawns all tasks and IO on `driver`,
280    /// regardless of policy.
281    pub fn new(driver: T) -> Self {
282        Self(driver)
283    }
284}
285
286/// The driver for [`SingleDriverBackend`].
287#[derive(Debug)]
288pub struct SingleDriver<T>(T);
289
290impl<T> Inspect for SingleDriver<T> {
291    fn inspect(&self, req: inspect::Request<'_>) {
292        req.ignore();
293    }
294}
295
296impl<T: Driver + Spawn + Clone> BuildVmTaskDriver for SingleDriverBackend<T> {
297    type Driver = SingleDriver<T>;
298
299    fn build(&self, _name: String, _target_vp: Option<u32>, _run_on_target: bool) -> Self::Driver {
300        SingleDriver(self.0.clone())
301    }
302}
303
304impl<T: Driver + Spawn> TargetedDriver for SingleDriver<T> {
305    fn spawner(&self) -> &dyn Spawn {
306        &self.0
307    }
308
309    fn driver(&self) -> &dyn Driver {
310        &self.0
311    }
312
313    fn retarget_vp(&self, _target_vp: u32) {}
314}
315
316pub mod thread {
317    //! Provides a thread-based task VM task driver backend
318    //! [`ThreadDriverBackend`].
319
320    use super::BuildVmTaskDriver;
321    use super::TargetedDriver;
322    use inspect::Inspect;
323    use pal_async::DefaultDriver;
324    use pal_async::DefaultPool;
325    use pal_async::driver::Driver;
326    use pal_async::task::Spawn;
327
328    /// A backend for [`VmTaskDriverSource`](super::VmTaskDriverSource) based on
329    /// individual threads.
330    ///
331    /// If no target VP is specified, this backend will spawn tasks and IO a
332    /// default single-threaded IO driver. If a target VP is specified, the
333    /// backend will spawn a separate thread and spawn tasks and IOs there.
334    #[derive(Debug)]
335    pub struct ThreadDriverBackend {
336        default_driver: DefaultDriver,
337    }
338
339    impl ThreadDriverBackend {
340        /// Returns a new backend, using `default_driver` to back task drivers
341        /// that did not specify a target VP.
342        pub fn new(default_driver: DefaultDriver) -> Self {
343            Self { default_driver }
344        }
345    }
346
347    impl BuildVmTaskDriver for ThreadDriverBackend {
348        type Driver = ThreadDriver;
349
350        fn build(
351            &self,
352            name: String,
353            target_vp: Option<u32>,
354            _run_on_target: bool,
355        ) -> Self::Driver {
356            // Build a standalone thread for this device if a target VP was specified.
357            if target_vp.is_some() {
358                let (_, driver) = DefaultPool::spawn_on_thread(name);
359                ThreadDriver {
360                    inner: driver,
361                    has_dedicated_thread: true,
362                }
363            } else {
364                ThreadDriver {
365                    inner: self.default_driver.clone(),
366                    has_dedicated_thread: false,
367                }
368            }
369        }
370    }
371
372    /// The driver for [`ThreadDriverBackend`].
373    #[derive(Debug, Inspect)]
374    pub struct ThreadDriver {
375        #[inspect(skip)]
376        inner: DefaultDriver,
377        has_dedicated_thread: bool,
378    }
379
380    impl TargetedDriver for ThreadDriver {
381        fn spawner(&self) -> &dyn Spawn {
382            &self.inner
383        }
384
385        fn driver(&self) -> &dyn Driver {
386            &self.inner
387        }
388
389        fn retarget_vp(&self, _target_vp: u32) {}
390    }
391}