devices/virtio/virtio_device.rs
1// Copyright 2018 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::collections::BTreeMap;
6
7#[cfg(target_arch = "x86_64")]
8use acpi_tables::sdt::SDT;
9use anyhow::anyhow;
10use anyhow::Result;
11use base::Protection;
12use base::RawDescriptor;
13use hypervisor::MemCacheType;
14use resources::AddressRange;
15use snapshot::AnySnapshot;
16use vm_control::VmMemorySource;
17use vm_memory::GuestMemory;
18
19use super::*;
20use crate::pci::MsixStatus;
21use crate::pci::PciAddress;
22use crate::pci::PciBarConfiguration;
23use crate::pci::PciCapability;
24
25/// Type of Virtio device memory mapping to use.
26pub enum SharedMemoryPrepareType {
27 /// On first attempted mapping, the entire SharedMemoryRegion is configured with declared
28 /// MemCacheType.
29 SingleMappingOnFirst(MemCacheType),
30 /// No mapping preparation is performed. each mapping is handled individually
31 DynamicPerMapping,
32}
33
34#[derive(Clone)]
35pub struct SharedMemoryRegion {
36 /// The id of the shared memory region. A device may have multiple regions, but each
37 /// must have a unique id. The meaning of a particular region is device-specific.
38 pub id: u8,
39 pub length: u64,
40}
41
42/// Trait for mapping memory into the device's shared memory region.
43pub trait SharedMemoryMapper: Send {
44 /// Maps the given |source| into the shared memory region at |offset|.
45 fn add_mapping(
46 &mut self,
47 source: VmMemorySource,
48 offset: u64,
49 prot: Protection,
50 cache: MemCacheType,
51 ) -> Result<()>;
52
53 /// Removes the mapping beginning at |offset|.
54 fn remove_mapping(&mut self, offset: u64) -> Result<()>;
55
56 fn as_raw_descriptor(&self) -> Option<RawDescriptor> {
57 None
58 }
59}
60
61/// Trait for virtio devices to be driven by a virtio transport.
62///
63/// The lifecycle of a virtio device is to be moved to a virtio transport, which will then query the
64/// device. Once the guest driver has configured the device, `VirtioDevice::activate` will be called
65/// and all the events, memory, and queues for device operation will be moved into the device.
66/// Optionally, a virtio device can implement device reset in which it returns said resources and
67/// resets its internal.
68///
69/// Virtio device state machine
70/// ```none
71/// restore (inactive)
72/// ----------------------------------------------------
73/// | |
74/// | V
75/// | ------------ --------------
76/// ------------- restore(active) | asleep | | asleep | // States in this row
77/// |asleep(new)|---------------> | (active) | | (inactive) | // can be snapshotted
78/// ------------- ------------ --------------
79/// ^ | ^ | ^ |
80/// | | | | | |
81/// sleep wake sleep wake sleep wake
82/// | | | | | |
83/// | V | V | V
84/// ------------ activate ---------- reset ------------
85/// | new | ---------------> | active | ------> | inactive |
86/// ------------ ---------- <------ ------------
87/// activate
88/// ```
89pub trait VirtioDevice: Send {
90 /// Returns a label suitable for debug output.
91 fn debug_label(&self) -> String {
92 format!("virtio-{}", self.device_type())
93 }
94
95 /// A vector of device-specific file descriptors that must be kept open
96 /// after jailing. Must be called before the process is jailed.
97 fn keep_rds(&self) -> Vec<RawDescriptor>;
98
99 /// The virtio device type.
100 fn device_type(&self) -> DeviceType;
101
102 /// The maximum size of each queue that this device supports.
103 fn queue_max_sizes(&self) -> &[u16];
104
105 /// The number of interrupts used by this device.
106 fn num_interrupts(&self) -> usize {
107 self.queue_max_sizes().len()
108 }
109
110 /// The set of feature bits that this device supports in addition to the base features.
111 fn features(&self) -> u64 {
112 0
113 }
114
115 /// Acknowledges that this set of features should be enabled.
116 fn ack_features(&mut self, value: u64) {
117 let _ = value;
118 }
119
120 /// Reads this device configuration space at `offset`.
121 fn read_config(&self, offset: u64, data: &mut [u8]) {
122 let _ = offset;
123 let _ = data;
124 }
125
126 /// Writes to this device configuration space at `offset`.
127 fn write_config(&mut self, offset: u64, data: &[u8]) {
128 let _ = offset;
129 let _ = data;
130 }
131
132 /// Activates this device for real usage.
133 fn activate(
134 &mut self,
135 mem: GuestMemory,
136 interrupt: Interrupt,
137 queues: BTreeMap<usize, Queue>,
138 ) -> Result<()>;
139
140 /// Optionally deactivates this device. If the reset method is
141 /// not able to reset the virtio device, or the virtio device model doesn't
142 /// implement the reset method, an `Err` value is returned to indicate
143 /// the reset is not successful. Otherwise `Ok(())` should be returned.
144 fn reset(&mut self) -> Result<()> {
145 Err(anyhow!("reset not implemented for {}", self.debug_label()))
146 }
147
148 /// Returns any additional BAR configuration required by the device.
149 fn get_device_bars(&mut self, _address: PciAddress) -> Vec<PciBarConfiguration> {
150 Vec::new()
151 }
152
153 /// Returns any additional capabiltiies required by the device.
154 fn get_device_caps(&self) -> Vec<Box<dyn PciCapability>> {
155 Vec::new()
156 }
157
158 /// Invoked when the device is sandboxed.
159 fn on_device_sandboxed(&mut self) {}
160
161 fn control_notify(&self, _behavior: MsixStatus) {}
162
163 #[cfg(target_arch = "x86_64")]
164 fn generate_acpi(
165 &mut self,
166 pci_address: PciAddress,
167 sdts: &mut Vec<SDT>,
168 ) -> anyhow::Result<()> {
169 let _ = pci_address;
170 let _ = sdts;
171 Ok(())
172 }
173
174 /// Returns the PCI address where the device will be allocated.
175 /// Returns `None` if any address is good for the device.
176 fn pci_address(&self) -> Option<PciAddress> {
177 None
178 }
179
180 /// Returns the device's shared memory region if present.
181 fn get_shared_memory_region(&self) -> Option<SharedMemoryRegion> {
182 None
183 }
184
185 /// If true, VFIO passthrough devices can access descriptors mapped into
186 /// this region by mapping the corresponding addresses from this device's
187 /// PCI bar into their IO address space with virtio-iommu.
188 ///
189 /// NOTE: Not all vm_control::VmMemorySource types are supported.
190 /// NOTE: Not yet compatible with PrepareSharedMemoryRegion (aka fixed mapping).
191 fn expose_shmem_descriptors_with_viommu(&self) -> bool {
192 false
193 }
194
195 /// Provides the trait object used to map files into the device's shared
196 /// memory region.
197 ///
198 /// If `get_shared_memory_region` returns `Some`, then this will be called
199 /// before `activate`.
200 fn set_shared_memory_mapper(&mut self, _mapper: Box<dyn SharedMemoryMapper>) {}
201
202 /// Provides the guest address range of the shared memory region, if one is present. Will
203 /// be called before `activate`.
204 fn set_shared_memory_region(&mut self, shmem_region: AddressRange) {
205 let _ = shmem_region;
206 }
207
208 /// Queries the implementation whether a single prepared hypervisor memory mapping with explicit
209 /// caching type should be setup lazily on first mapping request, or whether to dynamically
210 /// setup a hypervisor mapping with every request's caching type.
211 fn get_shared_memory_prepare_type(&mut self) -> SharedMemoryPrepareType {
212 // default to lazy-prepare of a single memslot with explicit caching type
213 SharedMemoryPrepareType::SingleMappingOnFirst(MemCacheType::CacheCoherent)
214 }
215
216 /// Pause all processing.
217 ///
218 /// Gives up the queues so that a higher layer can potentially snapshot them. The
219 /// implementations should also drop the `Interrupt` and queues `Event`s that were given along
220 /// with the queues originally.
221 ///
222 /// Unlike `Suspendable::sleep`, this is not idempotent. Attempting to sleep while already
223 /// asleep is an error.
224 fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
225 anyhow::bail!("virtio_sleep not implemented for {}", self.debug_label());
226 }
227
228 /// Resume all processing.
229 ///
230 /// If the device's queues are active, then the queues and associated data will is included.
231 ///
232 /// Unlike `Suspendable::wake`, this is not idempotent. Attempting to wake while already awake
233 /// is an error.
234 fn virtio_wake(
235 &mut self,
236 _queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
237 ) -> anyhow::Result<()> {
238 anyhow::bail!("virtio_wake not implemented for {}", self.debug_label());
239 }
240
241 /// Snapshot current state. Device must be asleep.
242 fn virtio_snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
243 anyhow::bail!("virtio_snapshot not implemented for {}", self.debug_label());
244 }
245
246 /// Restore device state from a snapshot.
247 /// TODO(b/280607404): Vhost user will need fds passed to the device process.
248 fn virtio_restore(&mut self, _data: AnySnapshot) -> anyhow::Result<()> {
249 anyhow::bail!("virtio_restore not implemented for {}", self.debug_label());
250 }
251
252 // Returns a tuple consisting of the non-arch specific part of the OpenFirmware path,
253 // represented as bytes, and the boot index of a device. The non-arch specific part of path for
254 // a virtio-blk device, for example, would consist of everything after the first '/' below:
255 // pci@i0cf8/scsi@6[,3]/disk@0,0
256 // ^ ^ ^ ^ ^
257 // | | | fixed
258 // | | (PCI function related to disk (optional))
259 // (x86 specf (PCI slot holding disk)
260 // root at sys
261 // bus port)
262 fn bootorder_fw_cfg(&self, _pci_address: u8) -> Option<(Vec<u8>, usize)> {
263 None
264 }
265}
266
267// General tests that should pass on all suspendables.
268// Do implement device-specific tests to validate the functionality of the device.
269// Those tests are not a replacement for regular tests. Only an extension specific to the trait's
270// basic functionality.
271/// `name` is the name of the test grouping. Can be anything unique within the same crate.
272/// `dev` is a block that returns a created virtio device.
273/// ``num_queues` is the number of queues to be created.
274/// `modfun` is the function name of the function that would modify the device. The function call
275/// should modify the device so that a snapshot taken after the function call would be different
276/// from a snapshot taken before the function call.
277#[macro_export]
278macro_rules! suspendable_virtio_tests {
279 ($name:ident, $dev: expr, $num_queues:literal, $modfun:expr) => {
280 mod $name {
281 use $crate::virtio::QueueConfig;
282
283 use super::*;
284
285 fn memory() -> GuestMemory {
286 use vm_memory::GuestAddress;
287 GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)])
288 .expect("Creating guest memory failed.")
289 }
290
291 fn interrupt() -> Interrupt {
292 Interrupt::new_for_test()
293 }
294
295 fn create_queues(
296 num_queues: usize,
297 queue_size: u16,
298 mem: &GuestMemory,
299 interrupt: Interrupt,
300 ) -> BTreeMap<usize, Queue> {
301 let mut queues = BTreeMap::new();
302 for i in 0..num_queues {
303 // activate with queues of an arbitrary size.
304 let mut queue = QueueConfig::new(queue_size, 0);
305 queue.set_ready(true);
306 let queue = queue
307 .activate(mem, base::Event::new().unwrap(), interrupt.clone())
308 .expect("QueueConfig::activate");
309 queues.insert(i, queue);
310 }
311 queues
312 }
313
314 #[test]
315 fn test_unactivated_sleep_snapshot_wake() {
316 let (_ctx, mut device) = $dev();
317 let sleep_result = device.virtio_sleep().expect("failed to sleep");
318 assert!(sleep_result.is_none());
319 device.virtio_snapshot().expect("failed to snapshot");
320 device.virtio_wake(None).expect("failed to wake");
321 }
322
323 #[test]
324 fn test_sleep_snapshot_wake() {
325 let (_ctx, mut device) = $dev();
326 let mem = memory();
327 let interrupt = interrupt();
328 let queues = create_queues(
329 $num_queues,
330 device
331 .queue_max_sizes()
332 .first()
333 .cloned()
334 .expect("missing queue size"),
335 &mem,
336 interrupt.clone(),
337 );
338 device
339 .activate(mem.clone(), interrupt.clone(), queues)
340 .expect("failed to activate");
341 let sleep_result = device
342 .virtio_sleep()
343 .expect("failed to sleep")
344 .expect("missing queues while sleeping");
345 device.virtio_snapshot().expect("failed to snapshot");
346 device
347 .virtio_wake(Some((mem.clone(), interrupt.clone(), sleep_result)))
348 .expect("failed to wake");
349 }
350
351 #[test]
352 fn test_suspend_mod_restore() {
353 let (mut context, mut device) = $dev();
354 let mem = memory();
355 let interrupt = interrupt();
356 let queues = create_queues(
357 $num_queues,
358 device
359 .queue_max_sizes()
360 .first()
361 .cloned()
362 .expect("missing queue size"),
363 &mem,
364 interrupt.clone(),
365 );
366 device
367 .activate(mem.clone(), interrupt.clone(), queues)
368 .expect("failed to activate");
369 let sleep_result = device
370 .virtio_sleep()
371 .expect("failed to sleep")
372 .expect("missing queues while sleeping");
373 // Modify device before snapshotting.
374 $modfun(&mut context, &mut device);
375 let snap = device
376 .virtio_snapshot()
377 .expect("failed to take initial snapshot");
378 device
379 .virtio_wake(Some((mem.clone(), interrupt.clone(), sleep_result)))
380 .expect("failed to wake");
381
382 // Create a new device to restore the previously taken snapshot
383 let (_ctx2, mut device) = $dev();
384 // Sleep the device before restore
385 assert!(device.virtio_sleep().expect("failed to sleep").is_none());
386 device
387 .virtio_restore(snap.clone())
388 .expect("failed to restore");
389 let snap2 = device
390 .virtio_snapshot()
391 .expect("failed to take snapshot after mod");
392 assert_eq!(snap, snap2);
393 }
394 }
395 };
396}