Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
36223f5
virtio: Change VirtioDevice::reset() return type to bool
ilstam May 12, 2026
31abcb9
virtio: mmio: Simplify MMIO transport reset logic
ilstam May 13, 2026
9d058aa
virtio: pci: Fix deadlock in device reset path
ilstam May 14, 2026
f2e233a
virtio: pci: Keep interrupt object across device reset
ilstam May 14, 2026
77ed1c2
virtio: pci: Reset feature select registers on device reset
ilstam May 14, 2026
82b3d5f
virtio: mmio: Add unit test for device reset failure
ilstam May 13, 2026
20cd54f
virtio: Add deactivate() method to VirtioDevice trait
ilstam May 15, 2026
3d106c7
virtio: Move queue reset from transport to VirtioDevice::reset()
ilstam May 15, 2026
2a21987
virtio: Complete reset() and introduce _reset()
ilstam May 15, 2026
da90c82
virtio: net: Add a RxBuffers::clear() method
ilstam May 13, 2026
3029da9
virtio: net: Implement device reset
ilstam May 13, 2026
6c585ba
tests: Add integration test for virtio-net device reset
ilstam May 13, 2026
95f58ad
virtio: block: Implement device reset
ilstam May 14, 2026
696bb5a
tests: Add integration test for virtio-block device reset
ilstam May 14, 2026
cb5f9fb
virtio: pmem: Implement device reset
ilstam May 14, 2026
e1d4372
tests: Add integration test for virtio-pmem device reset
ilstam May 14, 2026
44fea5a
virtio: balloon: Implement device reset
ilstam May 14, 2026
492afa5
tests: Add integration test for virtio-balloon device reset
ilstam May 14, 2026
2cc1972
virtio: entropy: Implement device reset
ilstam May 14, 2026
8bd7395
tests: Add integration test for virtio-rng device reset
ilstam May 14, 2026
72da556
virtio: vsock: Implement device reset
ilstam May 14, 2026
f5e6911
tests: Add integration test for virtio-vsock device reset
ilstam May 14, 2026
406c9bd
virtio: mem: Implement device reset
ilstam May 14, 2026
5b400f6
tests: Add integration test for virtio-mem device reset
ilstam May 15, 2026
df8a0e5
CHANGELOG: Mention virtio device reset support
ilstam May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we check if we need to upated other documentation files?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What other documentation files did you have in mind?

support for Vsock Unix domain socket path overriding on snapshot restore. More
information can be found in the
Expand Down
6 changes: 6 additions & 0 deletions src/vmm/src/device_manager/mmio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,12 @@ pub(crate) mod tests {
fn is_activated(&self) -> bool {
false
}

fn deactivate(&mut self) {}

fn _reset(&mut self) -> bool {
false
}
}

#[test]
Expand Down
11 changes: 11 additions & 0 deletions src/vmm/src/devices/virtio/balloon/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,17 @@ impl VirtioDevice for Balloon {
self.device_state.is_activated()
}

fn deactivate(&mut self) {
self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
Comment thread
ilstam marked this conversation as resolved.
self.stats_timer.arm(Duration::ZERO, None);
self.stats_desc_index = None;
self.hinting_state = Default::default();
true
}

fn kick(&mut self) {
if self.is_activated() {
if self.free_page_hinting() {
Expand Down
14 changes: 14 additions & 0 deletions src/vmm/src/devices/virtio/block/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,20 @@ impl VirtioDevice for Block {
}
}

fn deactivate(&mut self) {
match self {
Self::Virtio(b) => b.deactivate(),
Self::VhostUser(b) => b.deactivate(),
}
}

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(),
Expand Down
8 changes: 8 additions & 0 deletions src/vmm/src/devices/virtio/block/vhost_user/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,14 @@ where
fn is_activated(&self) -> bool {
self.device_state.is_activated()
}

fn deactivate(&mut self) {
self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
false
}
}

#[cfg(test)]
Expand Down
9 changes: 9 additions & 0 deletions src/vmm/src/devices/virtio/block/virtio/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,15 @@ impl VirtioDevice for VirtioBlock {
fn is_activated(&self) -> bool {
self.device_state.is_activated()
}

fn deactivate(&mut self) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about the rate limiters? are we assuming we want to preserve the state across reset?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not?

self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
self.is_io_engine_throttled = false;
true
}
}

impl Drop for VirtioBlock {
Expand Down
28 changes: 24 additions & 4 deletions src/vmm/src/devices/virtio/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,24 @@ 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<dyn VirtioInterrupt>, Vec<EventFd>)> {
None
/// Set the device state to Inactive
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);
}
self._reset()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be called before the rest? This is a semantic change from the old code where a failed reset left the device activated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It left it activated in Firecracker but set the state as FAILED so in theory the guest shouldn't continue using it. But does it matter much since we'll support reset for all devices except for vhost-user-block (for now)?

}

/// 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() {
Expand Down Expand Up @@ -310,6 +322,14 @@ pub(crate) mod tests {
fn is_activated(&self) -> bool {
todo!()
}

fn deactivate(&mut self) {
todo!()
}

fn _reset(&mut self) -> bool {
todo!()
}
}

#[test]
Expand Down
15 changes: 15 additions & 0 deletions src/vmm/src/devices/virtio/mem/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,21 @@ impl VirtioDevice for VirtioMem {
self.device_state.is_activated()
}

fn deactivate(&mut self) {
self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
// 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
}

Comment thread
ilstam marked this conversation as resolved.
fn activate(
&mut self,
mem: GuestMemoryMmap,
Expand Down
31 changes: 31 additions & 0 deletions src/vmm/src/devices/virtio/net/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1088,6 +1097,16 @@ impl VirtioDevice for Net {
self.device_state.is_activated()
}

fn deactivate(&mut self) {
self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
self.rx_buffer.clear();
self.tx_buffer.clear();
true
}

/// Prepare saving state
fn prepare_save(&mut self) {
// We shouldn't be messing with the queue if the device is not activated.
Expand Down Expand Up @@ -2601,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);
}
}
8 changes: 8 additions & 0 deletions src/vmm/src/devices/virtio/pmem/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,14 @@ impl VirtioDevice for Pmem {
self.device_state.is_activated()
}

fn deactivate(&mut self) {
self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
true
}

fn kick(&mut self) {
if self.is_activated() {
info!("kick pmem {}.", self.config.id);
Expand Down
13 changes: 0 additions & 13 deletions src/vmm/src/devices/virtio/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
8 changes: 8 additions & 0 deletions src/vmm/src/devices/virtio/rng/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,14 @@ impl VirtioDevice for Entropy {
self.device_state.is_activated()
}

fn deactivate(&mut self) {
self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
true
}

fn activate(
&mut self,
mem: GuestMemoryMmap,
Expand Down
64 changes: 38 additions & 26 deletions src/vmm/src/devices/virtio/transport/mmio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -165,7 +162,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::*;

Expand All @@ -183,25 +179,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;
let reset_result = locked_device.reset();
match reset_result {
Some((_interrupt_evt, mut _queue_evts)) => {}
None => {
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()
Expand Down Expand Up @@ -506,6 +488,7 @@ pub(crate) mod tests {
device_activated: bool,
config_bytes: [u8; 0xeff],
activate_should_error: bool,
reset_should_fail: bool,
}

impl DummyDevice {
Expand All @@ -522,6 +505,7 @@ pub(crate) mod tests {
device_activated: false,
config_bytes: [0; 0xeff],
activate_should_error: false,
reset_should_fail: false,
}
}

Expand Down Expand Up @@ -600,6 +584,14 @@ pub(crate) mod tests {
fn is_activated(&self) -> bool {
self.device_activated
}

fn deactivate(&mut self) {
self.device_activated = false;
}

fn _reset(&mut self) -> bool {
!self.reset_should_fail
}
}

fn set_device_status(d: &mut MmioTransport, status: u32) {
Expand All @@ -613,8 +605,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().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,
Expand Down Expand Up @@ -1144,11 +1135,32 @@ 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_eq!(d.device_status, device_status::INIT);
assert!(!d.locked_device().is_activated());
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]
Expand Down
Loading
Loading