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}