Skip to content

Commit 3f97840

Browse files
committed
refactor: VirtIO MMIO persistence logic
VirtIO MMIO restore logic activates the device the moment we restore the device state, if the device was activated when snapshotted. Move the activation responsibility to the logic the restores the MMIO transport. The reason for this change is that that's how it will be done for the PCI transport. Unifying this will allow us reusing the same types for restoring the non-transport state of devices. Note that we needed to change the way Net devices are saved/restored. RxBuffer type of Net devices holds RX descriptors that we have parsed from the Queue ahead of time. The way we restored this info was manipulating the queue to re-parse the RX descriptors during the restore phase. However, we need the device to be activated to do so, which now isn't. So, instead of storing this info inside the snapshot make sure we have flushed everything before taking the snapshot. Also, simplify a bit the types that we use for serializing/deserializing the state of a device. Signed-off-by: Babis Chalios <[email protected]>
1 parent f894249 commit 3f97840

File tree

12 files changed

+224
-336
lines changed

12 files changed

+224
-336
lines changed

src/vmm/src/device_manager/mmio.rs

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use kvm_ioctls::IoEventAddress;
1616
use linux_loader::cmdline as kernel_cmdline;
1717
#[cfg(target_arch = "x86_64")]
1818
use log::debug;
19-
use log::info;
2019
use serde::{Deserialize, Serialize};
2120
use vm_allocator::AllocPolicy;
2221

@@ -28,14 +27,8 @@ use crate::arch::{RTC_MEM_START, SERIAL_MEM_START};
2827
#[cfg(target_arch = "aarch64")]
2928
use crate::devices::legacy::{RTCDevice, SerialDevice};
3029
use crate::devices::pseudo::BootTimer;
31-
use crate::devices::virtio::balloon::Balloon;
32-
use crate::devices::virtio::block::device::Block;
3330
use crate::devices::virtio::device::VirtioDevice;
34-
use crate::devices::virtio::net::Net;
35-
use crate::devices::virtio::rng::Entropy;
3631
use crate::devices::virtio::transport::mmio::MmioTransport;
37-
use crate::devices::virtio::vsock::{TYPE_VSOCK, Vsock, VsockUnixBackend};
38-
use crate::devices::virtio::{TYPE_BALLOON, TYPE_BLOCK, TYPE_NET, TYPE_RNG};
3932
#[cfg(target_arch = "x86_64")]
4033
use crate::vstate::memory::GuestAddress;
4134

@@ -442,79 +435,6 @@ impl MMIODeviceManager {
442435
Ok(())
443436
}
444437

445-
/// Artificially kick devices as if they had external events.
446-
pub fn kick_devices(&self) {
447-
info!("Artificially kick devices.");
448-
// We only kick virtio devices for now.
449-
let _: Result<(), MmioError> = self.for_each_virtio_device(|virtio_type, id, device| {
450-
let mmio_transport_locked = device.inner.lock().expect("Poisoned locked");
451-
let mut virtio = mmio_transport_locked.locked_device();
452-
match *virtio_type {
453-
TYPE_BALLOON => {
454-
let balloon = virtio.as_mut_any().downcast_mut::<Balloon>().unwrap();
455-
// If device is activated, kick the balloon queue(s) to make up for any
456-
// pending or in-flight epoll events we may have not captured in snapshot.
457-
// Stats queue doesn't need kicking as it is notified via a `timer_fd`.
458-
if balloon.is_activated() {
459-
info!("kick balloon {}.", id);
460-
balloon.process_virtio_queues().unwrap();
461-
}
462-
}
463-
TYPE_BLOCK => {
464-
// We only care about kicking virtio block.
465-
// If we need to kick vhost-user-block we can do nothing.
466-
if let Some(block) = virtio.as_mut_any().downcast_mut::<Block>() {
467-
// If device is activated, kick the block queue(s) to make up for any
468-
// pending or in-flight epoll events we may have not captured in
469-
// snapshot. No need to kick Ratelimiters
470-
// because they are restored 'unblocked' so
471-
// any inflight `timer_fd` events can be safely discarded.
472-
if block.is_activated() {
473-
info!("kick block {}.", id);
474-
block.process_virtio_queues().unwrap();
475-
}
476-
}
477-
}
478-
TYPE_NET => {
479-
let net = virtio.as_mut_any().downcast_mut::<Net>().unwrap();
480-
// If device is activated, kick the net queue(s) to make up for any
481-
// pending or in-flight epoll events we may have not captured in snapshot.
482-
// No need to kick Ratelimiters because they are restored 'unblocked' so
483-
// any inflight `timer_fd` events can be safely discarded.
484-
if net.is_activated() {
485-
info!("kick net {}.", id);
486-
net.process_virtio_queues().unwrap();
487-
}
488-
}
489-
TYPE_VSOCK => {
490-
// Vsock has complicated protocol that isn't resilient to any packet loss,
491-
// so for Vsock we don't support connection persistence through snapshot.
492-
// Any in-flight packets or events are simply lost.
493-
// Vsock is restored 'empty'.
494-
// The only reason we still `kick` it is to make guest process
495-
// `TRANSPORT_RESET_EVENT` event we sent during snapshot creation.
496-
let vsock = virtio
497-
.as_mut_any()
498-
.downcast_mut::<Vsock<VsockUnixBackend>>()
499-
.unwrap();
500-
if vsock.is_activated() {
501-
info!("kick vsock {id}.");
502-
vsock.signal_used_queue(0).unwrap();
503-
}
504-
}
505-
TYPE_RNG => {
506-
let entropy = virtio.as_mut_any().downcast_mut::<Entropy>().unwrap();
507-
if entropy.is_activated() {
508-
info!("kick entropy {id}.");
509-
entropy.process_virtio_queues().unwrap();
510-
}
511-
}
512-
_ => (),
513-
}
514-
Ok(())
515-
});
516-
}
517-
518438
#[cfg(target_arch = "aarch64")]
519439
pub fn virtio_device_info(&self) -> Vec<&MMIODeviceInfo> {
520440
let mut device_info = Vec::new();

src/vmm/src/device_manager/mod.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Use of this source code is governed by a BSD-style license that can be
66
// found in the THIRD-PARTY file.
77

8+
use std::convert::Infallible;
89
use std::fmt::Debug;
910
use std::sync::{Arc, Mutex};
1011

@@ -13,7 +14,7 @@ use event_manager::{MutEventSubscriber, SubscriberOps};
1314
#[cfg(target_arch = "x86_64")]
1415
use legacy::{LegacyDeviceError, PortIODeviceManager};
1516
use linux_loader::loader::Cmdline;
16-
use log::error;
17+
use log::{error, info};
1718
use mmio::{MMIODeviceManager, MmioError};
1819
use pci_mngr::{PciDevices, PciDevicesConstructorArgs, PciManagerError};
1920
use persist::{ACPIDeviceManagerConstructorArgs, MMIODevManagerConstructorArgs};
@@ -30,8 +31,14 @@ use crate::devices::legacy::RTCDevice;
3031
use crate::devices::legacy::serial::SerialOut;
3132
use crate::devices::legacy::{IER_RDA_BIT, IER_RDA_OFFSET, SerialDevice};
3233
use crate::devices::pseudo::BootTimer;
34+
use crate::devices::virtio::balloon::Balloon;
35+
use crate::devices::virtio::block::device::Block;
3336
use crate::devices::virtio::device::VirtioDevice;
37+
use crate::devices::virtio::net::Net;
38+
use crate::devices::virtio::rng::Entropy;
3439
use crate::devices::virtio::transport::mmio::{IrqTrigger, MmioTransport};
40+
use crate::devices::virtio::vsock::{TYPE_VSOCK, Vsock, VsockUnixBackend};
41+
use crate::devices::virtio::{TYPE_BALLOON, TYPE_BLOCK, TYPE_NET, TYPE_RNG};
3542
use crate::resources::VmResources;
3643
use crate::snapshot::Persist;
3744
use crate::vstate::memory::GuestMemoryMmap;
@@ -274,6 +281,106 @@ impl DeviceManager {
274281
self.pci_devices
275282
.attach_pci_segment(&self.resource_allocator)
276283
}
284+
285+
fn do_kick_device(virtio_device: Arc<Mutex<dyn VirtioDevice>>) {
286+
let mut device = virtio_device.lock().expect("Poisoned lock");
287+
match device.device_type() {
288+
TYPE_BALLOON => {
289+
let balloon = device.as_mut_any().downcast_mut::<Balloon>().unwrap();
290+
// If device is activated, kick the balloon queue(s) to make up for any
291+
// pending or in-flight epoll events we may have not captured in snapshot.
292+
// Stats queue doesn't need kicking as it is notified via a `timer_fd`.
293+
if balloon.is_activated() {
294+
info!("kick balloon {}.", balloon.id());
295+
balloon.process_virtio_queues().unwrap();
296+
}
297+
}
298+
TYPE_BLOCK => {
299+
// We only care about kicking virtio block.
300+
// If we need to kick vhost-user-block we can do nothing.
301+
if let Some(block) = device.as_mut_any().downcast_mut::<Block>() {
302+
// If device is activated, kick the block queue(s) to make up for any
303+
// pending or in-flight epoll events we may have not captured in
304+
// snapshot. No need to kick Ratelimiters
305+
// because they are restored 'unblocked' so
306+
// any inflight `timer_fd` events can be safely discarded.
307+
if block.is_activated() {
308+
info!("kick block {}.", block.id());
309+
block.process_virtio_queues().unwrap();
310+
}
311+
}
312+
}
313+
TYPE_NET => {
314+
let net = device.as_mut_any().downcast_mut::<Net>().unwrap();
315+
// If device is activated, kick the net queue(s) to make up for any
316+
// pending or in-flight epoll events we may have not captured in snapshot.
317+
// No need to kick Ratelimiters because they are restored 'unblocked' so
318+
// any inflight `timer_fd` events can be safely discarded.
319+
if net.is_activated() {
320+
info!("kick net {}.", net.id());
321+
net.process_virtio_queues().unwrap();
322+
}
323+
}
324+
TYPE_VSOCK => {
325+
// Vsock has complicated protocol that isn't resilient to any packet loss,
326+
// so for Vsock we don't support connection persistence through snapshot.
327+
// Any in-flight packets or events are simply lost.
328+
// Vsock is restored 'empty'.
329+
// The only reason we still `kick` it is to make guest process
330+
// `TRANSPORT_RESET_EVENT` event we sent during snapshot creation.
331+
let vsock = device
332+
.as_mut_any()
333+
.downcast_mut::<Vsock<VsockUnixBackend>>()
334+
.unwrap();
335+
if vsock.is_activated() {
336+
info!("kick vsock {}.", vsock.id());
337+
vsock.signal_used_queue(0).unwrap();
338+
}
339+
}
340+
TYPE_RNG => {
341+
let entropy = device.as_mut_any().downcast_mut::<Entropy>().unwrap();
342+
if entropy.is_activated() {
343+
info!("kick entropy {}.", entropy.id());
344+
entropy.process_virtio_queues().unwrap();
345+
}
346+
}
347+
_ => (),
348+
}
349+
}
350+
351+
/// Artificially kick VirtIO devices as if they had external events.
352+
pub fn kick_virtio_devices(&self) {
353+
info!("Artificially kick devices");
354+
// Go through MMIO VirtIO devices
355+
let _: Result<(), MmioError> = self.mmio_devices.for_each_virtio_device(|_, _, device| {
356+
let mmio_transport_locked = device.inner.lock().expect("Poisoned lock");
357+
Self::do_kick_device(mmio_transport_locked.device());
358+
Ok(())
359+
});
360+
}
361+
362+
fn do_mark_virtio_queue_memory_dirty(
363+
device: Arc<Mutex<dyn VirtioDevice>>,
364+
mem: &GuestMemoryMmap,
365+
) {
366+
// SAFETY:
367+
// This should never fail as we mark pages only if device has already been activated,
368+
// and the address validation was already performed on device activation.
369+
let mut locked_device = device.lock().expect("Poisoned lock");
370+
if locked_device.is_activated() {
371+
locked_device.mark_queue_memory_dirty(mem).unwrap()
372+
}
373+
}
374+
375+
/// Mark queue memory dirty for activated VirtIO devices
376+
pub fn mark_virtio_queue_memory_dirty(&self, mem: &GuestMemoryMmap) {
377+
// Go through MMIO VirtIO devices
378+
let _: Result<(), Infallible> = self.mmio_devices.for_each_virtio_device(|_, _, device| {
379+
let mmio_transport_locked = device.inner.lock().expect("Poisoned locked");
380+
Self::do_mark_virtio_queue_memory_dirty(mmio_transport_locked.device(), mem);
381+
Ok(())
382+
});
383+
}
277384
}
278385

279386
#[derive(Debug, Default, Clone, Serialize, Deserialize)]

0 commit comments

Comments
 (0)