#[cfg(target_arch = "aarch64")]
use std::arch::asm;
use std::collections::BTreeMap;
use std::mem::replace;
use std::mem::size_of;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use base::error;
use base::info;
use base::warn;
use base::AsRawDescriptor;
#[cfg(windows)]
use base::CloseNotifier;
use base::Error;
use base::Event;
use base::EventToken;
use base::RawDescriptor;
use base::ReadNotifier;
use base::Tube;
use base::WaitContext;
use base::WorkerThread;
use chrono::DateTime;
use chrono::Utc;
use data_model::Le32;
use data_model::Le64;
use serde::Deserialize;
use serde::Serialize;
use snapshot::AnySnapshot;
use vm_control::PvClockCommand;
use vm_control::PvClockCommandResponse;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;
use vm_memory::GuestMemoryError;
use zerocopy::FromBytes;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
use zerocopy::KnownLayout;
use super::copy_config;
use super::DeviceType;
use super::Interrupt;
use super::Queue;
use super::VirtioDevice;
const QUEUE_SIZE: u16 = 1;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
const PVCLOCK_TSC_STABLE_BIT: u8 = 1;
const PVCLOCK_GUEST_STOPPED: u8 = 2;
const VIRTIO_PVCLOCK_F_TSC_STABLE: u64 = 0;
const VIRTIO_PVCLOCK_F_INJECT_SLEEP: u64 = 1;
const VIRTIO_PVCLOCK_F_CLOCKSOURCE_RATING: u64 = 2;
const VIRTIO_PVCLOCK_S_OK: u8 = 0;
const VIRTIO_PVCLOCK_S_IOERR: u8 = 1;
const VIRTIO_PVCLOCK_CLOCKSOURCE_RATING: u32 = 450;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn read_clock_counter() -> u64 {
unsafe { std::arch::x86_64::_rdtsc() }
}
#[cfg(target_arch = "aarch64")]
fn read_clock_counter() -> u64 {
let mut x: u64;
unsafe {
asm!("mrs {x}, cntvct_el0",
x = out(reg) x,
);
}
x
}
fn freq_scale_shift(scaled_hz: u64, base_hz: u64) -> (u32, i8) {
assert!(scaled_hz > 0 && base_hz > 0);
let mut shift = 0;
let mut scaled_hz = scaled_hz as u128;
let mut base_hz = base_hz as u128;
if scaled_hz >= base_hz {
while scaled_hz >= base_hz {
base_hz <<= 1;
shift += 1;
}
} else {
while base_hz > 2 * scaled_hz {
scaled_hz <<= 1;
shift -= 1;
}
}
assert!(base_hz < (1u128 << 65) && scaled_hz < (1u128 << 65));
let mult: u32 = ((scaled_hz << 32) / base_hz)
.try_into()
.expect("should not overflow");
(mult, shift)
}
#[derive(Debug, Clone, Copy, Default, FromBytes, Immutable, IntoBytes, KnownLayout)]
#[allow(non_camel_case_types)]
#[repr(C)]
struct virtio_pvclock_config {
suspend_time_ns: Le64,
clocksource_rating: Le32,
padding: u32,
}
#[derive(Debug, Clone, Copy, Default, FromBytes, Immutable, IntoBytes, KnownLayout)]
#[allow(non_camel_case_types)]
#[repr(C)]
struct virtio_pvclock_set_pvclock_page_req {
pvclock_page_pa: Le64,
system_time: Le64,
tsc_timestamp: Le64,
status: u8,
padding: [u8; 7],
}
struct PvclockSharedData {
mem: GuestMemory,
seqlock_addr: GuestAddress,
tsc_suspended_delta_addr: GuestAddress,
tsc_frequency_multiplier_addr: GuestAddress,
tsc_frequency_shift_addr: GuestAddress,
flags_addr: GuestAddress,
}
impl PvclockSharedData {
pub fn new(mem: GuestMemory, addr: GuestAddress) -> Self {
PvclockSharedData {
mem,
seqlock_addr: addr,
tsc_suspended_delta_addr: addr.unchecked_add(8),
tsc_frequency_multiplier_addr: addr.unchecked_add(24),
tsc_frequency_shift_addr: addr.unchecked_add(28),
flags_addr: addr.unchecked_add(29),
}
}
fn snapshot(&self) -> GuestAddress {
self.seqlock_addr
}
pub fn zero_fill(&mut self) -> Result<()> {
self.mem
.write_all_at_addr(&[0u8; 32], self.seqlock_addr)
.context("failed to zero fill the pvclock shared data")
}
pub fn increment_seqlock(&mut self) -> Result<()> {
let value = self
.mem
.read_obj_from_addr::<u32>(self.seqlock_addr)
.context("failed to read seqlock value")?;
self.mem
.write_obj_at_addr(value.wrapping_add(1), self.seqlock_addr)
.context("failed to write seqlock value")
}
pub fn set_tsc_suspended_delta(&mut self, delta: u64) -> Result<()> {
self.mem
.write_obj_at_addr(delta, self.tsc_suspended_delta_addr)
.context("failed to write tsc suspended delta")
}
pub fn set_tsc_frequency(&mut self, frequency: u64) -> Result<()> {
let (multiplier, shift): (u32, i8) = freq_scale_shift(1_000_000_000, frequency);
self.mem
.write_obj_at_addr(multiplier, self.tsc_frequency_multiplier_addr)
.context("failed to write tsc frequency mlutiplier")?;
self.mem
.write_obj_at_addr(shift, self.tsc_frequency_shift_addr)
.context("failed to write tsc frequency shift")
}
pub fn enable_pvclock_flags(&mut self, flags: u8) -> Result<()> {
let value = self
.mem
.read_obj_from_addr::<u8>(self.flags_addr)
.context("failed to read flags")?;
self.mem
.write_obj_at_addr(value | flags, self.flags_addr)
.context("failed to write flags")
}
}
#[derive(Serialize, Deserialize)]
struct PvClockState {
tsc_frequency: u64,
paused_main_worker: Option<PvClockWorkerSnapshot>,
total_suspend_ns: Arc<AtomicU64>,
features: u64,
acked_features: u64,
}
enum PvClockWorkerState {
Idle(Tube),
Stub(WorkerThread<StubWorkerReturn>),
Main(WorkerThread<MainWorkerReturn>),
None,
}
pub struct PvClock {
state: PvClockState,
worker_state: PvClockWorkerState,
}
impl PvClock {
pub fn new(base_features: u64, tsc_frequency: u64, suspend_tube: Tube) -> Self {
let state = PvClockState {
tsc_frequency,
paused_main_worker: None,
total_suspend_ns: Arc::new(AtomicU64::new(0)),
features: base_features
| 1 << VIRTIO_PVCLOCK_F_TSC_STABLE
| 1 << VIRTIO_PVCLOCK_F_INJECT_SLEEP
| 1 << VIRTIO_PVCLOCK_F_CLOCKSOURCE_RATING,
acked_features: 0,
};
PvClock {
state,
worker_state: PvClockWorkerState::Idle(suspend_tube),
}
}
fn get_config(&self) -> virtio_pvclock_config {
virtio_pvclock_config {
suspend_time_ns: self.state.total_suspend_ns.load(Ordering::SeqCst).into(),
clocksource_rating: VIRTIO_PVCLOCK_CLOCKSOURCE_RATING.into(),
padding: 0,
}
}
fn start_main_worker(
&mut self,
interrupt: Interrupt,
pvclock_worker: PvClockWorker,
mut queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
let last_state = replace(&mut self.worker_state, PvClockWorkerState::None);
if let PvClockWorkerState::Idle(suspend_tube) = last_state {
if queues.len() != QUEUE_SIZES.len() {
self.worker_state = PvClockWorkerState::Idle(suspend_tube);
return Err(anyhow!(
"expected {} queues, got {}",
QUEUE_SIZES.len(),
queues.len()
));
}
let set_pvclock_page_queue = queues.remove(&0).unwrap();
self.worker_state = PvClockWorkerState::Main(WorkerThread::start(
"virtio_pvclock".to_string(),
move |kill_evt| {
run_main_worker(
pvclock_worker,
set_pvclock_page_queue,
suspend_tube,
interrupt,
kill_evt,
)
},
));
} else {
panic!("Invalid state transition");
}
Ok(())
}
fn start_stub_worker(&mut self) {
let last_state = replace(&mut self.worker_state, PvClockWorkerState::None);
self.worker_state = if let PvClockWorkerState::Idle(suspend_tube) = last_state {
PvClockWorkerState::Stub(WorkerThread::start(
"virtio_pvclock_stub".to_string(),
move |kill_evt| run_stub_worker(suspend_tube, kill_evt),
))
} else {
panic!("Invalid state transition");
};
}
fn stop_stub_worker(&mut self) {
let last_state = replace(&mut self.worker_state, PvClockWorkerState::None);
self.worker_state = if let PvClockWorkerState::Stub(stub_worker_thread) = last_state {
let stub_worker_ret = stub_worker_thread.stop();
PvClockWorkerState::Idle(stub_worker_ret.suspend_tube)
} else {
panic!("Invalid state transition");
}
}
fn stop_main_worker(&mut self) {
let last_state = replace(&mut self.worker_state, PvClockWorkerState::None);
if let PvClockWorkerState::Main(main_worker_thread) = last_state {
let main_worker_ret = main_worker_thread.stop();
self.worker_state = PvClockWorkerState::Idle(main_worker_ret.suspend_tube);
let mut queues = BTreeMap::new();
queues.insert(0, main_worker_ret.set_pvclock_page_queue);
self.state.paused_main_worker = Some(main_worker_ret.worker.into());
} else {
panic!("Invalid state transition");
}
}
fn switch_to_stub_worker(&mut self) {
self.stop_main_worker();
self.start_stub_worker();
}
fn switch_to_main_worker(
&mut self,
interrupt: Interrupt,
pvclock_worker: PvClockWorker,
queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
self.stop_stub_worker();
self.start_main_worker(interrupt, pvclock_worker, queues)
}
}
#[derive(Serialize, Deserialize, Clone)]
struct PvclockInstant {
time: DateTime<Utc>,
tsc_value: u64,
}
#[derive(Serialize, Deserialize, Clone)]
struct PvClockWorkerSnapshot {
suspend_time: Option<PvclockInstant>,
total_suspend_tsc_delta: u64,
pvclock_shared_data_base_address: Option<GuestAddress>,
}
impl From<PvClockWorker> for PvClockWorkerSnapshot {
fn from(worker: PvClockWorker) -> Self {
PvClockWorkerSnapshot {
suspend_time: worker.suspend_time,
total_suspend_tsc_delta: worker.total_suspend_tsc_delta,
pvclock_shared_data_base_address: worker
.pvclock_shared_data
.map(|pvclock| pvclock.snapshot()),
}
}
}
struct PvClockWorker {
tsc_frequency: u64,
suspend_time: Option<PvclockInstant>,
total_injected_ns: Arc<AtomicU64>,
total_suspend_tsc_delta: u64,
pvclock_shared_data: Option<PvclockSharedData>,
mem: GuestMemory,
}
impl PvClockWorker {
pub fn new(tsc_frequency: u64, total_injected_ns: Arc<AtomicU64>, mem: GuestMemory) -> Self {
PvClockWorker {
tsc_frequency,
suspend_time: None,
total_injected_ns,
total_suspend_tsc_delta: 0,
pvclock_shared_data: None,
mem,
}
}
fn from_snapshot(
tsc_frequency: u64,
total_injected_ns: Arc<AtomicU64>,
snap: PvClockWorkerSnapshot,
mem: GuestMemory,
) -> Self {
PvClockWorker {
tsc_frequency,
suspend_time: snap.suspend_time,
total_injected_ns,
total_suspend_tsc_delta: snap.total_suspend_tsc_delta,
pvclock_shared_data: snap
.pvclock_shared_data_base_address
.map(|addr| PvclockSharedData::new(mem.clone(), addr)),
mem,
}
}
fn set_pvclock_page(&mut self, addr: u64) -> Result<()> {
if self.pvclock_shared_data.is_some() {
return Err(Error::new(libc::EALREADY)).context("pvclock page already set");
}
let mut shared_data = PvclockSharedData::new(self.mem.clone(), GuestAddress(addr));
shared_data.zero_fill()?;
shared_data.set_tsc_frequency(self.tsc_frequency)?;
shared_data.enable_pvclock_flags(PVCLOCK_TSC_STABLE_BIT)?;
self.pvclock_shared_data = Some(shared_data);
Ok(())
}
pub fn suspend(&mut self) {
if self.suspend_time.is_some() {
warn!("Suspend time already set, ignoring new suspend time");
return;
}
self.suspend_time = Some(PvclockInstant {
time: Utc::now(),
tsc_value: read_clock_counter(),
});
}
pub fn resume(&mut self) -> Result<u64> {
self.increment_pvclock_seqlock()?;
std::sync::atomic::fence(Ordering::SeqCst);
let result = self
.set_guest_stopped_bit()
.and_then(|_| self.set_suspended_time());
std::sync::atomic::fence(Ordering::SeqCst);
self.increment_pvclock_seqlock()?;
result
}
fn get_suspended_duration(suspend_time: &PvclockInstant) -> Duration {
match Utc::now().signed_duration_since(suspend_time.time).to_std() {
Ok(duration) => duration,
Err(e) => {
error!(
"pvclock found suspend time in the future (was the host \
clock adjusted?). Guest boot/realtime clock may now be \
incorrect. Details: {}",
e
);
Duration::ZERO
}
}
}
fn set_suspended_time(&mut self) -> Result<u64> {
let (this_suspend_duration, this_suspend_tsc_delta) =
if let Some(suspend_time) = self.suspend_time.take() {
(
Self::get_suspended_duration(&suspend_time),
read_clock_counter().wrapping_sub(suspend_time.tsc_value),
)
} else {
return Err(Error::new(libc::ENOTSUP))
.context("Cannot set suspend time because suspend was never called");
};
self.total_suspend_tsc_delta = self
.total_suspend_tsc_delta
.wrapping_add(this_suspend_tsc_delta);
self.pvclock_shared_data
.as_mut()
.ok_or(
anyhow::Error::new(Error::new(libc::ENODATA)).context("pvclock page is not set"),
)?
.set_tsc_suspended_delta(self.total_suspend_tsc_delta)?;
info!(
"set total suspend tsc delta to {}",
self.total_suspend_tsc_delta
);
self.total_injected_ns
.fetch_add(this_suspend_duration.as_nanos() as u64, Ordering::SeqCst);
Ok(self.total_suspend_tsc_delta)
}
fn increment_pvclock_seqlock(&mut self) -> Result<()> {
self.pvclock_shared_data
.as_mut()
.ok_or(
anyhow::Error::new(Error::new(libc::ENODATA)).context("pvclock page is not set"),
)?
.increment_seqlock()
}
fn set_guest_stopped_bit(&mut self) -> Result<()> {
self.pvclock_shared_data
.as_mut()
.ok_or(
anyhow::Error::new(Error::new(libc::ENODATA)).context("pvclock page is not set"),
)?
.enable_pvclock_flags(PVCLOCK_GUEST_STOPPED)
}
}
fn pvclock_response_error_from_anyhow(error: anyhow::Error) -> base::Error {
for cause in error.chain() {
if let Some(e) = cause.downcast_ref::<base::Error>() {
return *e;
}
if let Some(e) = cause.downcast_ref::<GuestMemoryError>() {
return match e {
GuestMemoryError::MemoryAddSealsFailed(e) => *e,
GuestMemoryError::MemoryCreationFailed(e) => *e,
_ => Error::new(libc::EINVAL),
};
}
}
Error::new(libc::EFAULT)
}
struct StubWorkerReturn {
suspend_tube: Tube,
}
fn run_stub_worker(suspend_tube: Tube, kill_evt: Event) -> StubWorkerReturn {
#[derive(EventToken, Debug)]
enum Token {
SomePvClockRequest,
Kill,
}
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
(suspend_tube.get_read_notifier(), Token::SomePvClockRequest),
#[cfg(windows)]
(suspend_tube.get_close_notifier(), Token::Kill),
(&kill_evt, Token::Kill),
]) {
Ok(wait_ctx) => wait_ctx,
Err(e) => {
error!("failed creating WaitContext: {}", e);
return StubWorkerReturn { suspend_tube };
}
};
'wait: loop {
let events = match wait_ctx.wait() {
Ok(v) => v,
Err(e) => {
error!("failed polling for events: {}", e);
break;
}
};
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::SomePvClockRequest => {
match suspend_tube.recv::<PvClockCommand>() {
Ok(req) => req,
Err(e) => {
error!("failed to receive request: {}", e);
continue;
}
};
if let Err(e) = suspend_tube.send(&PvClockCommandResponse::DeviceInactive) {
error!("error sending PvClockCommandResponse: {}", e);
}
}
Token::Kill => {
break 'wait;
}
}
}
}
StubWorkerReturn { suspend_tube }
}
struct MainWorkerReturn {
worker: PvClockWorker,
set_pvclock_page_queue: Queue,
suspend_tube: Tube,
}
fn run_main_worker(
mut worker: PvClockWorker,
mut set_pvclock_page_queue: Queue,
suspend_tube: Tube,
interrupt: Interrupt,
kill_evt: Event,
) -> MainWorkerReturn {
#[derive(EventToken)]
enum Token {
SetPvClockPageQueue,
SuspendResume,
Kill,
}
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
(set_pvclock_page_queue.event(), Token::SetPvClockPageQueue),
(suspend_tube.get_read_notifier(), Token::SuspendResume),
#[cfg(windows)]
(suspend_tube.get_close_notifier(), Token::Kill),
(&kill_evt, Token::Kill),
]) {
Ok(pc) => pc,
Err(e) => {
error!("failed creating WaitContext: {}", e);
return MainWorkerReturn {
suspend_tube,
set_pvclock_page_queue,
worker,
};
}
};
'wait: loop {
let events = match wait_ctx.wait() {
Ok(v) => v,
Err(e) => {
error!("failed polling for events: {}", e);
break;
}
};
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::SetPvClockPageQueue => {
let _ = set_pvclock_page_queue.event().wait();
let desc_chain = match set_pvclock_page_queue.pop() {
Some(desc_chain) => desc_chain,
None => {
continue;
}
};
let desc = desc_chain
.reader
.get_remaining_regions()
.chain(desc_chain.writer.get_remaining_regions())
.next()
.unwrap();
let len = if desc.len < size_of::<virtio_pvclock_set_pvclock_page_req>() {
error!("pvclock descriptor too short");
0
} else {
let addr = GuestAddress(desc.offset);
let mut req: virtio_pvclock_set_pvclock_page_req = match worker
.mem
.read_obj_from_addr(addr)
{
Ok(req) => req,
Err(e) => {
error!("failed to read request from set_pvclock_page queue: {}", e);
continue;
}
};
req.status = match worker.set_pvclock_page(req.pvclock_page_pa.into()) {
Err(e) => {
error!("failed to set pvclock page: {:#}", e);
VIRTIO_PVCLOCK_S_IOERR
}
Ok(_) => VIRTIO_PVCLOCK_S_OK,
};
if let Err(e) = worker.mem.write_obj_at_addr(req, addr) {
error!("failed to write set_pvclock_page status: {}", e);
continue;
}
desc.len as u32
};
set_pvclock_page_queue.add_used_with_bytes_written(desc_chain, len);
set_pvclock_page_queue.trigger_interrupt();
}
Token::SuspendResume => {
let req = match suspend_tube.recv::<PvClockCommand>() {
Ok(req) => req,
Err(e) => {
error!("failed to receive request: {}", e);
continue;
}
};
let resp = match req {
PvClockCommand::Suspend => {
worker.suspend();
PvClockCommandResponse::Ok
}
PvClockCommand::Resume => {
match worker.resume() {
Ok(total_suspended_ticks) => {
interrupt.signal_config_changed();
PvClockCommandResponse::Resumed {
total_suspended_ticks,
}
}
Err(e) => {
error!("Failed to resume pvclock: {:#}", e);
PvClockCommandResponse::Err(pvclock_response_error_from_anyhow(
e,
))
}
}
}
};
if let Err(e) = suspend_tube.send(&resp) {
error!("error sending PvClockCommandResponse: {}", e);
}
}
Token::Kill => {
break 'wait;
}
}
}
}
MainWorkerReturn {
suspend_tube,
set_pvclock_page_queue,
worker,
}
}
impl VirtioDevice for PvClock {
fn keep_rds(&self) -> Vec<RawDescriptor> {
if let PvClockWorkerState::Idle(suspend_tube) = &self.worker_state {
vec![suspend_tube.as_raw_descriptor()]
} else {
Vec::new()
}
}
fn device_type(&self) -> DeviceType {
DeviceType::Pvclock
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.state.features
}
fn ack_features(&mut self, mut value: u64) {
if value & !self.features() != 0 {
warn!("virtio-pvclock got unknown feature ack {:x}", value);
value &= self.features();
}
self.state.acked_features |= value;
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
copy_config(data, 0, self.get_config().as_bytes(), offset);
}
fn write_config(&mut self, offset: u64, data: &[u8]) {
warn!(
"Unexpected write to virtio-pvclock config at offset {}: {:?}",
offset, data
);
}
fn activate(
&mut self,
mem: GuestMemory,
interrupt: Interrupt,
queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
let tsc_frequency = self.state.tsc_frequency;
let total_suspend_ns = self.state.total_suspend_ns.clone();
let worker = PvClockWorker::new(tsc_frequency, total_suspend_ns, mem);
self.switch_to_main_worker(interrupt, worker, queues)
}
fn reset(&mut self) -> Result<()> {
self.switch_to_stub_worker();
Ok(())
}
fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
let last_state = replace(&mut self.worker_state, PvClockWorkerState::None);
match last_state {
PvClockWorkerState::Main(main_worker_thread) => {
let main_worker_ret = main_worker_thread.stop();
let mut queues = BTreeMap::new();
queues.insert(0, main_worker_ret.set_pvclock_page_queue);
self.worker_state = PvClockWorkerState::Idle(main_worker_ret.suspend_tube);
self.state.paused_main_worker = Some(main_worker_ret.worker.into());
Ok(Some(queues))
}
PvClockWorkerState::Stub(stub_worker_thread) => {
let stub_ret = stub_worker_thread.stop();
self.worker_state = PvClockWorkerState::Idle(stub_ret.suspend_tube);
Ok(None)
}
PvClockWorkerState::Idle(suspend_tube) => {
self.worker_state = PvClockWorkerState::Idle(suspend_tube);
Ok(None)
}
PvClockWorkerState::None => panic!("invalid state transition"),
}
}
fn virtio_wake(
&mut self,
queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
) -> anyhow::Result<()> {
if let Some((mem, interrupt, queues)) = queues_state {
let worker_snap = self
.state
.paused_main_worker
.take()
.ok_or(anyhow!("a sleeping pvclock must have a paused worker"))?;
let worker = PvClockWorker::from_snapshot(
self.state.tsc_frequency,
self.state.total_suspend_ns.clone(),
worker_snap,
mem,
);
self.start_main_worker(interrupt, worker, queues)?;
} else {
self.start_stub_worker();
}
Ok(())
}
fn virtio_snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
AnySnapshot::to_any(&self.state).context("failed to serialize PvClockState")
}
fn virtio_restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
let state: PvClockState = AnySnapshot::from_any(data).context("error deserializing")?;
if state.features != self.features() {
bail!(
"expected virtio_features to match, but they did not. Live: {:?}, snapshot {:?}",
self.features(),
state.features,
);
}
self.state = state;
Ok(())
}
fn on_device_sandboxed(&mut self) {
self.start_stub_worker();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::virtio::QueueConfig;
const TEST_QUEUE_SIZE: u16 = 2048;
fn make_interrupt() -> Interrupt {
Interrupt::new_for_test()
}
fn create_pvclock_device() -> (Tube, PvClock) {
let (host_tube, device_tube) = Tube::pair().unwrap();
let mut pvclock_device = PvClock::new(0, 1e9 as u64, device_tube);
pvclock_device.on_device_sandboxed();
(host_tube, pvclock_device)
}
fn create_sleeping_device() -> (PvClock, GuestMemory, Tube) {
let (_host_tube, mut pvclock_device) = create_pvclock_device();
let mut fake_queue = QueueConfig::new(TEST_QUEUE_SIZE, 0);
fake_queue.set_ready(true);
let mem = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let interrupt = make_interrupt();
pvclock_device
.activate(
mem.clone(),
interrupt.clone(),
BTreeMap::from([(
0,
fake_queue
.activate(&mem, Event::new().unwrap(), interrupt)
.unwrap(),
)]),
)
.expect("activate should succeed");
let queues = pvclock_device
.virtio_sleep()
.expect("sleep should succeed")
.expect("sleep should yield queues");
assert_eq!(queues.len(), 1);
assert_eq!(
queues.get(&0).expect("queue must be present").size(),
TEST_QUEUE_SIZE
);
assert!(pvclock_device.state.paused_main_worker.is_some());
(pvclock_device, mem, _host_tube)
}
fn assert_wake_successful(pvclock_device: &mut PvClock, mem: &GuestMemory) {
let mut wake_queues = BTreeMap::new();
let mut fake_queue = QueueConfig::new(TEST_QUEUE_SIZE, 0);
let interrupt = make_interrupt();
fake_queue.set_ready(true);
wake_queues.insert(
0,
fake_queue
.activate(mem, Event::new().unwrap(), interrupt.clone())
.unwrap(),
);
let queues_state = (mem.clone(), interrupt, wake_queues);
pvclock_device
.virtio_wake(Some(queues_state))
.expect("wake should succeed");
assert!(pvclock_device.state.paused_main_worker.is_none());
}
#[test]
fn test_command_response_when_inactive() {
let (host_tube, _pvclock_device) = create_pvclock_device();
assert!(host_tube.send(&PvClockCommand::Suspend).is_ok());
let res = host_tube.recv::<PvClockCommandResponse>();
assert!(matches!(res, Ok(PvClockCommandResponse::DeviceInactive)));
}
#[test]
fn test_sleep_wake_smoke() {
let (mut pvclock_device, mem, _tube) = create_sleeping_device();
assert_wake_successful(&mut pvclock_device, &mem);
}
#[test]
fn test_save_restore() {
let (mut pvclock_device, mem, _tube) = create_sleeping_device();
let test_suspend_ns = 9999;
pvclock_device
.state
.total_suspend_ns
.store(test_suspend_ns, Ordering::SeqCst);
let snap = pvclock_device.virtio_snapshot().unwrap();
pvclock_device
.state
.total_suspend_ns
.store(0, Ordering::SeqCst);
pvclock_device.virtio_restore(snap).unwrap();
assert_eq!(
pvclock_device.state.total_suspend_ns.load(Ordering::SeqCst),
test_suspend_ns
);
assert_wake_successful(&mut pvclock_device, &mem);
}
fn pvclock_scale_tsc(mult: u32, shift: i8, tsc: u64) -> u64 {
let shifted = if shift < 0 {
tsc >> -shift
} else {
tsc << shift
};
let product = shifted as u128 * mult as u128;
(product >> 32).try_into().expect("should not overflow")
}
fn check_freq_scale(f: u64, input: u64) {
let (mult, shift) = freq_scale_shift(1_000_000_000, f);
let scaled = pvclock_scale_tsc(mult, shift, input);
let expected: u64 = (input as u128 * 1_000_000_000u128 / f as u128) as u64;
let expected_lo: u64 = (input as u128 * 999_999_990u128 / f as u128) as u64;
let expected_hi: u64 = (input as u128 * 1_000_000_010u128 / f as u128) as u64;
assert!(
(expected_lo..=expected_hi).contains(&scaled),
"{scaled} should be close to {expected} (base_hz={f}, mult={mult}, shift={shift})"
);
}
#[test]
fn test_freq_scale_shift_accuracy() {
for f in (1..=50).map(|n| n * 100_000_000) {
check_freq_scale(f, f);
}
}
#[test]
fn test_freq_scale_shift_overflow_high_freq() {
for f in (11..=50).map(|n| n * 100_000_000) {
check_freq_scale(f, u64::MAX);
}
}
#[test]
fn test_freq_scale_shift_overflow_low_freq() {
fn prev_power_of_two(n: u64) -> u64 {
assert_ne!(n, 0);
let highest_bit_set = 63 - n.leading_zeros();
1 << highest_bit_set
}
for f in (1..=10).map(|n| n * 100_000_000) {
let factor = 1_000_000_000 / f;
let target = u64::MAX / (prev_power_of_two(factor) << 1);
check_freq_scale(f, target);
}
}
}