use std::{sync::Arc, time::Instant};
use parking_lot::Mutex;
use squib_core::GuestMemory;
use crate::{
device::{ActivateError, VirtioDevice},
device_id::VirtioDeviceType,
interrupt::IrqLine,
queue::Queue,
};
#[derive(Debug)]
pub struct BootTimerDevice {
avail: u64,
acked: u64,
queues: Vec<Queue>,
boot_start: Instant,
state: Arc<Mutex<ActiveState>>,
}
#[derive(Debug, Default)]
struct ActiveState {
activated: bool,
}
impl BootTimerDevice {
#[must_use]
pub fn new() -> Self {
Self {
avail: 0,
acked: 0,
queues: Vec::new(),
boot_start: Instant::now(),
state: Arc::new(Mutex::new(ActiveState::default())),
}
}
fn elapsed_us(&self) -> u32 {
let elapsed_us = self.boot_start.elapsed().as_micros();
u32::try_from(elapsed_us.min(u128::from(u32::MAX))).unwrap_or(u32::MAX)
}
}
impl Default for BootTimerDevice {
fn default() -> Self {
Self::new()
}
}
impl VirtioDevice for BootTimerDevice {
fn device_type(&self) -> VirtioDeviceType {
VirtioDeviceType::BootTimer
}
fn avail_features(&self) -> u64 {
self.avail
}
fn acked_features(&self) -> u64 {
self.acked
}
fn set_acked_features(&mut self, value: u64) {
self.acked = value;
}
fn queue_max_sizes(&self) -> &[u16] {
&[]
}
fn queues(&self) -> &[Queue] {
&self.queues
}
fn queues_mut(&mut self) -> &mut [Queue] {
&mut self.queues
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
if offset == 0 && data.len() == 4 {
data.copy_from_slice(&self.elapsed_us().to_le_bytes());
return;
}
for b in data.iter_mut() {
*b = 0;
}
}
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
}
fn activate(&mut self, _mem: Arc<dyn GuestMemory>, _irq: IrqLine) -> Result<(), ActivateError> {
self.state.lock().activated = true;
Ok(())
}
fn is_activated(&self) -> bool {
self.state.lock().activated
}
fn process_queue(&mut self, _queue_index: u16) {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_have_zero_queues() {
let dev = BootTimerDevice::new();
assert_eq!(dev.queue_max_sizes().len(), 0);
assert_eq!(dev.queues().len(), 0);
}
#[test]
fn test_should_return_increasing_microseconds_on_consecutive_reads() {
let dev = BootTimerDevice::new();
let mut a = [0u8; 4];
let mut b = [0u8; 4];
dev.read_config(0, &mut a);
std::thread::sleep(std::time::Duration::from_millis(2));
dev.read_config(0, &mut b);
let av = u32::from_le_bytes(a);
let bv = u32::from_le_bytes(b);
assert!(bv >= av);
}
#[test]
fn test_should_return_zero_on_invalid_offset_or_length() {
let dev = BootTimerDevice::new();
let mut buf = [0xAAu8; 8];
dev.read_config(0x10, &mut buf);
assert_eq!(buf, [0u8; 8]);
}
}