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
dc09afb
virtio: Change VirtioDevice::reset() return type to bool
ilstam May 12, 2026
03603b0
virtio: mmio: Simplify MMIO transport reset logic
ilstam May 13, 2026
140072a
virtio: pci: Fix deadlock in device reset path
ilstam May 14, 2026
05ea8e3
virtio: pci: Keep interrupt object across device reset
ilstam May 14, 2026
ff1e508
virtio: pci: Reset feature select registers on device reset
ilstam May 14, 2026
fb227c3
virtio: mmio: Add unit test for device reset failure
ilstam May 13, 2026
ad333db
virtio: Add deactivate() method to VirtioDevice trait
ilstam May 15, 2026
43500bc
virtio: Move queue reset from transport to VirtioDevice::reset()
ilstam May 15, 2026
17501db
virtio: Complete reset() and introduce _reset()
ilstam May 15, 2026
05d33f0
virtio: net: Add a RxBuffers::clear() method
ilstam May 13, 2026
511ed8c
virtio: net: Implement device reset
ilstam May 13, 2026
d03aae7
tests: Add integration test for virtio-net device reset
ilstam May 13, 2026
ebd0dbf
virtio: block: Implement device reset
ilstam May 14, 2026
d0bdb3b
tests: Add integration test for virtio-block device reset
ilstam May 14, 2026
2e6d6f9
virtio: pmem: Implement device reset
ilstam May 14, 2026
dfacf7e
tests: Add integration test for virtio-pmem device reset
ilstam May 14, 2026
93f3bba
virtio: balloon: Implement device reset
ilstam May 14, 2026
cd9e5df
tests: Add integration test for virtio-balloon device reset
ilstam May 14, 2026
29a6f2e
virtio: entropy: Implement device reset
ilstam May 14, 2026
f27986e
tests: Add integration test for virtio-rng device reset
ilstam May 14, 2026
7528064
virtio: vsock: Implement device reset
ilstam May 14, 2026
208119d
tests: Add integration test for virtio-vsock device reset
ilstam May 14, 2026
a041c80
virtio: mem: Implement device reset
ilstam May 14, 2026
93dbf36
tests: Add integration test for virtio-mem device reset
ilstam May 15, 2026
6f0ab8e
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 @@ -28,6 +28,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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Returning false here combined with the new MMIO path means any status=0 write after ACKNOWLEDGE permanently sets FAILED for vhost-user-block.

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.

As noted in the other comment, even before this PR we'd set the device to FAILED state if we got a reset after DRIVER_OK. Does it matter now that this can happen after ACKNOWLEDGE?

}
}

#[cfg(test)]
Expand Down
13 changes: 13 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,19 @@ 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?

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.

I think we must since otherwise guest can bypass rate limiter by resetting

self.device_state = DeviceState::Inactive;
}

fn _reset(&mut self) -> bool {
if let Err(err) = self.disk.file_engine.reset() {
error!("Failed to reset block IO engine: {:?}", err);
return false;
}
self.is_io_engine_throttled = false;
Comment thread
ilstam marked this conversation as resolved.
true
}
}

impl Drop for VirtioBlock {
Expand Down
7 changes: 7 additions & 0 deletions src/vmm/src/devices/virtio/block/virtio/io/async_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ impl AsyncFileEngine {
Ok(())
}

pub fn reset_ring(&mut self) -> Result<(), AsyncIoError> {
let ring = Self::new_ring(&self.file, self.completion_evt.as_raw_fd())
.map_err(AsyncIoError::IoUring)?;
self.ring = ring;
Ok(())
}

#[cfg(test)]
pub fn file(&self) -> &File {
&self.file
Expand Down
7 changes: 7 additions & 0 deletions src/vmm/src/devices/virtio/block/virtio/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ impl FileEngine {
}
}

pub fn reset(&mut self) -> Result<(), BlockIoError> {
match self {
FileEngine::Async(engine) => engine.reset_ring().map_err(BlockIoError::Async),
FileEngine::Sync(_) => Ok(()),
}
}

pub fn drain(&mut self, discard: bool) -> Result<(), BlockIoError> {
match self {
FileEngine::Async(engine) => engine.drain(discard).map_err(BlockIoError::Async),
Expand Down
31 changes: 27 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,27 @@ 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should "must not be overridden" be enforced somehow?

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.

I felt the same, but unfortunately Rust doesn't seem to have a final keyword like C++. I'm open to suggestions though if you can think of a different way to do it.

fn reset(&mut self) -> bool {
if !self._reset() {
return false;
}
self.deactivate();
self.set_acked_features(0);
for queue in self.queues_mut() {
*queue = Queue::new(queue.max_size);
}
true
}

/// 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 +325,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 @@ -683,19 +683,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
Loading
Loading