mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-09 20:04:20 +00:00
devices: adding Suspendable trait to BusDevice and PciDevice
Add supertrait Suspednable to BusDevice and PciDevice Bug=b:232437513 Test=WIP Change-Id: I74162399091fc4bc5fdef4bbeefb2639148bd283 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3924745 Commit-Queue: Elie Kheirallah <khei@google.com> Reviewed-by: Noah Gold <nkgold@google.com> Auto-Submit: Elie Kheirallah <khei@google.com>
This commit is contained in:
parent
2cfd0293af
commit
eb27c46876
29 changed files with 116 additions and 4 deletions
|
@ -36,6 +36,7 @@ use crate::BusDevice;
|
|||
use crate::BusResumeDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ACPIPMError {
|
||||
|
@ -217,6 +218,8 @@ impl ACPIPMResource {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for ACPIPMResource {}
|
||||
|
||||
fn run_worker(
|
||||
sci_evt: IrqLevelEvent,
|
||||
kill_evt: Event,
|
||||
|
|
|
@ -28,6 +28,7 @@ use crate::BusAccessInfo;
|
|||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
/// Errors for battery devices.
|
||||
#[sorted]
|
||||
|
@ -472,3 +473,5 @@ impl Aml for GoldfishBattery {
|
|||
.to_aml_bytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for GoldfishBattery {}
|
||||
|
|
|
@ -26,6 +26,7 @@ use crate::BusStatistics;
|
|||
use crate::DeviceId;
|
||||
use crate::PciAddress;
|
||||
use crate::PciDevice;
|
||||
use crate::Suspendable;
|
||||
#[cfg(unix)]
|
||||
use crate::VfioPlatformDevice;
|
||||
use crate::VirtioMmioDevice;
|
||||
|
@ -80,7 +81,7 @@ pub enum BusType {
|
|||
/// The device does not care where it exists in address space as each method is only given an offset
|
||||
/// into its allocated portion of address space.
|
||||
#[allow(unused_variables)]
|
||||
pub trait BusDevice: Send {
|
||||
pub trait BusDevice: Send + Suspendable {
|
||||
/// Returns a label suitable for debug output.
|
||||
fn debug_label(&self) -> String;
|
||||
/// Returns a unique id per device type suitable for metrics gathering.
|
||||
|
|
|
@ -13,6 +13,7 @@ use crate::pci::CrosvmDeviceId;
|
|||
use crate::BusAccessInfo;
|
||||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
const INDEX_OFFSET: u64 = 0x0;
|
||||
|
@ -122,6 +123,8 @@ impl BusDevice for Cmos {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Cmos {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::NaiveDateTime;
|
||||
|
|
|
@ -20,6 +20,7 @@ use crate::BusAccessInfo;
|
|||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::SerialDevice;
|
||||
use crate::Suspendable;
|
||||
|
||||
const BOCHS_DEBUGCON_READBACK: u8 = 0xe9;
|
||||
|
||||
|
@ -88,6 +89,8 @@ impl Debugcon {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Debugcon {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io;
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::BusDevice;
|
|||
use crate::BusDeviceSync;
|
||||
use crate::BusRange;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
pub struct DirectIo {
|
||||
dev: Mutex<File>,
|
||||
|
@ -86,6 +87,8 @@ impl BusDeviceSync for DirectIo {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for DirectIo {}
|
||||
|
||||
pub struct DirectMmio {
|
||||
dev: Mutex<Vec<(BusRange, MemoryMapping)>>,
|
||||
read_only: bool,
|
||||
|
@ -216,3 +219,5 @@ impl BusDeviceSync for DirectMmio {
|
|||
self.iowr(ai, data);
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for DirectMmio {}
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::pci::CrosvmDeviceId;
|
|||
use crate::BusAccessInfo;
|
||||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
||||
pub struct I8042Device {
|
||||
|
@ -55,3 +56,5 @@ impl BusDevice for I8042Device {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for I8042Device {}
|
||||
|
|
|
@ -31,6 +31,7 @@ use crate::pci::CrosvmDeviceId;
|
|||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::IrqEventSource;
|
||||
use crate::Suspendable;
|
||||
|
||||
// ICH10 I/O APIC version: 0x20
|
||||
const IOAPIC_VERSION_ID: u32 = 0x00000020;
|
||||
|
@ -498,6 +499,8 @@ impl Ioapic {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Ioapic {}
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
enum IoapicError {
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::bus::BusAccessInfo;
|
|||
use crate::pci::CrosvmDeviceId;
|
||||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
pub struct Pic {
|
||||
// Indicates a pending INTR signal to LINT0 of vCPU, checked by vCPU thread.
|
||||
|
@ -538,6 +539,8 @@ impl Pic {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Pic {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// ICW4: Special fully nested mode with no auto EOI.
|
||||
|
|
|
@ -76,6 +76,7 @@ use crate::IrqEventSource;
|
|||
use crate::IrqLevelEvent;
|
||||
use crate::Pit;
|
||||
use crate::PitError;
|
||||
use crate::Suspendable;
|
||||
|
||||
/// PIT channel 0 timer is connected to IRQ 0
|
||||
const PIT_CHANNEL0_IRQ: u32 = 0;
|
||||
|
@ -860,6 +861,8 @@ impl<V: VcpuX86_64 + 'static> BusDevice for UserspaceIrqChip<V> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<V: VcpuX86_64 + 'static> Suspendable for UserspaceIrqChip<V> {}
|
||||
|
||||
impl<V: VcpuX86_64 + 'static> BusDeviceSync for UserspaceIrqChip<V> {
|
||||
fn read(&self, info: BusAccessInfo, data: &mut [u8]) {
|
||||
self.apics[info.id].lock().read(info.offset, data)
|
||||
|
|
|
@ -46,6 +46,7 @@ use crate::pci::PciDeviceError;
|
|||
use crate::pci::PciInterruptPin;
|
||||
use crate::pci::PCI_VENDOR_ID_INTEL;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
// Use 82801AA because it's what qemu does.
|
||||
const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415;
|
||||
|
@ -405,6 +406,8 @@ impl PciDevice for Ac97Dev {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Ac97Dev {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use resources::AddressRange;
|
||||
|
|
|
@ -80,6 +80,7 @@ use crate::pci::pci_device::Result as PciResult;
|
|||
use crate::pci::PciAddress;
|
||||
use crate::pci::PciDeviceError;
|
||||
use crate::vfio::VfioContainer;
|
||||
use crate::Suspendable;
|
||||
use crate::UnpinRequest;
|
||||
use crate::UnpinResponse;
|
||||
|
||||
|
@ -1675,3 +1676,5 @@ impl Drop for CoIommuDev {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for CoIommuDev {}
|
||||
|
|
|
@ -44,6 +44,7 @@ use crate::BusAccessInfo;
|
|||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -307,7 +308,7 @@ pub enum PreferredIrq {
|
|||
Fixed { pin: PciInterruptPin, gsi: u32 },
|
||||
}
|
||||
|
||||
pub trait PciDevice: Send {
|
||||
pub trait PciDevice: Send + Suspendable {
|
||||
/// Returns a label suitable for debug output.
|
||||
fn debug_label(&self) -> String;
|
||||
|
||||
|
@ -697,6 +698,24 @@ impl<T: PciDevice + ?Sized> PciDevice for Box<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: PciDevice + ?Sized> Suspendable for Box<T> {
|
||||
fn snapshot(&self) -> anyhow::Result<String> {
|
||||
(**self).snapshot()
|
||||
}
|
||||
|
||||
fn restore(&mut self, data: &str) -> anyhow::Result<()> {
|
||||
(**self).restore(data)
|
||||
}
|
||||
|
||||
fn sleep(&mut self) -> anyhow::Result<()> {
|
||||
(**self).sleep()
|
||||
}
|
||||
|
||||
fn wake(&mut self) -> anyhow::Result<()> {
|
||||
(**self).wake()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + PciDevice> BusDeviceObj for T {
|
||||
fn as_pci_device(&self) -> Option<&dyn PciDevice> {
|
||||
Some(self)
|
||||
|
@ -759,6 +778,8 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for TestDev {}
|
||||
|
||||
#[test]
|
||||
fn config_write_result() {
|
||||
let mut test_dev = TestDev {
|
||||
|
|
|
@ -33,6 +33,7 @@ use crate::BusAccessInfo;
|
|||
use crate::BusDevice;
|
||||
use crate::BusType;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
// A PciDevice that holds the root hub's configuration.
|
||||
struct PciRootConfiguration {
|
||||
|
@ -71,6 +72,8 @@ impl PciDevice for PciRootConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for PciRootConfiguration {}
|
||||
|
||||
// Command send to pci root worker thread to add/remove device from pci root
|
||||
pub enum PciRootCommand {
|
||||
Add(PciAddress, Arc<Mutex<dyn BusDevice>>),
|
||||
|
@ -428,6 +431,8 @@ impl BusDevice for PciConfigIo {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for PciConfigIo {}
|
||||
|
||||
/// Emulates PCI memory-mapped configuration access mechanism.
|
||||
pub struct PciConfigMmio {
|
||||
/// PCI root bridge.
|
||||
|
@ -493,7 +498,9 @@ impl BusDevice for PciConfigMmio {
|
|||
}
|
||||
}
|
||||
|
||||
/// Inspired by PCI configuration space, crosvm provides 2048 dword virtual registers (8KiB in
|
||||
impl Suspendable for PciConfigMmio {}
|
||||
|
||||
/// Inspired by PCI configuration space, CrosVM provides 2048 dword virtual registers (8KiB in
|
||||
/// total) for each PCI device. The guest can use these registers to exchange device-specific
|
||||
/// information with crosvm. The first 4kB is trapped by crosvm and crosvm supplies these
|
||||
/// register's emulation. The second 4KB is mapped into guest directly as shared memory, so
|
||||
|
@ -567,3 +574,5 @@ impl BusDevice for PciVirtualConfigMmio {
|
|||
.virtual_config_space_write(address, register, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for PciVirtualConfigMmio {}
|
||||
|
|
|
@ -31,6 +31,7 @@ use crate::pci::PciHeaderType;
|
|||
use crate::pci::PCI_VENDOR_ID_INTEL;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::PciInterruptPin;
|
||||
use crate::Suspendable;
|
||||
|
||||
pub const BR_BUS_NUMBER_REG: usize = 0x6;
|
||||
pub const BR_BUS_SUBORDINATE_OFFSET: usize = 0x2;
|
||||
|
@ -504,3 +505,5 @@ impl PciDevice for PciBridge {
|
|||
self.msi_config.lock().destroy()
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for PciBridge {}
|
||||
|
|
|
@ -36,6 +36,7 @@ use crate::pci::pci_device::Result;
|
|||
use crate::pci::PciAddress;
|
||||
use crate::pci::PciDeviceError;
|
||||
use crate::pci::PCI_VENDOR_ID_REDHAT;
|
||||
use crate::Suspendable;
|
||||
|
||||
const PCI_DEVICE_ID_REDHAT_PVPANIC: u16 = 0x0011;
|
||||
const PCI_PVPANIC_REVISION_ID: u8 = 1;
|
||||
|
@ -205,6 +206,8 @@ impl PciDevice for PvPanicPciDevice {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for PvPanicPciDevice {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use base::Tube;
|
||||
|
|
|
@ -28,6 +28,7 @@ use crate::pci::pci_device::PciDevice;
|
|||
use crate::pci::pci_device::Result;
|
||||
use crate::pci::PciAddress;
|
||||
use crate::pci::PciDeviceError;
|
||||
use crate::Suspendable;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct StubPciParameters {
|
||||
|
@ -136,6 +137,8 @@ impl PciDevice for StubPciDevice {
|
|||
fn write_bar(&mut self, _addr: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
impl Suspendable for StubPciDevice {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use resources::AddressRange;
|
||||
|
|
|
@ -89,6 +89,7 @@ use crate::vfio::VfioError;
|
|||
use crate::vfio::VfioIrqType;
|
||||
use crate::vfio::VfioPciConfig;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
const PCI_VENDOR_ID: u32 = 0x0;
|
||||
const PCI_DEVICE_ID: u32 = 0x2;
|
||||
|
@ -2192,6 +2193,8 @@ impl Drop for VfioPciDevice {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for VfioPciDevice {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use resources::AddressRange;
|
||||
|
|
|
@ -34,6 +34,7 @@ use crate::pci::CrosvmDeviceId;
|
|||
use crate::BusAccessInfo;
|
||||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
const COMMAND_WRITE_BYTE: u8 = 0x10;
|
||||
const COMMAND_BLOCK_ERASE: u8 = 0x20;
|
||||
|
@ -230,6 +231,8 @@ impl BusDevice for Pflash {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Pflash {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base::FileReadWriteAtVolatile;
|
||||
|
|
|
@ -41,6 +41,7 @@ use crate::pci::CrosvmDeviceId;
|
|||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::IrqEdgeEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
// Bitmask for areas of standard (non-ReadBack) Control Word Format. Constant
|
||||
// names are kept the same as Intel PIT data sheet.
|
||||
|
@ -380,6 +381,8 @@ impl Pit {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Pit {}
|
||||
|
||||
// Each instance of this represents one of the PIT counters. They are used to
|
||||
// implement one-shot and repeating timer alarms. An 8254 has three counters.
|
||||
struct PitCounter {
|
||||
|
|
|
@ -13,6 +13,7 @@ use crate::BusAccessInfo;
|
|||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::IrqEdgeEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
// Register offsets
|
||||
// Data register
|
||||
|
@ -154,6 +155,9 @@ impl BusDevice for Pl030 {
|
|||
*data_array = reg_content.to_ne_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Pl030 {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -37,6 +37,7 @@ use crate::BusDeviceObj;
|
|||
use crate::DeviceId;
|
||||
use crate::IrqEdgeEvent;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
struct MmioInfo {
|
||||
index: u32,
|
||||
|
@ -72,6 +73,8 @@ impl BusDevice for VfioPlatformDevice {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for VfioPlatformDevice {}
|
||||
|
||||
impl BusDeviceObj for VfioPlatformDevice {
|
||||
fn as_platform_device(&self) -> Option<&VfioPlatformDevice> {
|
||||
Some(self)
|
||||
|
|
|
@ -27,6 +27,7 @@ use crate::BusDevice;
|
|||
use crate::BusRange;
|
||||
use crate::BusType;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
/// Errors for proxy devices.
|
||||
#[sorted]
|
||||
|
@ -359,6 +360,8 @@ impl BusDevice for ProxyDevice {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for ProxyDevice {}
|
||||
|
||||
impl Drop for ProxyDevice {
|
||||
fn drop(&mut self) {
|
||||
self.sync_send(&Command::Shutdown);
|
||||
|
@ -420,6 +423,8 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for EchoDevice {}
|
||||
|
||||
fn new_proxied_echo_device() -> ProxyDevice {
|
||||
let device = EchoDevice::new();
|
||||
let keep_fds: Vec<RawDescriptor> = Vec::new();
|
||||
|
|
|
@ -21,6 +21,7 @@ use base::Result;
|
|||
use crate::bus::BusAccessInfo;
|
||||
use crate::pci::CrosvmDeviceId;
|
||||
use crate::serial_device::SerialInput;
|
||||
use crate::suspendable::Suspendable;
|
||||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
|
||||
|
@ -399,6 +400,8 @@ impl BusDevice for Serial {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Serial {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io;
|
||||
|
|
|
@ -20,7 +20,7 @@ pub enum DeviceState {
|
|||
pub trait Suspendable {
|
||||
/// Save the device state in an image that can be restored.
|
||||
fn snapshot(&self) -> AnyhowResult<String> {
|
||||
Ok(format!(
|
||||
Err(anyhow!(
|
||||
"Suspendable::snapshot not implemented for {}",
|
||||
std::any::type_name::<Self>()
|
||||
))
|
||||
|
|
|
@ -37,6 +37,7 @@ use crate::usb::xhci::xhci_regs::init_xhci_mmio_space_and_regs;
|
|||
use crate::usb::xhci::xhci_regs::XhciRegs;
|
||||
use crate::utils::FailHandle;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
const XHCI_BAR0_SIZE: u64 = 0x10000;
|
||||
|
||||
|
@ -326,3 +327,5 @@ impl PciDevice for XhciController {
|
|||
self.init_when_forked();
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for XhciController {}
|
||||
|
|
|
@ -38,6 +38,7 @@ use crate::BusDevice;
|
|||
use crate::BusDeviceObj;
|
||||
use crate::DeviceId;
|
||||
use crate::IrqEdgeEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
const VIRT_MAGIC: u32 = 0x74726976; /* 'virt' */
|
||||
const VIRT_VERSION: u8 = 2;
|
||||
|
@ -506,3 +507,5 @@ impl BusDevice for VirtioMmioDevice {
|
|||
self.on_device_sandboxed();
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for VirtioMmioDevice {}
|
||||
|
|
|
@ -62,6 +62,7 @@ use crate::pci::PciInterruptPin;
|
|||
use crate::pci::PciSubclass;
|
||||
use crate::virtio::ipc_memory_mapper::IpcMemoryMapper;
|
||||
use crate::IrqLevelEvent;
|
||||
use crate::Suspendable;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, enumn::N)]
|
||||
|
@ -933,6 +934,8 @@ impl PciDevice for VirtioPciDevice {
|
|||
}
|
||||
}
|
||||
|
||||
impl Suspendable for VirtioPciDevice {}
|
||||
|
||||
struct VmRequester {
|
||||
tube: Tube,
|
||||
alloc: Alloc,
|
||||
|
|
|
@ -35,6 +35,7 @@ use crate::pci::CrosvmDeviceId;
|
|||
use crate::BusAccessInfo;
|
||||
use crate::BusDevice;
|
||||
use crate::DeviceId;
|
||||
use crate::Suspendable;
|
||||
|
||||
// Registers offsets
|
||||
const VMWDT_REG_STATUS: u32 = 0x00;
|
||||
|
@ -339,6 +340,9 @@ impl BusDevice for Vmwdt {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Suspendable for Vmwdt {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::thread::sleep;
|
||||
|
|
Loading…
Reference in a new issue