virtio-devices: pci: Clear all config state on reset

Per the virtio spec a device reset must return the device to its
power-on state. The reset path was only zeroing queue_select. Add
VirtioPciCommonConfig::reset() and call it from the transport's reset
path so the configuration is cleared. Also relax the condition to allow
the device to be reset at any time to match the spec.

Signed-off-by: Rob Bradford <rbradford@meta.com>
This commit is contained in:
Rob Bradford
2026-04-18 12:21:39 +01:00
parent d573f1bf96
commit fe6bcd0376
2 changed files with 60 additions and 17 deletions

View File

@@ -160,6 +160,19 @@ impl VirtioPciCommonConfig {
}
}
/// Returns the common configuration to its power-on state. Per the virtio
/// spec a device reset must restore the values that a fresh driver would
/// observe.
pub fn reset(&mut self) {
self.driver_status.store(0, Ordering::Release);
self.device_feature_select = 0;
self.driver_feature_select = 0;
self.queue_select = 0;
self.msix_config
.store(VIRTQ_MSI_NO_VECTOR, Ordering::Release);
self.msix_queues.lock().unwrap().fill(VIRTQ_MSI_NO_VECTOR);
}
pub fn read(&mut self, offset: u64, data: &mut [u8], queues: &[Queue]) {
assert!(data.len() <= 8);
@@ -521,4 +534,38 @@ mod unit_tests {
// Write queue_msix_vector — must not panic.
regs.write(0x1a, &[0xAB, 0xCD], &mut queues);
}
#[test]
fn reset_returns_initial_state() {
let dev: Arc<Mutex<dyn VirtioDevice>> = Arc::new(Mutex::new(DummyDevice(0)));
let mut regs = VirtioPciCommonConfig {
device: dev,
driver_status: Arc::new(AtomicU8::new(0x55)),
config_generation: 0xab,
device_feature_select: 1,
driver_feature_select: 1,
queue_select: 7,
msix_config: Arc::new(AtomicU16::new(3)),
msix_queues: Arc::new(Mutex::new(vec![1, 2, 3])),
};
regs.reset();
assert_eq!(regs.driver_status.load(Ordering::Acquire), 0);
assert_eq!(regs.config_generation, 0xab); // unchanged across reset
assert_eq!(regs.device_feature_select, 0);
assert_eq!(regs.driver_feature_select, 0);
assert_eq!(regs.queue_select, 0);
assert_eq!(
regs.msix_config.load(Ordering::Acquire),
VIRTQ_MSI_NO_VECTOR
);
assert!(
regs.msix_queues
.lock()
.unwrap()
.iter()
.all(|v| *v == VIRTQ_MSI_NO_VECTOR)
);
}
}

View File

@@ -1245,24 +1245,20 @@ impl PciDevice for VirtioPciDevice {
return Some(barrier);
}
// 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();
if let Some(virtio_interrupt) = device.reset() {
// Upon reset the device returns its interrupt EventFD
self.virtio_interrupt = Some(virtio_interrupt);
self.device_activated.store(false, Ordering::SeqCst);
// Reset queue readiness (changes queue_enable), queue sizes
// and selected_queue as per spec for reset
self.queues.iter_mut().for_each(Queue::reset);
self.common_config.queue_select = 0;
} else {
error!("Attempt to reset device when not implemented in underlying device");
self.common_config
.driver_status
.store(crate::DEVICE_FAILED as u8, Ordering::SeqCst);
// The driver requested a reset by writing 0 to device_status. Per the
// virtio spec this is permitted at any point in initialisation.
if self.is_driver_init() {
if self.device_activated.swap(false, Ordering::SeqCst) {
let mut device = self.device.lock().unwrap();
if let Some(virtio_interrupt) = device.reset() {
// Upon reset the device returns its interrupt EventFD
self.virtio_interrupt = Some(virtio_interrupt);
}
}
// Reset queue readiness and the common configuration
self.queues.iter_mut().for_each(Queue::reset);
self.common_config.reset();
}
None