From 36223f5b49d89865614a0e43bc3f7c8c81bbd8b1 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Tue, 12 May 2026 17:33:55 +0100 Subject: [PATCH 01/25] virtio: Change VirtioDevice::reset() return type to bool The reset() trait method returns Option<(Arc, Vec)> for no apparent reason and the values are never used by any caller. Change the return value to a bool to signify success or failure. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/device.rs | 7 +- src/vmm/src/devices/virtio/transport/mmio.rs | 10 +-- .../devices/virtio/transport/pci/device.rs | 65 +++++++++---------- 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/src/vmm/src/devices/virtio/device.rs b/src/vmm/src/devices/virtio/device.rs index 1cb591d7a3a..774f2f68ad8 100644 --- a/src/vmm/src/devices/virtio/device.rs +++ b/src/vmm/src/devices/virtio/device.rs @@ -178,10 +178,9 @@ pub trait VirtioDevice: AsAny + MutEventSubscriber + Send { /// Checks if the resources of this device are activated. fn is_activated(&self) -> bool; - /// Optionally deactivates this device and returns ownership of the guest memory map, interrupt - /// event, and queue events. - fn reset(&mut self) -> Option<(Arc, Vec)> { - None + /// Reset the device. Returns true on success, false otherwise. + fn reset(&mut self) -> bool { + false } /// Mark pages used by queues as dirty. diff --git a/src/vmm/src/devices/virtio/transport/mmio.rs b/src/vmm/src/devices/virtio/transport/mmio.rs index 9c519604a96..a09636c1ccb 100644 --- a/src/vmm/src/devices/virtio/transport/mmio.rs +++ b/src/vmm/src/devices/virtio/transport/mmio.rs @@ -187,12 +187,8 @@ impl MmioTransport { let mut locked_device = self.device.lock().expect("Poisoned lock"); if locked_device.is_activated() { let mut device_status = self.device_status; - let reset_result = locked_device.reset(); - match reset_result { - Some((_interrupt_evt, mut _queue_evts)) => {} - None => { - device_status |= FAILED; - } + if !locked_device.reset() { + device_status |= FAILED; } self.device_status = device_status; } @@ -614,7 +610,7 @@ pub(crate) mod tests { let interrupt = Arc::new(IrqTrigger::new()); let mut dummy = DummyDevice::new(); // Validate reset is no-op. - assert!(dummy.reset().is_none()); + assert!(!dummy.reset()); let mut d = MmioTransport::new(m, interrupt, Arc::new(Mutex::new(dummy)), false); // We just make sure here that the implementation of a mmio device behaves as we expect, diff --git a/src/vmm/src/devices/virtio/transport/pci/device.rs b/src/vmm/src/devices/virtio/transport/pci/device.rs index 58735c1b723..70bcda4fa8d 100644 --- a/src/vmm/src/devices/virtio/transport/pci/device.rs +++ b/src/vmm/src/devices/virtio/transport/pci/device.rs @@ -944,41 +944,36 @@ impl PciDevice for VirtioPciDevice { // Device has been reset by the driver if self.device_activated.load(Ordering::SeqCst) && self.is_driver_init() { let mut device = self.device.lock().unwrap(); - let reset_result = device.reset(); - match reset_result { - Some(_) => { - // Upon reset the device returns its interrupt EventFD - self.virtio_interrupt = None; - self.device_activated.store(false, Ordering::SeqCst); - - // Reset queue readiness (changes queue_enable), queue sizes - // and selected_queue as per spec for reset - self.virtio_device() - .lock() - .unwrap() - .queues_mut() - .iter_mut() - .for_each(Queue::reset); - self.common_config.queue_select = 0; - } - None => { - error!("Attempt to reset device when not implemented in underlying device"); - // The virtio spec does not specify what to do if reset fails. - // - // Our MMIO transport sets FAILED in this case, but we must NOT do that for PCI. - // During shutdown, the Linux kernel issues a reset to each virtio device. The - // virtio PCI driver then polls device_status until it reads back 0, unlike the - // virtio MMIO driver which simply writes 0 and returns. Setting FAILED would - // cause the poll to spin forever, breaking reboot command and Ctrl-Alt-Del. - // - PCI: https://elixir.bootlin.com/linux/v6.19.8/source/drivers/virtio/virtio_pci_modern.c#L546-L565 - // - MMIO: https://elixir.bootlin.com/linux/v6.19.8/source/drivers/virtio/virtio_mmio.c#L251-L258 - // - // Since device_status was already set to INIT by set_device_status(), we don't - // need to set it again here. However, the backend device is still active since - // reset() is unimplemented. The combination of device_activated == true and - // device_status == INIT will cause set_device_status() to block any - // re-initialization attempts. - } + if device.reset() { + self.virtio_interrupt = None; + self.device_activated.store(false, Ordering::SeqCst); + + // Reset queue readiness (changes queue_enable), queue sizes + // and selected_queue as per spec for reset + self.virtio_device() + .lock() + .unwrap() + .queues_mut() + .iter_mut() + .for_each(Queue::reset); + self.common_config.queue_select = 0; + } else { + error!("Attempt to reset device when not implemented in underlying device"); + // The virtio spec does not specify what to do if reset fails. + // + // Our MMIO transport sets FAILED in this case, but we must NOT do that for PCI. + // During shutdown, the Linux kernel issues a reset to each virtio device. The + // virtio PCI driver then polls device_status until it reads back 0, unlike the + // virtio MMIO driver which simply writes 0 and returns. Setting FAILED would + // cause the poll to spin forever, breaking reboot command and Ctrl-Alt-Del. + // - PCI: https://elixir.bootlin.com/linux/v6.19.8/source/drivers/virtio/virtio_pci_modern.c#L546-L565 + // - MMIO: https://elixir.bootlin.com/linux/v6.19.8/source/drivers/virtio/virtio_mmio.c#L251-L258 + // + // Since device_status was already set to INIT by set_device_status(), we don't + // need to set it again here. However, the backend device is still active since + // reset() is unimplemented. The combination of device_activated == true and + // device_status == INIT will cause set_device_status() to block any + // re-initialization attempts. } } None From 31abcb92ffb736c43c4cdbf3aa814a82ad3cba3f Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Wed, 13 May 2026 17:50:21 +0100 Subject: [PATCH 02/25] virtio: mmio: Simplify MMIO transport reset logic Simplify the MMIO transport reset logic by always performing the MMIO transport reset unconditionally and calling device.reset() regardless of activation state. This requires updating the affected unit tests and implementing reset() for the DummyDevice. The unit test assumed that reset() is never supported (i.e. always returns false), but that is going to change soon so we want to make sure that resetting moves the device to the deactivated state. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/transport/mmio.rs | 35 +++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/vmm/src/devices/virtio/transport/mmio.rs b/src/vmm/src/devices/virtio/transport/mmio.rs index a09636c1ccb..e41e2a34391 100644 --- a/src/vmm/src/devices/virtio/transport/mmio.rs +++ b/src/vmm/src/devices/virtio/transport/mmio.rs @@ -165,7 +165,6 @@ impl MmioTransport { /// of the driver initialization sequence specified in 3.1. The driver MUST NOT clear /// a device status bit. If the driver sets the FAILED bit, the driver MUST later reset /// the device before attempting to re-initialize. - #[allow(unused_assignments)] fn set_device_status(&mut self, status: u32) { use device_status::*; @@ -183,21 +182,11 @@ impl MmioTransport { // TODO: notify backend driver to stop the device self.device_status |= FAILED; } else if status == INIT { - { - let mut locked_device = self.device.lock().expect("Poisoned lock"); - if locked_device.is_activated() { - let mut device_status = self.device_status; - if !locked_device.reset() { - device_status |= FAILED; - } - self.device_status = device_status; - } - } - - // If the backend device driver doesn't support reset, - // just leave the device marked as FAILED. - if self.device_status & FAILED == 0 { + if self.device_status != INIT { self.reset(); + if !self.device.lock().expect("Poisoned lock").reset() { + self.device_status |= FAILED; + } } } else if VALID_TRANSITIONS .iter() @@ -596,6 +585,12 @@ pub(crate) mod tests { fn is_activated(&self) -> bool { self.device_activated } + + fn reset(&mut self) -> bool { + self.device_activated = false; + self.acked_features = 0; + true + } } fn set_device_status(d: &mut MmioTransport, status: u32) { @@ -609,8 +604,7 @@ pub(crate) mod tests { let m = single_region_mem(0x1000); let interrupt = Arc::new(IrqTrigger::new()); let mut dummy = DummyDevice::new(); - // Validate reset is no-op. - assert!(!dummy.reset()); + assert!(dummy.reset()); let mut d = MmioTransport::new(m, interrupt, Arc::new(Mutex::new(dummy)), false); // We just make sure here that the implementation of a mmio device behaves as we expect, @@ -1140,11 +1134,12 @@ pub(crate) mod tests { assert_eq!(d.device_status, 0x8f); assert!(d.locked_device().is_activated()); - // Nothing happens when backend driver doesn't support reset + // Resetting the device should deactivate it write_le_u32(&mut buf[..], 0x0); d.write(0x0, 0x70, &buf[..]); - assert_eq!(d.device_status, 0x8f); - assert!(d.locked_device().is_activated()); + assert_eq!(d.device_status, device_status::INIT); + assert!(!d.locked_device().is_activated()); + assert_eq!(d.locked_device().acked_features(), 0); } #[test] From 9d058aa888417ec26bd2f011f54ed2ab2c8d1883 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 15:55:49 +0100 Subject: [PATCH 03/25] virtio: pci: Fix deadlock in device reset path The reset success path locked self.device, then tried to lock it again via self.virtio_device().lock() to reset queues. This was a deadlock that was never triggered because no device previously implemented reset() (all returned false). Use the already-held guard instead. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/transport/pci/device.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vmm/src/devices/virtio/transport/pci/device.rs b/src/vmm/src/devices/virtio/transport/pci/device.rs index 70bcda4fa8d..fcfbf73cf2b 100644 --- a/src/vmm/src/devices/virtio/transport/pci/device.rs +++ b/src/vmm/src/devices/virtio/transport/pci/device.rs @@ -950,12 +950,7 @@ impl PciDevice for VirtioPciDevice { // Reset queue readiness (changes queue_enable), queue sizes // and selected_queue as per spec for reset - self.virtio_device() - .lock() - .unwrap() - .queues_mut() - .iter_mut() - .for_each(Queue::reset); + device.queues_mut().iter_mut().for_each(Queue::reset); self.common_config.queue_select = 0; } else { error!("Attempt to reset device when not implemented in underlying device"); From f2e233a3ab907a0376ddadb1bad51752b6341ae9 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 15:56:03 +0100 Subject: [PATCH 04/25] virtio: pci: Keep interrupt object across device reset Don't clear virtio_interrupt on reset. The interrupt object holds Arc references to the PCI device's MSI-X configuration which remains valid across reset. Clearing it will cause a panic at the unwrap() in the activation path when the guest re-probes the device after reset. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/transport/pci/device.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vmm/src/devices/virtio/transport/pci/device.rs b/src/vmm/src/devices/virtio/transport/pci/device.rs index fcfbf73cf2b..d38aa96d2c7 100644 --- a/src/vmm/src/devices/virtio/transport/pci/device.rs +++ b/src/vmm/src/devices/virtio/transport/pci/device.rs @@ -945,7 +945,6 @@ impl PciDevice for VirtioPciDevice { if self.device_activated.load(Ordering::SeqCst) && self.is_driver_init() { let mut device = self.device.lock().unwrap(); if device.reset() { - self.virtio_interrupt = None; self.device_activated.store(false, Ordering::SeqCst); // Reset queue readiness (changes queue_enable), queue sizes From 77ed1c2dd8a2fa679899d8fd6f824226a292092b Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 16:05:49 +0100 Subject: [PATCH 05/25] virtio: pci: Reset feature select registers on device reset Reset device_feature_select and driver_feature_select to 0 on device reset, matching what the MMIO transport does with features_select and acked_features_select. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/transport/pci/device.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vmm/src/devices/virtio/transport/pci/device.rs b/src/vmm/src/devices/virtio/transport/pci/device.rs index d38aa96d2c7..05793ee9714 100644 --- a/src/vmm/src/devices/virtio/transport/pci/device.rs +++ b/src/vmm/src/devices/virtio/transport/pci/device.rs @@ -951,6 +951,8 @@ impl PciDevice for VirtioPciDevice { // and selected_queue as per spec for reset device.queues_mut().iter_mut().for_each(Queue::reset); self.common_config.queue_select = 0; + self.common_config.device_feature_select = 0; + self.common_config.driver_feature_select = 0; } else { error!("Attempt to reset device when not implemented in underlying device"); // The virtio spec does not specify what to do if reset fails. From 82b3d5ffd3062246c8ee7c45433b077966d8366e Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Wed, 13 May 2026 17:52:13 +0100 Subject: [PATCH 06/25] virtio: mmio: Add unit test for device reset failure Add test_bus_device_reset_failure to verify that when device.reset() returns false, the FAILED bit is set in device_status. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/transport/mmio.rs | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/vmm/src/devices/virtio/transport/mmio.rs b/src/vmm/src/devices/virtio/transport/mmio.rs index e41e2a34391..eb90d888d33 100644 --- a/src/vmm/src/devices/virtio/transport/mmio.rs +++ b/src/vmm/src/devices/virtio/transport/mmio.rs @@ -491,6 +491,7 @@ pub(crate) mod tests { device_activated: bool, config_bytes: [u8; 0xeff], activate_should_error: bool, + reset_should_fail: bool, } impl DummyDevice { @@ -507,6 +508,7 @@ pub(crate) mod tests { device_activated: false, config_bytes: [0; 0xeff], activate_should_error: false, + reset_should_fail: false, } } @@ -587,6 +589,9 @@ pub(crate) mod tests { } fn reset(&mut self) -> bool { + if self.reset_should_fail { + return false; + } self.device_activated = false; self.acked_features = 0; true @@ -1142,6 +1147,26 @@ pub(crate) mod tests { assert_eq!(d.locked_device().acked_features(), 0); } + #[test] + fn test_bus_device_reset_failure() { + let m = single_region_mem(0x1000); + let interrupt = Arc::new(IrqTrigger::new()); + let device = DummyDevice { + reset_should_fail: true, + ..DummyDevice::new() + }; + let mut d = MmioTransport::new(m, interrupt, Arc::new(Mutex::new(device)), false); + + activate_device(&mut d); + assert!(d.locked_device().is_activated()); + + // A backend that doesn't support reset must set FAILED. + let mut buf = [0; 4]; + write_le_u32(&mut buf[..], 0x0); + d.write(0x0, 0x70, &buf[..]); + assert_ne!(d.device_status & device_status::FAILED, 0); + } + #[test] fn test_get_avail_features() { let dummy_dev = DummyDevice::new(); From 20cd54f1fa31589f63396f4f1d583c374d541c89 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Fri, 15 May 2026 16:14:07 +0100 Subject: [PATCH 07/25] virtio: Add deactivate() method to VirtioDevice trait Add a deactivate() method that sets the device state to Inactive. This will be used by the generic reset implementation in a subsequent patch. Signed-off-by: Ilias Stamatis --- src/vmm/src/device_manager/mmio.rs | 2 ++ src/vmm/src/devices/virtio/balloon/device.rs | 4 ++++ src/vmm/src/devices/virtio/block/device.rs | 7 +++++++ src/vmm/src/devices/virtio/block/vhost_user/device.rs | 4 ++++ src/vmm/src/devices/virtio/block/virtio/device.rs | 4 ++++ src/vmm/src/devices/virtio/device.rs | 7 +++++++ src/vmm/src/devices/virtio/mem/device.rs | 4 ++++ src/vmm/src/devices/virtio/net/device.rs | 4 ++++ src/vmm/src/devices/virtio/pmem/device.rs | 4 ++++ src/vmm/src/devices/virtio/rng/device.rs | 4 ++++ src/vmm/src/devices/virtio/transport/mmio.rs | 4 ++++ src/vmm/src/devices/virtio/vsock/device.rs | 4 ++++ 12 files changed, 52 insertions(+) diff --git a/src/vmm/src/device_manager/mmio.rs b/src/vmm/src/device_manager/mmio.rs index e84784a3d5f..1d5a3083dae 100644 --- a/src/vmm/src/device_manager/mmio.rs +++ b/src/vmm/src/device_manager/mmio.rs @@ -587,6 +587,8 @@ pub(crate) mod tests { fn is_activated(&self) -> bool { false } + + fn deactivate(&mut self) {} } #[test] diff --git a/src/vmm/src/devices/virtio/balloon/device.rs b/src/vmm/src/devices/virtio/balloon/device.rs index 1e962e828d7..f19a05514a3 100644 --- a/src/vmm/src/devices/virtio/balloon/device.rs +++ b/src/vmm/src/devices/virtio/balloon/device.rs @@ -979,6 +979,10 @@ impl VirtioDevice for Balloon { self.device_state.is_activated() } + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } + fn kick(&mut self) { if self.is_activated() { if self.free_page_hinting() { diff --git a/src/vmm/src/devices/virtio/block/device.rs b/src/vmm/src/devices/virtio/block/device.rs index 31bea2771d8..2edca8f4675 100644 --- a/src/vmm/src/devices/virtio/block/device.rs +++ b/src/vmm/src/devices/virtio/block/device.rs @@ -207,6 +207,13 @@ impl VirtioDevice for Block { } } + fn deactivate(&mut self) { + match self { + Self::Virtio(b) => b.deactivate(), + Self::VhostUser(b) => b.deactivate(), + } + } + fn prepare_save(&mut self) { match self { Self::Virtio(b) => b.prepare_save(), diff --git a/src/vmm/src/devices/virtio/block/vhost_user/device.rs b/src/vmm/src/devices/virtio/block/vhost_user/device.rs index 7df600722ab..4b178d476b5 100644 --- a/src/vmm/src/devices/virtio/block/vhost_user/device.rs +++ b/src/vmm/src/devices/virtio/block/vhost_user/device.rs @@ -379,6 +379,10 @@ where fn is_activated(&self) -> bool { self.device_state.is_activated() } + + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } } #[cfg(test)] diff --git a/src/vmm/src/devices/virtio/block/virtio/device.rs b/src/vmm/src/devices/virtio/block/virtio/device.rs index ec7ec468505..ed55db37a8e 100644 --- a/src/vmm/src/devices/virtio/block/virtio/device.rs +++ b/src/vmm/src/devices/virtio/block/virtio/device.rs @@ -674,6 +674,10 @@ impl VirtioDevice for VirtioBlock { fn is_activated(&self) -> bool { self.device_state.is_activated() } + + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } } impl Drop for VirtioBlock { diff --git a/src/vmm/src/devices/virtio/device.rs b/src/vmm/src/devices/virtio/device.rs index 774f2f68ad8..c333ab75bdb 100644 --- a/src/vmm/src/devices/virtio/device.rs +++ b/src/vmm/src/devices/virtio/device.rs @@ -178,6 +178,9 @@ pub trait VirtioDevice: AsAny + MutEventSubscriber + Send { /// Checks if the resources of this device are activated. fn is_activated(&self) -> bool; + /// Set the device state to Inactive + fn deactivate(&mut self); + /// Reset the device. Returns true on success, false otherwise. fn reset(&mut self) -> bool { false @@ -309,6 +312,10 @@ pub(crate) mod tests { fn is_activated(&self) -> bool { todo!() } + + fn deactivate(&mut self) { + todo!() + } } #[test] diff --git a/src/vmm/src/devices/virtio/mem/device.rs b/src/vmm/src/devices/virtio/mem/device.rs index c6c0ea443e4..3a77bcf939e 100644 --- a/src/vmm/src/devices/virtio/mem/device.rs +++ b/src/vmm/src/devices/virtio/mem/device.rs @@ -656,6 +656,10 @@ impl VirtioDevice for VirtioMem { self.device_state.is_activated() } + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } + fn activate( &mut self, mem: GuestMemoryMmap, diff --git a/src/vmm/src/devices/virtio/net/device.rs b/src/vmm/src/devices/virtio/net/device.rs index 22b223b383b..f6c27d4dd19 100644 --- a/src/vmm/src/devices/virtio/net/device.rs +++ b/src/vmm/src/devices/virtio/net/device.rs @@ -1088,6 +1088,10 @@ impl VirtioDevice for Net { self.device_state.is_activated() } + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } + /// Prepare saving state fn prepare_save(&mut self) { // We shouldn't be messing with the queue if the device is not activated. diff --git a/src/vmm/src/devices/virtio/pmem/device.rs b/src/vmm/src/devices/virtio/pmem/device.rs index 4d4da40a8e9..acca6adb0e8 100644 --- a/src/vmm/src/devices/virtio/pmem/device.rs +++ b/src/vmm/src/devices/virtio/pmem/device.rs @@ -588,6 +588,10 @@ impl VirtioDevice for Pmem { self.device_state.is_activated() } + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } + fn kick(&mut self) { if self.is_activated() { info!("kick pmem {}.", self.config.id); diff --git a/src/vmm/src/devices/virtio/rng/device.rs b/src/vmm/src/devices/virtio/rng/device.rs index a6fee702628..4035838087e 100644 --- a/src/vmm/src/devices/virtio/rng/device.rs +++ b/src/vmm/src/devices/virtio/rng/device.rs @@ -307,6 +307,10 @@ impl VirtioDevice for Entropy { self.device_state.is_activated() } + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } + fn activate( &mut self, mem: GuestMemoryMmap, diff --git a/src/vmm/src/devices/virtio/transport/mmio.rs b/src/vmm/src/devices/virtio/transport/mmio.rs index eb90d888d33..bf1dddb66ac 100644 --- a/src/vmm/src/devices/virtio/transport/mmio.rs +++ b/src/vmm/src/devices/virtio/transport/mmio.rs @@ -588,6 +588,10 @@ pub(crate) mod tests { self.device_activated } + fn deactivate(&mut self) { + self.device_activated = false; + } + fn reset(&mut self) -> bool { if self.reset_should_fail { return false; diff --git a/src/vmm/src/devices/virtio/vsock/device.rs b/src/vmm/src/devices/virtio/vsock/device.rs index 8c8c51eb45e..4e501b7ca40 100644 --- a/src/vmm/src/devices/virtio/vsock/device.rs +++ b/src/vmm/src/devices/virtio/vsock/device.rs @@ -389,6 +389,10 @@ where self.device_state.is_activated() } + fn deactivate(&mut self) { + self.device_state = DeviceState::Inactive; + } + fn kick(&mut self) { // Vsock has complicated protocol that isn't resilient to any packet loss, // so for Vsock we don't support connection persistence through snapshot. From 3d106c75a811437fde57843c06a7845ef3243387 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Fri, 15 May 2026 16:16:22 +0100 Subject: [PATCH 08/25] virtio: Move queue reset from transport to VirtioDevice::reset() Move the queue reset logic from the MMIO and PCI transport code into the default reset() implementation in the VirtioDevice trait. This is generic virtio state that should be reset for all devices, regardless of transport. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/device.rs | 3 +++ src/vmm/src/devices/virtio/queue.rs | 13 ------------- src/vmm/src/devices/virtio/transport/mmio.rs | 3 --- src/vmm/src/devices/virtio/transport/pci/device.rs | 3 --- 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/vmm/src/devices/virtio/device.rs b/src/vmm/src/devices/virtio/device.rs index c333ab75bdb..28518854b89 100644 --- a/src/vmm/src/devices/virtio/device.rs +++ b/src/vmm/src/devices/virtio/device.rs @@ -183,6 +183,9 @@ pub trait VirtioDevice: AsAny + MutEventSubscriber + Send { /// Reset the device. Returns true on success, false otherwise. fn reset(&mut self) -> bool { + for queue in self.queues_mut() { + *queue = Queue::new(queue.max_size); + } false } diff --git a/src/vmm/src/devices/virtio/queue.rs b/src/vmm/src/devices/virtio/queue.rs index 7fd862f45ca..79c635e5c4d 100644 --- a/src/vmm/src/devices/virtio/queue.rs +++ b/src/vmm/src/devices/virtio/queue.rs @@ -669,19 +669,6 @@ impl Queue { new - used_event - Wrapping(1) < new - old } - - /// Resets the Virtio Queue - pub(crate) fn reset(&mut self) { - self.ready = false; - self.size = self.max_size; - self.desc_table_address = GuestAddress(0); - self.avail_ring_address = GuestAddress(0); - self.used_ring_address = GuestAddress(0); - self.next_avail = Wrapping(0); - self.next_used = Wrapping(0); - self.num_added = Wrapping(0); - self.uses_notif_suppression = false; - } } #[cfg(kani)] diff --git a/src/vmm/src/devices/virtio/transport/mmio.rs b/src/vmm/src/devices/virtio/transport/mmio.rs index bf1dddb66ac..644c8162441 100644 --- a/src/vmm/src/devices/virtio/transport/mmio.rs +++ b/src/vmm/src/devices/virtio/transport/mmio.rs @@ -153,9 +153,6 @@ impl MmioTransport { // . Keep interrupt_evt and queue_evts as is. There may be pending notifications in those // eventfds, but nothing will happen other than supurious wakeups. // . Do not reset config_generation and keep it monotonically increasing - for queue in self.locked_device().queues_mut() { - *queue = Queue::new(queue.max_size); - } } /// Update device status according to the state machine defined by VirtIO Spec 1.0. diff --git a/src/vmm/src/devices/virtio/transport/pci/device.rs b/src/vmm/src/devices/virtio/transport/pci/device.rs index 05793ee9714..7db06537771 100644 --- a/src/vmm/src/devices/virtio/transport/pci/device.rs +++ b/src/vmm/src/devices/virtio/transport/pci/device.rs @@ -947,9 +947,6 @@ impl PciDevice for VirtioPciDevice { if device.reset() { self.device_activated.store(false, Ordering::SeqCst); - // Reset queue readiness (changes queue_enable), queue sizes - // and selected_queue as per spec for reset - device.queues_mut().iter_mut().for_each(Queue::reset); self.common_config.queue_select = 0; self.common_config.device_feature_select = 0; self.common_config.driver_feature_select = 0; From 2a21987f50452d56f026e54d9cd8fddfce390c88 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Fri, 15 May 2026 16:17:53 +0100 Subject: [PATCH 09/25] virtio: Complete reset() and introduce _reset() Complete the reset() implementation by deactivating the device and setting acked_features to 0. This must happen for all virtio backends. Introduce a _reset() method that is called at the end of reset() and can be overridden by virtio backends with backend specific reset code. Make all backends return false for now since none of them supports reset yet. Signed-off-by: Ilias Stamatis --- src/vmm/src/device_manager/mmio.rs | 4 ++++ src/vmm/src/devices/virtio/balloon/device.rs | 4 ++++ src/vmm/src/devices/virtio/block/device.rs | 7 +++++++ .../src/devices/virtio/block/vhost_user/device.rs | 4 ++++ src/vmm/src/devices/virtio/block/virtio/device.rs | 4 ++++ src/vmm/src/devices/virtio/device.rs | 13 ++++++++++++- src/vmm/src/devices/virtio/mem/device.rs | 4 ++++ src/vmm/src/devices/virtio/net/device.rs | 4 ++++ src/vmm/src/devices/virtio/pmem/device.rs | 4 ++++ src/vmm/src/devices/virtio/rng/device.rs | 4 ++++ src/vmm/src/devices/virtio/transport/mmio.rs | 9 ++------- src/vmm/src/devices/virtio/vsock/device.rs | 4 ++++ 12 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/vmm/src/device_manager/mmio.rs b/src/vmm/src/device_manager/mmio.rs index 1d5a3083dae..d405c9eef11 100644 --- a/src/vmm/src/device_manager/mmio.rs +++ b/src/vmm/src/device_manager/mmio.rs @@ -589,6 +589,10 @@ pub(crate) mod tests { } fn deactivate(&mut self) {} + + fn _reset(&mut self) -> bool { + false + } } #[test] diff --git a/src/vmm/src/devices/virtio/balloon/device.rs b/src/vmm/src/devices/virtio/balloon/device.rs index f19a05514a3..5c98db1b6d7 100644 --- a/src/vmm/src/devices/virtio/balloon/device.rs +++ b/src/vmm/src/devices/virtio/balloon/device.rs @@ -983,6 +983,10 @@ impl VirtioDevice for Balloon { self.device_state = DeviceState::Inactive; } + fn _reset(&mut self) -> bool { + false + } + fn kick(&mut self) { if self.is_activated() { if self.free_page_hinting() { diff --git a/src/vmm/src/devices/virtio/block/device.rs b/src/vmm/src/devices/virtio/block/device.rs index 2edca8f4675..8afb40a075d 100644 --- a/src/vmm/src/devices/virtio/block/device.rs +++ b/src/vmm/src/devices/virtio/block/device.rs @@ -214,6 +214,13 @@ impl VirtioDevice for Block { } } + fn _reset(&mut self) -> bool { + match self { + Self::Virtio(b) => b._reset(), + Self::VhostUser(b) => b._reset(), + } + } + fn prepare_save(&mut self) { match self { Self::Virtio(b) => b.prepare_save(), diff --git a/src/vmm/src/devices/virtio/block/vhost_user/device.rs b/src/vmm/src/devices/virtio/block/vhost_user/device.rs index 4b178d476b5..f4dc506053a 100644 --- a/src/vmm/src/devices/virtio/block/vhost_user/device.rs +++ b/src/vmm/src/devices/virtio/block/vhost_user/device.rs @@ -383,6 +383,10 @@ where fn deactivate(&mut self) { self.device_state = DeviceState::Inactive; } + + fn _reset(&mut self) -> bool { + false + } } #[cfg(test)] diff --git a/src/vmm/src/devices/virtio/block/virtio/device.rs b/src/vmm/src/devices/virtio/block/virtio/device.rs index ed55db37a8e..5e8af63af1e 100644 --- a/src/vmm/src/devices/virtio/block/virtio/device.rs +++ b/src/vmm/src/devices/virtio/block/virtio/device.rs @@ -678,6 +678,10 @@ impl VirtioDevice for VirtioBlock { fn deactivate(&mut self) { self.device_state = DeviceState::Inactive; } + + fn _reset(&mut self) -> bool { + false + } } impl Drop for VirtioBlock { diff --git a/src/vmm/src/devices/virtio/device.rs b/src/vmm/src/devices/virtio/device.rs index 28518854b89..2f95d1c891b 100644 --- a/src/vmm/src/devices/virtio/device.rs +++ b/src/vmm/src/devices/virtio/device.rs @@ -182,13 +182,20 @@ pub trait VirtioDevice: AsAny + MutEventSubscriber + Send { fn deactivate(&mut self); /// Reset the device. Returns true on success, false otherwise. + /// It must not be overridden. fn reset(&mut self) -> bool { + self.deactivate(); + self.set_acked_features(0); for queue in self.queues_mut() { *queue = Queue::new(queue.max_size); } - false + self._reset() } + /// Backend-specific reset logic. Returns true on success, false if the + /// backend does not support reset. + fn _reset(&mut self) -> bool; + /// Mark pages used by queues as dirty. fn mark_queue_memory_dirty(&mut self, mem: &GuestMemoryMmap) -> Result<(), QueueError> { for queue in self.queues_mut() { @@ -319,6 +326,10 @@ pub(crate) mod tests { fn deactivate(&mut self) { todo!() } + + fn _reset(&mut self) -> bool { + todo!() + } } #[test] diff --git a/src/vmm/src/devices/virtio/mem/device.rs b/src/vmm/src/devices/virtio/mem/device.rs index 3a77bcf939e..3d4e2f68ddd 100644 --- a/src/vmm/src/devices/virtio/mem/device.rs +++ b/src/vmm/src/devices/virtio/mem/device.rs @@ -660,6 +660,10 @@ impl VirtioDevice for VirtioMem { self.device_state = DeviceState::Inactive; } + fn _reset(&mut self) -> bool { + false + } + fn activate( &mut self, mem: GuestMemoryMmap, diff --git a/src/vmm/src/devices/virtio/net/device.rs b/src/vmm/src/devices/virtio/net/device.rs index f6c27d4dd19..5d6648be3ab 100644 --- a/src/vmm/src/devices/virtio/net/device.rs +++ b/src/vmm/src/devices/virtio/net/device.rs @@ -1092,6 +1092,10 @@ impl VirtioDevice for Net { self.device_state = DeviceState::Inactive; } + fn _reset(&mut self) -> bool { + false + } + /// Prepare saving state fn prepare_save(&mut self) { // We shouldn't be messing with the queue if the device is not activated. diff --git a/src/vmm/src/devices/virtio/pmem/device.rs b/src/vmm/src/devices/virtio/pmem/device.rs index acca6adb0e8..2331111a3b7 100644 --- a/src/vmm/src/devices/virtio/pmem/device.rs +++ b/src/vmm/src/devices/virtio/pmem/device.rs @@ -592,6 +592,10 @@ impl VirtioDevice for Pmem { self.device_state = DeviceState::Inactive; } + fn _reset(&mut self) -> bool { + false + } + fn kick(&mut self) { if self.is_activated() { info!("kick pmem {}.", self.config.id); diff --git a/src/vmm/src/devices/virtio/rng/device.rs b/src/vmm/src/devices/virtio/rng/device.rs index 4035838087e..4a35c22934b 100644 --- a/src/vmm/src/devices/virtio/rng/device.rs +++ b/src/vmm/src/devices/virtio/rng/device.rs @@ -311,6 +311,10 @@ impl VirtioDevice for Entropy { self.device_state = DeviceState::Inactive; } + fn _reset(&mut self) -> bool { + false + } + fn activate( &mut self, mem: GuestMemoryMmap, diff --git a/src/vmm/src/devices/virtio/transport/mmio.rs b/src/vmm/src/devices/virtio/transport/mmio.rs index 644c8162441..e45198b97e2 100644 --- a/src/vmm/src/devices/virtio/transport/mmio.rs +++ b/src/vmm/src/devices/virtio/transport/mmio.rs @@ -589,13 +589,8 @@ pub(crate) mod tests { self.device_activated = false; } - fn reset(&mut self) -> bool { - if self.reset_should_fail { - return false; - } - self.device_activated = false; - self.acked_features = 0; - true + fn _reset(&mut self) -> bool { + !self.reset_should_fail } } diff --git a/src/vmm/src/devices/virtio/vsock/device.rs b/src/vmm/src/devices/virtio/vsock/device.rs index 4e501b7ca40..7d89ca906ca 100644 --- a/src/vmm/src/devices/virtio/vsock/device.rs +++ b/src/vmm/src/devices/virtio/vsock/device.rs @@ -393,6 +393,10 @@ where self.device_state = DeviceState::Inactive; } + fn _reset(&mut self) -> bool { + false + } + fn kick(&mut self) { // Vsock has complicated protocol that isn't resilient to any packet loss, // so for Vsock we don't support connection persistence through snapshot. From da90c82046050721ddf1290505a23b18ab2bd268 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Wed, 13 May 2026 17:28:05 +0100 Subject: [PATCH 10/25] virtio: net: Add a RxBuffers::clear() method Add a clear() method to RxBuffers that resets all fields to their initial state. This will be used by the virtio-net reset implementation. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/net/device.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vmm/src/devices/virtio/net/device.rs b/src/vmm/src/devices/virtio/net/device.rs index 5d6648be3ab..bfddc363891 100644 --- a/src/vmm/src/devices/virtio/net/device.rs +++ b/src/vmm/src/devices/virtio/net/device.rs @@ -135,6 +135,15 @@ impl RxBuffers { }) } + /// Reset the RX buffers to their initial state. + fn clear(&mut self) { + self.iovec.clear(); + self.parsed_descriptors.clear(); + self.used_descriptors = 0; + self.used_bytes = 0; + self.min_buffer_size = 0; + } + /// Add a new `DescriptorChain` that we received from the RX queue in the buffer. /// /// SAFETY: The `DescriptorChain` cannot be referencing the same memory location as any other From 3029da908cc203a51d9b6ba88879649edc2e5008 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Wed, 13 May 2026 17:28:11 +0100 Subject: [PATCH 11/25] virtio: net: Implement device reset Implement the reset() method for the virtio-net device, resetting the net specific state in-place. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/net/device.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/vmm/src/devices/virtio/net/device.rs b/src/vmm/src/devices/virtio/net/device.rs index bfddc363891..625302e4afd 100644 --- a/src/vmm/src/devices/virtio/net/device.rs +++ b/src/vmm/src/devices/virtio/net/device.rs @@ -1102,7 +1102,9 @@ impl VirtioDevice for Net { } fn _reset(&mut self) -> bool { - false + self.rx_buffer.clear(); + self.tx_buffer.clear(); + true } /// Prepare saving state @@ -2618,4 +2620,16 @@ pub mod tests { assert!(queues[RX_INDEX].uses_notif_suppression); assert!(queues[TX_INDEX].uses_notif_suppression); } + + #[test] + fn test_reset() { + let mem = single_region_mem(2 * MAX_BUFFER_SIZE); + let mut th = TestHelper::get_default(&mem); + th.activate_net(); + + assert!(th.net().is_activated()); + assert!(th.net().reset()); + assert!(!th.net().is_activated()); + assert_eq!(th.net().acked_features(), 0); + } } From 6c585baa7dbbeaf8e67575200a9bfa57332ccf3c Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Wed, 13 May 2026 17:28:19 +0100 Subject: [PATCH 12/25] tests: Add integration test for virtio-net device reset Add a test that verifies virtio-net device reset works end-to-end by unbinding and rebinding the guest driver and checking the device remains functional. Suggested-by: Adam Jensen Signed-off-by: Ilias Stamatis --- .../integration_tests/functional/test_net.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/integration_tests/functional/test_net.py b/tests/integration_tests/functional/test_net.py index 3830c30ef6b..1b00f07f7ac 100644 --- a/tests/integration_tests/functional/test_net.py +++ b/tests/integration_tests/functional/test_net.py @@ -175,3 +175,46 @@ def test_tap_mtu_advertised_to_guest(uvm_plain_any): f"{iface_name} (guest: {guest_if}): VIRTIO_NET_F_MTU (bit {VIRTIO_NET_F_MTU_BIT})" f" not set in negotiated features: {features!r}" ) + + +def test_device_reset(uvm_plain_any): + """ + Test that virtio-net device reset works. + """ + vm = uvm_plain_any + vm.spawn() + vm.basic_config() + vm.add_net_iface() # eth0 - used for SSH + iface2 = vm.add_net_iface() # eth1 - will be reset + vm.start() + + guest_ip2 = iface2.guest_ip + host_ip2 = iface2.host_ip + virtio_dev = vm.ssh.check_output( + "basename $(readlink /sys/class/net/eth1/device)" + ).stdout.strip() + + def configure_eth1(): + vm.ssh.check_output( + f"ip addr flush dev eth1 && " + f"ip addr add {guest_ip2}/30 dev eth1 && " + f"ip link set eth1 up" + ) + + configure_eth1() + vm.ssh.check_output(f"ping -c 1 {host_ip2}") + + # Reset eth1 by unbinding and rebinding its virtio driver. + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_net/unbind" + ) + + # Verify that ping fails after unbind. + ret = vm.ssh.run(f"ping -c 1 -W 1 {host_ip2}") + assert ret.returncode != 0 + + # Re-bind and check again + vm.ssh.check_output(f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_net/bind") + + configure_eth1() + vm.ssh.check_output(f"ping -c 1 {host_ip2}") From 95f58ad3c25f3fb8e544c4dcf57c58a504bfba2e Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 15:25:51 +0100 Subject: [PATCH 13/25] virtio: block: Implement device reset Implement the reset() method for the virtio-block device, resetting the device-specific state in-place. This only adds support for the Virtio backend for now and not VhostUser. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/block/virtio/device.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vmm/src/devices/virtio/block/virtio/device.rs b/src/vmm/src/devices/virtio/block/virtio/device.rs index 5e8af63af1e..1fb2e372938 100644 --- a/src/vmm/src/devices/virtio/block/virtio/device.rs +++ b/src/vmm/src/devices/virtio/block/virtio/device.rs @@ -680,7 +680,8 @@ impl VirtioDevice for VirtioBlock { } fn _reset(&mut self) -> bool { - false + self.is_io_engine_throttled = false; + true } } From 696bb5ac898a25c48d333db12e487b48560d78d3 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 15:25:58 +0100 Subject: [PATCH 14/25] tests: Add integration test for virtio-block device reset Add a test that verifies virtio-block device reset works end-to-end by unbinding and rebinding the guest driver for a scratch block device and checking the device remains functional. Signed-off-by: Ilias Stamatis --- .../functional/test_drive_virtio.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/integration_tests/functional/test_drive_virtio.py b/tests/integration_tests/functional/test_drive_virtio.py index 9c61ead56a9..f2720e73260 100644 --- a/tests/integration_tests/functional/test_drive_virtio.py +++ b/tests/integration_tests/functional/test_drive_virtio.py @@ -383,3 +383,38 @@ def _check_mount(ssh_connection, dev_path): assert stderr == "" _, _, stderr = ssh_connection.run("umount /tmp", timeout=30.0) assert stderr == "" + + +def test_device_reset(uvm_plain_any): + """ + Test that virtio-block device reset works. + """ + vm = uvm_plain_any + vm.spawn() + vm.basic_config() + vm.add_net_iface() + + fs = drive_tools.FilesystemFile(os.path.join(vm.fsfiles, "scratch"), size=2) + vm.add_drive("scratch", fs.path) + vm.start() + + # Verify the scratch drive is accessible. + vm.ssh.check_output("mount /dev/vdb /tmp && umount /tmp") + + # Find the virtio device backing vdb and unbind it. + virtio_dev = vm.ssh.check_output( + "basename $(readlink /sys/block/vdb/device)" + ).stdout.strip() + + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_blk/unbind" + ) + + # Verify the drive is gone. + ret = vm.ssh.run("ls /dev/vdb") + assert ret.returncode != 0 + + # Rebind and verify the drive is back. + vm.ssh.check_output(f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_blk/bind") + vm.ssh.check_output("ls /dev/vdb") + vm.ssh.check_output("mount /dev/vdb /tmp && umount /tmp") From cb5f9fb604f32b8add16a5e0c62bfe40ae53bfc1 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:15:00 +0100 Subject: [PATCH 15/25] virtio: pmem: Implement device reset Pmem does not have any backend specific state that needs resetting so implement the method by simply returning true. The rest of the state is handled by the generic reset(). Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/pmem/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vmm/src/devices/virtio/pmem/device.rs b/src/vmm/src/devices/virtio/pmem/device.rs index 2331111a3b7..1ae19c25bcb 100644 --- a/src/vmm/src/devices/virtio/pmem/device.rs +++ b/src/vmm/src/devices/virtio/pmem/device.rs @@ -593,7 +593,7 @@ impl VirtioDevice for Pmem { } fn _reset(&mut self) -> bool { - false + true } fn kick(&mut self) { From e1d4372421ec165ffea3742e27371bc86c884065 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:22:07 +0100 Subject: [PATCH 16/25] tests: Add integration test for virtio-pmem device reset Add a test that verifies virtio-pmem device reset works end-to-end by unbinding and rebinding the guest driver and checking the device remains functional. Signed-off-by: Ilias Stamatis --- .../integration_tests/functional/test_pmem.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/integration_tests/functional/test_pmem.py b/tests/integration_tests/functional/test_pmem.py index ad7fc35020e..904438c61e4 100644 --- a/tests/integration_tests/functional/test_pmem.py +++ b/tests/integration_tests/functional/test_pmem.py @@ -226,3 +226,40 @@ def test_pmem_dax_memory_saving( assert ( pmem_rss_usage < block_rss_usage ), f"{block_cache_usage} <= {pmem_cache_usage}" + + +def test_device_reset(uvm_plain_any): + """ + Test that virtio-pmem device reset works. + """ + vm = uvm_plain_any + vm.spawn() + vm.basic_config(add_root_device=True) + vm.add_net_iface() + + fs = drive_tools.FilesystemFile(os.path.join(vm.fsfiles, "scratch"), size=2) + vm.add_pmem("pmem_scratch", fs.path, False, False) + vm.start() + + # Verify the pmem device is accessible. + vm.ssh.check_output("ls /dev/pmem0") + + virtio_dev = vm.ssh.check_output( + "basename $(realpath /sys/block/pmem0/device/../../..)" + ).stdout.strip() + + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_pmem/unbind" + ) + + # Verify the device is gone. + ret = vm.ssh.run("ls /dev/pmem0") + assert ret.returncode != 0 + + # Rebind and verify the device is functional. + vm.ssh.check_output(f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_pmem/bind") + vm.ssh.check_output("mount /dev/pmem0 /tmp") + vm.ssh.check_output("echo reset_test > /tmp/testfile") + ret = vm.ssh.check_output("cat /tmp/testfile") + assert "reset_test" in ret.stdout + vm.ssh.check_output("umount /tmp") From 44fea5a6d62fe489306b6df10ebfe3d59d92e3c9 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:37:26 +0100 Subject: [PATCH 17/25] virtio: balloon: Implement device reset Implement the reset() method for the virtio-balloon device, resetting the balloon specific state in-place. Do not deflate the balloon on reset. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/balloon/device.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vmm/src/devices/virtio/balloon/device.rs b/src/vmm/src/devices/virtio/balloon/device.rs index 5c98db1b6d7..cb25a20861b 100644 --- a/src/vmm/src/devices/virtio/balloon/device.rs +++ b/src/vmm/src/devices/virtio/balloon/device.rs @@ -984,7 +984,10 @@ impl VirtioDevice for Balloon { } fn _reset(&mut self) -> bool { - false + self.stats_timer.arm(Duration::ZERO, None); + self.stats_desc_index = None; + self.hinting_state = Default::default(); + true } fn kick(&mut self) { From 492afa5900379d0691289a045d799f0a7b8c45a2 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:44:33 +0100 Subject: [PATCH 18/25] tests: Add integration test for virtio-balloon device reset Add a test that verifies virtio-balloon device reset works end-to-end by unbinding and rebinding the guest driver and checking the device remains functional. Signed-off-by: Ilias Stamatis --- .../functional/test_balloon.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/integration_tests/functional/test_balloon.py b/tests/integration_tests/functional/test_balloon.py index 44d9b9b6f37..2be3261f181 100644 --- a/tests/integration_tests/functional/test_balloon.py +++ b/tests/integration_tests/functional/test_balloon.py @@ -622,3 +622,46 @@ def test_memory_scrub(uvm_plain_any, method): microvm.ssh.check_output("/usr/local/bin/readmem {} {}".format(60, 1)) check_guest_dmesg_for_stalls(microvm.ssh) + + +def test_device_reset(uvm_plain_any): + """ + Test that virtio-balloon device reset works. + """ + vm = uvm_plain_any + vm.spawn() + vm.basic_config() + vm.add_net_iface() + vm.api.balloon.put(amount_mib=0, deflate_on_oom=True, stats_polling_interval_s=0) + vm.start() + + # Find the virtio balloon device. + virtio_dev = vm.ssh.check_output( + "ls -d /sys/bus/virtio/drivers/virtio_balloon/virtio* | xargs -n1 basename" + ).stdout.strip() + + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_balloon/unbind" + ) + + # Verify the balloon is gone. + ret = vm.ssh.run("ls /sys/bus/virtio/drivers/virtio_balloon/virtio*") + assert ret.returncode != 0 + + # Rebind and verify it's back. + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_balloon/bind" + ) + + # Verify the balloon is functional by inflating it and checking that guest + # free memory decreases. + meminfo = MeminfoGuest(vm) + free_before = meminfo.get().mem_free.kib() + + vm.api.balloon.patch(amount_mib=64) + _ = get_stable_rss_mem(vm) + + free_after = meminfo.get().mem_free.kib() + # Inflating 64 MiB should reclaim at least 85% of that from guest free + # memory. The 15% slack accounts for kernel accounting overhead. + assert free_after <= free_before - 64 * 1024 * 85 // 100 From 2cc19721efad14c788c66a9d1b449fa262aa9f7a Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:37:33 +0100 Subject: [PATCH 19/25] virtio: entropy: Implement device reset The entropy device does not have any backend specific state that needs resetting so implement the method by simply returning true. The test_failed_reset_blocks_reinitialization() test used an entropy device assuming it doesn't support reset. Since it now does, adjust the test to check that the device is first deactivated after a reset and then the driver can perform the initialization sequence again. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/rng/device.rs | 2 +- .../devices/virtio/transport/pci/device.rs | 44 +++++-------------- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/src/vmm/src/devices/virtio/rng/device.rs b/src/vmm/src/devices/virtio/rng/device.rs index 4a35c22934b..e0da1f08f53 100644 --- a/src/vmm/src/devices/virtio/rng/device.rs +++ b/src/vmm/src/devices/virtio/rng/device.rs @@ -312,7 +312,7 @@ impl VirtioDevice for Entropy { } fn _reset(&mut self) -> bool { - false + true } fn activate( diff --git a/src/vmm/src/devices/virtio/transport/pci/device.rs b/src/vmm/src/devices/virtio/transport/pci/device.rs index 7db06537771..23f262420a1 100644 --- a/src/vmm/src/devices/virtio/transport/pci/device.rs +++ b/src/vmm/src/devices/virtio/transport/pci/device.rs @@ -1660,7 +1660,7 @@ mod tests { } #[test] - fn test_failed_reset_blocks_reinitialization() { + fn test_reset_and_reinitialization() { let mut vmm = create_vmm_with_virtio_pci_device(); let device = get_virtio_device(&vmm); let mut locked = device.lock().unwrap(); @@ -1676,40 +1676,20 @@ mod tests { assert!(locked.device_activated.load(Ordering::SeqCst)); // Write 0 to device_status to request a reset. - // Entropy's reset() returns None (unimplemented), so the reset fails. write_driver_status(&mut locked, 0); assert_eq!(read_driver_status(&mut locked), 0); - // device_activated stays true because the backend was not actually reset. - assert!(locked.device_activated.load(Ordering::SeqCst)); + // Device should be deactivated after successful reset. + assert!(!locked.device_activated.load(Ordering::SeqCst)); - // Attempt to re-initialize should be rejected because device_activated is - // still true while driver_status is INIT. + // Re-initialization should succeed after a successful reset. write_driver_status(&mut locked, ACKNOWLEDGE); - assert_eq!(read_driver_status(&mut locked), 0); - - // Save state and restore into a new device -- the combination of - // device_activated == true and driver_status == INIT is preserved in the - // snapshot, so the blocking behavior survives restore. - let saved_state = locked.state(); - drop(locked); - - // Fully drop the original device before constructing the restored copy: the restored - // copy reuses the same MSI-X GSIs, so the two `MsixVectorGroup` instances must never - // exist concurrently. This is how it will happen in real scenario anyway. - let kvm_vm = vmm.vm.as_kvm().unwrap().clone(); - let saved_allocator = kvm_vm.resource_allocator().clone(); - drop(device); - drop(vmm); - // Restore the allocator state so the restored group's GSIs are marked allocated and - // its `MsixVectorGroup::drop` will succeed when the test ends. - *kvm_vm.resource_allocator() = saved_allocator; - - let new_entropy = Arc::new(Mutex::new(Entropy::new(RateLimiter::default()).unwrap())); - let restored = - VirtioPciDevice::new_from_state("rng".to_string(), &kvm_vm, new_entropy, saved_state) - .unwrap(); - - assert!(restored.device_activated.load(Ordering::SeqCst)); - assert_eq!(restored.common_config.driver_status, 0); + assert_eq!(read_driver_status(&mut locked), ACKNOWLEDGE); + write_driver_status(&mut locked, ACKNOWLEDGE | DRIVER); + let features = read_device_features(&mut locked); + write_driver_features(&mut locked, features); + write_driver_status(&mut locked, ACKNOWLEDGE | DRIVER | FEATURES_OK); + setup_queues(&mut locked); + write_driver_status(&mut locked, ACKNOWLEDGE | DRIVER | FEATURES_OK | DRIVER_OK); + assert!(locked.device_activated.load(Ordering::SeqCst)); } } From 8bd739532359b55bdc2c769d9705fb405370f3a5 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:44:40 +0100 Subject: [PATCH 20/25] tests: Add integration test for virtio-rng device reset Add a test that verifies virtio-rng device reset works end-to-end by unbinding and rebinding the guest driver and checking the device remains functional. Signed-off-by: Ilias Stamatis --- .../integration_tests/functional/test_rng.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/integration_tests/functional/test_rng.py b/tests/integration_tests/functional/test_rng.py index 7eb26f95c50..5cdac9d8e7c 100644 --- a/tests/integration_tests/functional/test_rng.py +++ b/tests/integration_tests/functional/test_rng.py @@ -231,3 +231,35 @@ def test_rng_bw_rate_limiter(uvm_any): # Check the rate limiter using a request size equal to the size # of the token bucket. _check_entropy_rate_limited(vm.ssh, size, expected_kbps) + + +def test_device_reset(uvm_plain_any): + """ + Test that virtio-rng device reset works. + """ + vm = uvm_plain_any + vm.spawn() + vm.basic_config() + vm.add_net_iface() + vm.api.entropy.put() + vm.start() + + # Verify the rng device works. + vm.ssh.check_output("dd if=/dev/hwrng of=/dev/null bs=32 count=1") + + # Find the virtio rng device. + virtio_dev = vm.ssh.check_output( + "ls -d /sys/bus/virtio/drivers/virtio_rng/virtio* | xargs -n1 basename" + ).stdout.strip() + + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_rng/unbind" + ) + + # Verify the rng device is gone. + ret = vm.ssh.run("ls /sys/bus/virtio/drivers/virtio_rng/virtio*") + assert ret.returncode != 0 + + # Rebind and verify it works again. + vm.ssh.check_output(f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_rng/bind") + vm.ssh.check_output("dd if=/dev/hwrng of=/dev/null bs=32 count=1") From 72da556d1377ff61a0cc88d10e8274397e146b55 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:37:41 +0100 Subject: [PATCH 21/25] virtio: vsock: Implement device reset Implement the reset() method for the virtio-vsock device, resetting the vsock specific state in-place. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/vsock/device.rs | 5 +++- src/vmm/src/devices/virtio/vsock/mod.rs | 5 +++- src/vmm/src/devices/virtio/vsock/packet.rs | 12 ++++++++ .../src/devices/virtio/vsock/test_utils.rs | 4 ++- .../src/devices/virtio/vsock/unix/muxer.rs | 28 ++++++++++++++++++- 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/vmm/src/devices/virtio/vsock/device.rs b/src/vmm/src/devices/virtio/vsock/device.rs index 7d89ca906ca..dcd39f14514 100644 --- a/src/vmm/src/devices/virtio/vsock/device.rs +++ b/src/vmm/src/devices/virtio/vsock/device.rs @@ -394,7 +394,10 @@ where } fn _reset(&mut self) -> bool { - false + self.backend.reset(); + self.rx_packet.clear(); + self.tx_packet.clear(); + true } fn kick(&mut self) { diff --git a/src/vmm/src/devices/virtio/vsock/mod.rs b/src/vmm/src/devices/virtio/vsock/mod.rs index cc9f7746580..1ec87534af1 100644 --- a/src/vmm/src/devices/virtio/vsock/mod.rs +++ b/src/vmm/src/devices/virtio/vsock/mod.rs @@ -179,4 +179,7 @@ pub trait VsockChannel { /// The vsock backend, which is basically an epoll-event-driven vsock channel. /// Currently, the only implementation we have is `crate::devices::virtio::unix::muxer::VsockMuxer`, /// which translates guest-side vsock connections to host-side Unix domain socket connections. -pub trait VsockBackend: VsockChannel + VsockEpollListener + Send {} +pub trait VsockBackend: VsockChannel + VsockEpollListener + Send { + /// Reset the backend, dropping all active connections. + fn reset(&mut self); +} diff --git a/src/vmm/src/devices/virtio/vsock/packet.rs b/src/vmm/src/devices/virtio/vsock/packet.rs index 7253f41ce76..baac307e773 100644 --- a/src/vmm/src/devices/virtio/vsock/packet.rs +++ b/src/vmm/src/devices/virtio/vsock/packet.rs @@ -197,6 +197,12 @@ pub struct VsockPacketTx { } impl VsockPacketTx { + /// Clear the packet buffer. + pub fn clear(&mut self) { + self.hdr = Default::default(); + self.buffer.clear(); + } + /// Create the packet wrapper from a TX virtq chain head. /// /// ## Errors @@ -290,6 +296,12 @@ pub struct VsockPacketRx { } impl VsockPacketRx { + /// Clear the packet buffer. + pub fn clear(&mut self) { + self.hdr = Default::default(); + self.buffer.clear(); + } + /// Creates new VsockPacketRx. pub fn new() -> Result { let buffer = IoVecBufferMut::new().map_err(VsockError::IovDeque)?; diff --git a/src/vmm/src/devices/virtio/vsock/test_utils.rs b/src/vmm/src/devices/virtio/vsock/test_utils.rs index 3d4ab704975..30c86fad169 100644 --- a/src/vmm/src/devices/virtio/vsock/test_utils.rs +++ b/src/vmm/src/devices/virtio/vsock/test_utils.rs @@ -113,7 +113,9 @@ impl VsockEpollListener for TestBackend { self.evset = Some(evset); } } -impl VsockBackend for TestBackend {} +impl VsockBackend for TestBackend { + fn reset(&mut self) {} +} #[derive(Debug)] pub struct TestContext { diff --git a/src/vmm/src/devices/virtio/vsock/unix/muxer.rs b/src/vmm/src/devices/virtio/vsock/unix/muxer.rs index e2c28033ec7..7946ec1e7bb 100644 --- a/src/vmm/src/devices/virtio/vsock/unix/muxer.rs +++ b/src/vmm/src/devices/virtio/vsock/unix/muxer.rs @@ -305,7 +305,33 @@ impl VsockEpollListener for VsockMuxer { } } -impl VsockBackend for VsockMuxer {} +impl VsockBackend for VsockMuxer { + fn reset(&mut self) { + // Remove all connections and their epoll listeners. + let keys: Vec = self.conn_map.keys().copied().collect(); + for key in keys { + self.remove_connection(key); + } + + // Remove any pending LocalStream listeners (host sockets that + // connected but haven't sent a CONNECT command yet). Keep only + // the HostSock listener. + let stale_fds: Vec = self + .listener_map + .iter() + .filter(|(_, listener)| !matches!(listener, EpollListener::HostSock)) + .map(|(fd, _)| *fd) + .collect(); + for fd in stale_fds { + self.remove_listener(fd); + } + + self.rxq = MuxerRxQ::new(); + self.killq = MuxerKillQ::new(); + self.local_port_set.clear(); + self.local_port_last = (1u32 << 30) - 1; + } +} impl VsockMuxer { /// Muxer constructor. From f5e6911751e66f2ac1d018a4b599695a0b00f829 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:44:48 +0100 Subject: [PATCH 22/25] tests: Add integration test for virtio-vsock device reset Add a test that verifies virtio-vsock device reset works end-to-end by unbinding and rebinding the guest driver and checking the device remains functional. Signed-off-by: Ilias Stamatis --- .../functional/test_vsock.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/integration_tests/functional/test_vsock.py b/tests/integration_tests/functional/test_vsock.py index 7dd96ba7fc4..3a544d97c7e 100644 --- a/tests/integration_tests/functional/test_vsock.py +++ b/tests/integration_tests/functional/test_vsock.py @@ -367,3 +367,44 @@ def test_vsock_override_fails_without_device(uvm_plain_any, microvm_factory): ) vm2.mark_killed() + + +def test_device_reset(uvm_plain_any): + """ + Test that virtio-vsock device reset works. + """ + vm = uvm_plain_any + vm.spawn() + vm.basic_config() + vm.add_net_iface() + vm.api.vsock.put(guest_cid=3, vsock_id="vsock0", uds_path="/" + VSOCK_UDS_PATH) + vm.start() + + # Establish a vsock connection before reset so there is active state in the + # backend that must be cleaned up. + uds_path = start_guest_echo_server(vm) + blob_path, blob_hash = make_blob(vm.path) + check_host_connections(uds_path, blob_path, blob_hash) + + # Find the virtio vsock device. + virtio_dev = vm.ssh.check_output( + "ls -d /sys/bus/virtio/drivers/vmw_vsock_virtio_transport/virtio*" + " | xargs -n1 basename" + ).stdout.strip() + + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/vmw_vsock_virtio_transport/unbind" + ) + + # Verify the vsock device is gone. + ret = vm.ssh.run("ls /sys/bus/virtio/drivers/vmw_vsock_virtio_transport/virtio*") + assert ret.returncode != 0 + + # Rebind and verify the data path works by starting a guest echo server + # and sending data from the host through the vsock. + vm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/vmw_vsock_virtio_transport/bind" + ) + uds_path = start_guest_echo_server(vm) + blob_path, blob_hash = make_blob(vm.path) + check_host_connections(uds_path, blob_path, blob_hash) From 406c9bd677f12a259f0b7d4bc3103b3a6fd71a16 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Thu, 14 May 2026 17:37:48 +0100 Subject: [PATCH 23/25] virtio: mem: Implement device reset The virtio-mem device does not have any backend specific state that needs resetting so implement the method by simply returning true. Plugged memory regions remain intact. Signed-off-by: Ilias Stamatis --- src/vmm/src/devices/virtio/mem/device.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vmm/src/devices/virtio/mem/device.rs b/src/vmm/src/devices/virtio/mem/device.rs index 3d4e2f68ddd..ffc4e22ad23 100644 --- a/src/vmm/src/devices/virtio/mem/device.rs +++ b/src/vmm/src/devices/virtio/mem/device.rs @@ -661,7 +661,14 @@ impl VirtioDevice for VirtioMem { } fn _reset(&mut self) -> bool { - false + // Virtio spec, section 5.15.5.2: + // The device MUST NOT change the state of memory blocks during device + // reset. The device MUST NOT modify memory or memory properties of + // plugged memory blocks during device reset. + // + // Note: the Linux virtio-mem driver does not support rebinding when + // memory is plugged + true } fn activate( From 5b400f68c309eb23659b946a648ebc66ff848471 Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Fri, 15 May 2026 15:38:14 +0100 Subject: [PATCH 24/25] tests: Add integration test for virtio-mem device reset Add a test that verifies virtio-mem device reset works end-to-end by unbinding and rebinding the guest driver and checking the device remains functional. Signed-off-by: Ilias Stamatis --- .../performance/test_hotplug_memory.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/integration_tests/performance/test_hotplug_memory.py b/tests/integration_tests/performance/test_hotplug_memory.py index d6b6cee8da6..8108f560eaa 100644 --- a/tests/integration_tests/performance/test_hotplug_memory.py +++ b/tests/integration_tests/performance/test_hotplug_memory.py @@ -499,3 +499,29 @@ def test_memory_hotplug_latency( timed_memory_hotplug(uvm, 0, metrics, "hotunplug", "unplug_agg") timed_memory_hotplug(uvm, hotplug_size, metrics, "hotplug_2nd", "plug_agg") uvm.kill() + + +def test_device_reset(uvm_plain): + """ + Test that virtio-mem device reset works. + + Note: the Linux virtio-mem driver does not support rebinding when memory is + plugged (the resource region can't be re-registered), so we reset without + any plugged memory and verify the device is functional afterwards. + """ + config = {"total_size_mib": 1024, "slot_size_mib": 128, "block_size_mib": 2} + uvm = uvm_booted_memhp(uvm_plain, None, None, False, config, None, None, None) + + # Reset the device via driver unbind/bind. + virtio_dev = uvm.ssh.check_output( + "ls -d /sys/bus/virtio/drivers/virtio_mem/virtio* | xargs -n1 basename" + ).stdout.strip() + + uvm.ssh.check_output( + f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_mem/unbind" + ) + uvm.ssh.check_output(f"echo {virtio_dev} > /sys/bus/virtio/drivers/virtio_mem/bind") + + # Verify the device is functional after reset by hotplugging memory. + # check_hotplug() asserts that guest mem_total reflects the new size. + check_hotplug(uvm, 256) From df8a0e5d28f02d0f3a4de01821dd1f651480a24c Mon Sep 17 00:00:00 2001 From: Ilias Stamatis Date: Fri, 15 May 2026 17:30:00 +0100 Subject: [PATCH 25/25] CHANGELOG: Mention virtio device reset support https://github.com/firecracker-microvm/firecracker/pull/5891 Signed-off-by: Ilias Stamatis --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79153c990dc..75912fedbd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to rescan the PCI bus after hotplug and remove the device before unplug since no automatic notification mechanism is implemented yet. More information can be found in the [Device Hotplugging](docs/device-hotplug.md) documentation page. +- [#5891](https://github.com/firecracker-microvm/firecracker/pull/5891): Added + support for virtio device reset. - [#5323](https://github.com/firecracker-microvm/firecracker/pull/5323): Add support for Vsock Unix domain socket path overriding on snapshot restore. More information can be found in the