use std::convert::TryFrom;
use std::fs;
use std::io::Error as IoError;
use std::process;
use std::sync::Arc;
use std::time::Duration;
use base::debug;
use base::error;
use base::gettid;
use base::warn;
use base::AsRawDescriptor;
use base::Descriptor;
use base::Error as SysError;
use base::Event;
use base::EventToken;
use base::SendTube;
use base::Timer;
use base::TimerTrait;
use base::VmEventType;
use base::WaitContext;
use base::WorkerThread;
use remain::sorted;
use sync::Mutex;
use thiserror::Error;
use crate::pci::CrosvmDeviceId;
use crate::BusAccessInfo;
use crate::BusDevice;
use crate::DeviceId;
use crate::Suspendable;
const VMWDT_REG_STATUS: u32 = 0x00;
const VMWDT_REG_LOAD_CNT: u32 = 0x04;
const VMWDT_REG_CURRENT_CNT: u32 = 0x08;
const VMWDT_REG_CLOCK_FREQ_HZ: u32 = 0x0C;
const VMWDT_REG_LEN: u64 = 0x10;
pub const VMWDT_DEFAULT_TIMEOUT_SEC: u32 = 10;
pub const VMWDT_DEFAULT_CLOCK_HZ: u32 = 2;
const PROCSTAT_GUEST_TIME_INDX: usize = 42;
#[sorted]
#[derive(Error, Debug)]
pub enum VmwdtError {
#[error("failed to create event: {0}")]
CreateEvent(SysError),
#[error("failed to spawn thread: {0}")]
SpawnThread(IoError),
#[error("failed to create vmwdt counter due to timer fd: {0}")]
TimerCreateError(SysError),
#[error("failed to wait for events: {0}")]
WaitError(SysError),
}
type VmwdtResult<T> = std::result::Result<T, VmwdtError>;
pub struct VmwdtPerCpu {
is_enabled: bool,
timer: Timer,
timer_freq_hz: u64,
last_guest_time_ms: i64,
pid: u32,
ppid: u32,
next_expiration_interval_ms: i64,
}
pub struct Vmwdt {
vm_wdts: Arc<Mutex<Vec<VmwdtPerCpu>>>,
worker_thread: Option<WorkerThread<()>>,
reset_evt_wrtube: SendTube,
activated: bool,
}
impl Vmwdt {
pub fn new(cpu_count: usize, reset_evt_wrtube: SendTube) -> VmwdtResult<Vmwdt> {
let mut vec = Vec::new();
for _ in 0..cpu_count {
vec.push(VmwdtPerCpu {
last_guest_time_ms: 0,
pid: 0,
ppid: 0,
is_enabled: false,
timer: Timer::new().unwrap(),
timer_freq_hz: 0,
next_expiration_interval_ms: 0,
});
}
let vm_wdts = Arc::new(Mutex::new(vec));
Ok(Vmwdt {
vm_wdts,
worker_thread: None,
reset_evt_wrtube,
activated: false,
})
}
pub fn vmwdt_worker_thread(
vm_wdts: Arc<Mutex<Vec<VmwdtPerCpu>>>,
kill_evt: Event,
reset_evt_wrtube: SendTube,
) {
#[derive(EventToken)]
enum Token {
Kill,
Timer(usize),
}
let wait_ctx: WaitContext<Token> = WaitContext::new().unwrap();
wait_ctx.add(&kill_evt, Token::Kill).unwrap();
let len = vm_wdts.lock().len();
for clock_id in 0..len {
let timer_fd = vm_wdts.lock()[clock_id].timer.as_raw_descriptor();
wait_ctx
.add(&Descriptor(timer_fd), Token::Timer(clock_id))
.unwrap();
}
loop {
let events = wait_ctx.wait().unwrap();
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::Kill => {
return;
}
Token::Timer(cpu_id) => {
let mut wdts_locked = vm_wdts.lock();
let watchdog = &mut wdts_locked[cpu_id];
if let Err(_e) = watchdog.timer.wait() {
error!("error waiting for timer event on vcpu {}", cpu_id);
}
let current_guest_time_ms_result =
Vmwdt::get_guest_time_ms(watchdog.ppid, watchdog.pid);
let current_guest_time_ms = match current_guest_time_ms_result {
Ok(value) => value,
Err(_e) => return,
};
let remaining_time_ms = watchdog.next_expiration_interval_ms
- (current_guest_time_ms - watchdog.last_guest_time_ms);
if remaining_time_ms > 0 {
watchdog.next_expiration_interval_ms = remaining_time_ms;
if let Err(_e) = watchdog
.timer
.reset_oneshot(Duration::from_millis(remaining_time_ms as u64))
{
error!("failed to reset internal timer on vcpu {}", cpu_id);
}
} else {
if let Err(_e) =
reset_evt_wrtube.send::<VmEventType>(&VmEventType::WatchdogReset)
{
error!("failed to send reset event from vcpu {}", cpu_id)
}
}
}
}
}
}
}
fn start(&mut self) {
let vm_wdts = self.vm_wdts.clone();
let reset_evt_wrtube = self.reset_evt_wrtube.try_clone().unwrap();
self.activated = true;
self.worker_thread = Some(WorkerThread::start("vmwdt worker", |kill_evt| {
Vmwdt::vmwdt_worker_thread(vm_wdts, kill_evt, reset_evt_wrtube)
}));
}
fn ensure_started(&mut self) {
if self.worker_thread.is_some() {
return;
}
self.start();
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_guest_time_ms(ppid: u32, pid: u32) -> Result<i64, SysError> {
let stat_path = format!("/proc/{}/task/{}/stat", ppid, pid);
let contents = fs::read_to_string(stat_path)?;
let gtime_ticks = contents
.split_whitespace()
.nth(PROCSTAT_GUEST_TIME_INDX)
.and_then(|guest_time| guest_time.parse::<u64>().ok())
.unwrap_or(0);
let ticks_per_sec = unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as u64;
Ok((gtime_ticks * 1000 / ticks_per_sec) as i64)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn get_guest_time_ms(ppid: u32, pid: u32) -> Result<i64, SysError> {
Ok(0)
}
}
impl BusDevice for Vmwdt {
fn debug_label(&self) -> String {
"Vmwdt".to_owned()
}
fn device_id(&self) -> DeviceId {
CrosvmDeviceId::VmWatchdog.into()
}
fn read(&mut self, _offset: BusAccessInfo, _data: &mut [u8]) {}
fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
let data_array = match <&[u8; 4]>::try_from(data) {
Ok(array) => array,
_ => {
error!("Bad write size: {} for vmwdt", data.len());
return;
}
};
let reg_val = u32::from_ne_bytes(*data_array);
let cpu_index: usize = (info.offset / VMWDT_REG_LEN) as usize;
let reg_offset = (info.offset % VMWDT_REG_LEN) as u32;
if cpu_index > self.vm_wdts.lock().len() {
error!("Bad write cpu_index {}", cpu_index);
return;
}
match reg_offset {
VMWDT_REG_STATUS => {
self.ensure_started();
let mut wdts_locked = self.vm_wdts.lock();
let cpu_watchdog = &mut wdts_locked[cpu_index];
cpu_watchdog.is_enabled = reg_val != 0;
if reg_val != 0 {
let interval = Duration::from_millis(1000 / cpu_watchdog.timer_freq_hz);
cpu_watchdog.timer.reset_repeating(interval).unwrap();
} else {
cpu_watchdog.timer.clear().unwrap();
}
}
VMWDT_REG_LOAD_CNT => {
let ppid = process::id();
let pid = gettid();
let guest_time_ms_result = Vmwdt::get_guest_time_ms(ppid, pid as u32);
let guest_time_ms = match guest_time_ms_result {
Ok(time) => time,
Err(_e) => return,
};
let mut wdts_locked = self.vm_wdts.lock();
let cpu_watchdog = &mut wdts_locked[cpu_index];
let next_expiration_interval_ms =
reg_val as u64 * 1000 / cpu_watchdog.timer_freq_hz;
cpu_watchdog.pid = pid as u32;
cpu_watchdog.ppid = ppid;
cpu_watchdog.last_guest_time_ms = guest_time_ms;
cpu_watchdog.next_expiration_interval_ms = next_expiration_interval_ms as i64;
if cpu_watchdog.is_enabled {
if let Err(_e) = cpu_watchdog
.timer
.reset_oneshot(Duration::from_millis(next_expiration_interval_ms))
{
error!("failed to reset one-shot vcpu time {}", cpu_index);
}
}
}
VMWDT_REG_CURRENT_CNT => {
warn!("invalid write to read-only VMWDT_REG_CURRENT_CNT register");
}
VMWDT_REG_CLOCK_FREQ_HZ => {
let mut wdts_locked = self.vm_wdts.lock();
let cpu_watchdog = &mut wdts_locked[cpu_index];
debug!(
"CPU:{:x} wrote VMWDT_REG_CLOCK_FREQ_HZ {:x}",
cpu_index, reg_val
);
cpu_watchdog.timer_freq_hz = reg_val as u64;
}
_ => unreachable!(),
}
}
}
impl Suspendable for Vmwdt {
fn sleep(&mut self) -> anyhow::Result<()> {
if let Some(worker) = self.worker_thread.take() {
worker.stop();
}
Ok(())
}
fn wake(&mut self) -> anyhow::Result<()> {
if self.activated {
self.start();
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::thread::sleep;
use base::poll_assert;
use base::Tube;
use super::*;
const AARCH64_VMWDT_ADDR: u64 = 0x3000;
const TEST_VMWDT_CPU_NO: usize = 0x1;
fn vmwdt_bus_address(offset: u64) -> BusAccessInfo {
BusAccessInfo {
offset,
address: AARCH64_VMWDT_ADDR,
id: 0,
}
}
#[test]
fn test_watchdog_internal_timer() {
let (vm_evt_wrtube, _vm_evt_rdtube) = Tube::directional_pair().unwrap();
let mut device = Vmwdt::new(TEST_VMWDT_CPU_NO, vm_evt_wrtube).unwrap();
device.write(
vmwdt_bus_address(VMWDT_REG_CLOCK_FREQ_HZ as u64),
&[10, 0, 0, 0],
);
device.write(vmwdt_bus_address(VMWDT_REG_LOAD_CNT as u64), &[1, 0, 0, 0]);
device.write(vmwdt_bus_address(VMWDT_REG_STATUS as u64), &[1, 0, 0, 0]);
let next_expiration_ms = {
let mut vmwdt_locked = device.vm_wdts.lock();
vmwdt_locked[0].last_guest_time_ms = 10;
vmwdt_locked[0].next_expiration_interval_ms
};
poll_assert!(10, || {
sleep(Duration::from_millis(50));
let vmwdt_locked = device.vm_wdts.lock();
vmwdt_locked[0].next_expiration_interval_ms != next_expiration_ms
});
}
#[test]
fn test_watchdog_expiration() {
let (vm_evt_wrtube, vm_evt_rdtube) = Tube::directional_pair().unwrap();
let mut device = Vmwdt::new(TEST_VMWDT_CPU_NO, vm_evt_wrtube).unwrap();
device.write(
vmwdt_bus_address(VMWDT_REG_CLOCK_FREQ_HZ as u64),
&[10, 0, 0, 0],
);
device.write(vmwdt_bus_address(VMWDT_REG_LOAD_CNT as u64), &[1, 0, 0, 0]);
device.write(vmwdt_bus_address(VMWDT_REG_STATUS as u64), &[1, 0, 0, 0]);
device.vm_wdts.lock()[0].last_guest_time_ms = -100;
poll_assert!(10, || {
sleep(Duration::from_millis(50));
match vm_evt_rdtube.recv::<VmEventType>() {
Ok(vm_event) => vm_event == VmEventType::WatchdogReset,
Err(_e) => false,
}
});
}
}