diff --git a/devices/src/pci/pci_address.rs b/devices/src/pci/pci_address.rs index bed43caad4..966fd9f6ab 100644 --- a/devices/src/pci/pci_address.rs +++ b/devices/src/pci/pci_address.rs @@ -47,7 +47,7 @@ pub enum Error { pub type Result = std::result::Result; /// PCI Device Address, AKA Bus:Device.Function -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PciAddress { /// Bus number, in the range `0..=255`. pub bus: u8, diff --git a/devices/src/pci/stub.rs b/devices/src/pci/stub.rs index 120962417f..594f65d05b 100644 --- a/devices/src/pci/stub.rs +++ b/devices/src/pci/stub.rs @@ -12,6 +12,7 @@ //! scanned if function 0 is present. A stub PCI device is useful in that situation to present //! something to the guest on function 0. +use anyhow::Context; use base::RawDescriptor; use resources::Alloc; use resources::SystemAllocator; @@ -106,8 +107,13 @@ pub struct StubPciParameters { pub revision: u8, } +#[derive(Serialize, Deserialize)] pub struct StubPciDevice { + #[serde(default)] + #[serde(skip)] requested_address: PciAddress, + #[serde(default)] + #[serde(skip)] assigned_address: Option, config_regs: PciConfiguration, } @@ -210,6 +216,17 @@ impl Suspendable for StubPciDevice { // There are no workers to sleep/wake. Ok(()) } + + fn snapshot(&self) -> anyhow::Result { + serde_json::to_value(self).context("failed to snapshot") + } + + fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { + let restored_device: StubPciDevice = + serde_json::from_value(data).context("failed to restore snapshot")?; + *self = restored_device; + Ok(()) + } } #[cfg(test)] @@ -365,4 +382,54 @@ mod test { assert_eq!(params.class.programming_interface, 0x45); assert_eq!(params.revision, 52); } + + #[test] + fn stub_pci_device_snapshot_restore() -> anyhow::Result<()> { + let mut device = StubPciDevice::new(&CONFIG); + let init_reg_value = device.read_config_register(1); + let snapshot_init = device.snapshot().unwrap(); + + // Modify config reg 1 and make sure it went through. + let new_reg_value: u32 = 0xCAFE; + device.write_config_register(1, 0, &new_reg_value.to_le_bytes()); + assert_eq!(device.read_config_register(1), new_reg_value); + + // Capture a snapshot after the modification. + let mut snapshot_modified = device.snapshot().unwrap(); + assert_ne!(snapshot_init, snapshot_modified); + + // Modify the same register and verify that it's restored correctly. + device.write_config_register(1, 0, &[0xBA, 0xBA]); + assert_ne!(device.read_config_register(1), new_reg_value); + assert_ne!(device.read_config_register(1), init_reg_value); + device.restore(snapshot_init.clone())?; + assert_eq!(device.read_config_register(1), init_reg_value); + + // Capture a snapshot after restoring the initial snapshot. + let mut snapshot_restored = device.snapshot().unwrap(); + assert_eq!(snapshot_init, snapshot_restored); + + // Restore to the first modification and verify the values. + device.restore(snapshot_modified.clone())?; + assert_eq!(device.read_config_register(1), new_reg_value); + snapshot_restored = device.snapshot().unwrap(); + assert_eq!(snapshot_modified, snapshot_restored); + + /* + Restore the initial snapshot and verify that addresses are not encoded. + The addresses are only configurable during VM creation so they never + change afterwards and are not part of the snapshot. Force a change + to requested_address to confirm that. + */ + device.restore(snapshot_init.clone())?; + device.requested_address = PciAddress { + bus: 0x0d, + dev: 0x0e, + func: 0x4, + }; + snapshot_modified = device.snapshot().unwrap(); + assert_eq!(snapshot_init, snapshot_modified); + + Ok(()) + } }