Skip to main content

virtio/
device.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Per-queue virtio device trait (`VirtioDevice`) and object-safe wrapper
5//! (`DynVirtioDevice`).
6
7use crate::DEFAULT_QUEUE_SIZE;
8use crate::DeviceTraits;
9use crate::QueueResources;
10use crate::queue::QueueState;
11use crate::spec::VirtioDeviceFeatures;
12use guestmem::MappedMemoryRegion;
13use inspect::InspectMut;
14use std::future::Future;
15use std::pin::Pin;
16use std::sync::Arc;
17
18/// Per-queue virtio device trait. Ergonomic async fn — not object-safe.
19///
20/// Devices implement this trait. The blanket impl converts any
21/// `VirtioDevice` into a `DynVirtioDevice` for use behind `Box<dyn>`.
22pub trait VirtioDevice: InspectMut + Send {
23    /// Device identity and capabilities.
24    fn traits(&self) -> DeviceTraits;
25
26    /// The queue size for the given queue index.
27    ///
28    /// This is the initial value the transport advertises to the guest
29    /// (e.g. via `QUEUE_NUM_MAX` on MMIO, or `QUEUE_SIZE` on PCI). The
30    /// transport does not enforce this as a per-device cap; the only hard
31    /// limit is [`crate::MAX_QUEUE_SIZE`].
32    ///
33    /// Must be a power of two, >0, and ≤ [`crate::MAX_QUEUE_SIZE`]. The
34    /// transport validates these invariants at construction time.
35    ///
36    /// `queue_index` must be less than `traits().max_queues`. The caller
37    /// is responsible for bounds checking; implementations may panic on
38    /// out-of-range indices.
39    ///
40    /// Override to provide per-device or per-queue sizes. The default
41    /// returns [`DEFAULT_QUEUE_SIZE`] (256).
42    fn queue_size(&self, _queue_index: u16) -> u16 {
43        DEFAULT_QUEUE_SIZE
44    }
45
46    /// Read device-specific config registers.
47    fn read_registers_u32(&mut self, offset: u16) -> impl Future<Output = u32> + Send;
48
49    /// Write device-specific config registers.
50    fn write_registers_u32(&mut self, offset: u16, val: u32) -> impl Future<Output = ()> + Send;
51
52    /// Provide the shared memory region to the device.
53    ///
54    /// Called before `start_queue` when the device advertises a shared
55    /// memory region (e.g., virtio-pmem, virtio-fs with DAX). Corresponds
56    /// to `VHOST_USER_GET_SHARED_MEMORY_REGIONS` in the vhost-user protocol.
57    ///
58    /// Default: no-op.
59    fn set_shared_memory_region(
60        &mut self,
61        _region: &Arc<dyn MappedMemoryRegion>,
62    ) -> anyhow::Result<()> {
63        Ok(())
64    }
65
66    /// Start a single queue.
67    ///
68    /// Called when a queue becomes active — either because the guest set
69    /// DRIVER_OK (transport starts all enabled queues), or a vhost-user
70    /// frontend activated a specific queue.
71    ///
72    /// `idx` is in `0..DeviceTraits::max_queues`. The caller will never
73    /// pass an index outside that range.
74    ///
75    /// `initial_state` provides restored queue indices for save/restore
76    /// or vhost-user `SET_VRING_BASE`. If `None`, the queue starts fresh
77    /// (indices at 0).
78    fn start_queue(
79        &mut self,
80        idx: u16,
81        resources: QueueResources,
82        features: &VirtioDeviceFeatures,
83        initial_state: Option<QueueState>,
84    ) -> impl Future<Output = anyhow::Result<()>> + Send;
85
86    /// Stop a single queue and return its state.
87    ///
88    /// `idx` is in `0..DeviceTraits::max_queues`. The caller will never
89    /// pass an index outside that range.
90    ///
91    /// Returns the queue's `QueueState` on completion, or `None` if the
92    /// queue was not active.
93    ///
94    /// This must be idempotent: calling it on a queue that was never
95    /// started (or has already been stopped) must return `None`
96    /// immediately. Transports rely on this during reset/disable by
97    /// iterating all queue indices, not just active ones.
98    fn stop_queue(&mut self, idx: u16) -> impl Future<Output = Option<QueueState>> + Send;
99
100    /// Reset device-internal state to initial values.
101    ///
102    /// Called after all queues have been stopped on guest-initiated reset.
103    /// Default: no-op.
104    fn reset(&mut self) -> impl Future<Output = ()> + Send {
105        async {}
106    }
107
108    /// Whether the device supports save/restore.
109    ///
110    /// Devices that return `false` will cause the transport's `save()` to
111    /// fail with `SaveError::NotSupported`. Devices with host-side session
112    /// state that cannot be serialized (e.g., virtio-9p, virtiofs) should
113    /// leave this as `false`.
114    fn supports_save_restore(&self) -> bool {
115        false
116    }
117}
118
119/// Object-safe wrapper for [`VirtioDevice`].
120///
121/// Uses boxed futures instead of `async fn` for object safety. The blanket
122/// impl converts any `T: VirtioDevice` into a `DynVirtioDevice`.
123///
124/// The device task, backend server, and resolver hold `Box<dyn DynVirtioDevice>`.
125pub trait DynVirtioDevice: InspectMut + Send {
126    /// Device identity and capabilities.
127    fn traits(&self) -> DeviceTraits;
128
129    /// The queue size for the given queue index.
130    fn queue_size(&self, queue_index: u16) -> u16;
131
132    /// Read device-specific config registers.
133    fn read_registers_u32(&mut self, offset: u16)
134    -> Pin<Box<dyn Future<Output = u32> + Send + '_>>;
135
136    /// Write device-specific config registers.
137    fn write_registers_u32(
138        &mut self,
139        offset: u16,
140        val: u32,
141    ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
142
143    /// Provide the shared memory region to the device.
144    fn set_shared_memory_region(
145        &mut self,
146        region: &Arc<dyn MappedMemoryRegion>,
147    ) -> anyhow::Result<()>;
148
149    /// Start a single queue.
150    fn start_queue<'a>(
151        &'a mut self,
152        idx: u16,
153        resources: QueueResources,
154        features: &'a VirtioDeviceFeatures,
155        initial_state: Option<QueueState>,
156    ) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + 'a>>;
157
158    /// Stop a single queue and return its state.
159    fn stop_queue(
160        &mut self,
161        idx: u16,
162    ) -> Pin<Box<dyn Future<Output = Option<QueueState>> + Send + '_>>;
163
164    /// Reset device-internal state.
165    fn reset(&mut self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
166
167    /// Whether the device supports save/restore.
168    fn supports_save_restore(&self) -> bool;
169}
170
171impl<T: VirtioDevice> DynVirtioDevice for T {
172    fn traits(&self) -> DeviceTraits {
173        VirtioDevice::traits(self)
174    }
175
176    fn queue_size(&self, queue_index: u16) -> u16 {
177        VirtioDevice::queue_size(self, queue_index)
178    }
179
180    fn read_registers_u32(
181        &mut self,
182        offset: u16,
183    ) -> Pin<Box<dyn Future<Output = u32> + Send + '_>> {
184        Box::pin(VirtioDevice::read_registers_u32(self, offset))
185    }
186
187    fn write_registers_u32(
188        &mut self,
189        offset: u16,
190        val: u32,
191    ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
192        Box::pin(VirtioDevice::write_registers_u32(self, offset, val))
193    }
194
195    fn set_shared_memory_region(
196        &mut self,
197        region: &Arc<dyn MappedMemoryRegion>,
198    ) -> anyhow::Result<()> {
199        VirtioDevice::set_shared_memory_region(self, region)
200    }
201
202    fn start_queue<'a>(
203        &'a mut self,
204        idx: u16,
205        resources: QueueResources,
206        features: &'a VirtioDeviceFeatures,
207        initial_state: Option<QueueState>,
208    ) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + 'a>> {
209        Box::pin(VirtioDevice::start_queue(
210            self,
211            idx,
212            resources,
213            features,
214            initial_state,
215        ))
216    }
217
218    fn stop_queue(
219        &mut self,
220        idx: u16,
221    ) -> Pin<Box<dyn Future<Output = Option<QueueState>> + Send + '_>> {
222        Box::pin(VirtioDevice::stop_queue(self, idx))
223    }
224
225    fn reset(&mut self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
226        Box::pin(VirtioDevice::reset(self))
227    }
228
229    fn supports_save_restore(&self) -> bool {
230        VirtioDevice::supports_save_restore(self)
231    }
232}