Skip to main content

squib_virtio/devices/
boot_timer.rs

1//! Boot-timer — squib-internal latency device.
2//!
3//! Per [14-virtio-and-devices.md § 4.8](../../../specs/14-virtio-and-devices.md#48-boot-timer):
4//! a trivial virtio device with no queues. The device exposes a 4-byte
5//! config-space register at offset `0x100`; on guest read, the register
6//! returns `(now_monotonic_ns − boot_start_ns)` truncated to `u32`
7//! microseconds (saturating at `u32::MAX` ≈ 71 minutes).
8//!
9//! `boot_start_ns` is captured at the same point the kernel-load completes,
10//! so the value the guest reads after `init` runs is "time from the moment
11//! the kernel got control to the moment Linux first scheduled userspace."
12//! Used by the `boot.rs` criterion benchmark in
13//! [71-performance-budgets.md § 7](../../../specs/71-performance-budgets.md#7-bench-harness).
14
15use std::{sync::Arc, time::Instant};
16
17use parking_lot::Mutex;
18use squib_core::GuestMemory;
19
20use crate::{
21    device::{ActivateError, VirtioDevice},
22    device_id::VirtioDeviceType,
23    interrupt::IrqLine,
24    queue::Queue,
25};
26
27/// virtio-boot-timer device.
28#[derive(Debug)]
29pub struct BootTimerDevice {
30    avail: u64,
31    acked: u64,
32    queues: Vec<Queue>,
33    /// Captured at construction; this is the closest meaningful approximation
34    /// to "kernel load completed". The VMM creates the device immediately
35    /// after writing the kernel into guest RAM so the value is accurate to
36    /// well under a millisecond.
37    boot_start: Instant,
38    state: Arc<Mutex<ActiveState>>,
39}
40
41#[derive(Debug, Default)]
42struct ActiveState {
43    activated: bool,
44}
45
46impl BootTimerDevice {
47    /// Build a boot-timer with the start instant captured now.
48    #[must_use]
49    pub fn new() -> Self {
50        Self {
51            avail: 0,
52            acked: 0,
53            queues: Vec::new(),
54            boot_start: Instant::now(),
55            state: Arc::new(Mutex::new(ActiveState::default())),
56        }
57    }
58
59    /// Microseconds since `boot_start`, saturated at `u32::MAX`.
60    fn elapsed_us(&self) -> u32 {
61        let elapsed_us = self.boot_start.elapsed().as_micros();
62        u32::try_from(elapsed_us.min(u128::from(u32::MAX))).unwrap_or(u32::MAX)
63    }
64}
65
66impl Default for BootTimerDevice {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl VirtioDevice for BootTimerDevice {
73    fn device_type(&self) -> VirtioDeviceType {
74        VirtioDeviceType::BootTimer
75    }
76    fn avail_features(&self) -> u64 {
77        self.avail
78    }
79    fn acked_features(&self) -> u64 {
80        self.acked
81    }
82    fn set_acked_features(&mut self, value: u64) {
83        self.acked = value;
84    }
85    fn queue_max_sizes(&self) -> &[u16] {
86        &[]
87    }
88    fn queues(&self) -> &[Queue] {
89        &self.queues
90    }
91    fn queues_mut(&mut self) -> &mut [Queue] {
92        &mut self.queues
93    }
94    fn read_config(&self, offset: u64, data: &mut [u8]) {
95        // Single 4-byte register at offset 0 of config-space (== MMIO 0x100).
96        // Reads at any other offset return zero so the guest driver's probe
97        // doesn't spuriously panic on a wider read.
98        if offset == 0 && data.len() == 4 {
99            data.copy_from_slice(&self.elapsed_us().to_le_bytes());
100            return;
101        }
102        for b in data.iter_mut() {
103            *b = 0;
104        }
105    }
106    fn write_config(&mut self, _offset: u64, _data: &[u8]) {
107        // Read-only register; the spec wording in 14 § 4.8 doesn't define
108        // a write side, so we silently drop.
109    }
110    fn activate(&mut self, _mem: Arc<dyn GuestMemory>, _irq: IrqLine) -> Result<(), ActivateError> {
111        self.state.lock().activated = true;
112        Ok(())
113    }
114    fn is_activated(&self) -> bool {
115        self.state.lock().activated
116    }
117    fn process_queue(&mut self, _queue_index: u16) {
118        // No queues; nothing to process.
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_should_have_zero_queues() {
128        let dev = BootTimerDevice::new();
129        assert_eq!(dev.queue_max_sizes().len(), 0);
130        assert_eq!(dev.queues().len(), 0);
131    }
132
133    #[test]
134    fn test_should_return_increasing_microseconds_on_consecutive_reads() {
135        let dev = BootTimerDevice::new();
136        let mut a = [0u8; 4];
137        let mut b = [0u8; 4];
138        dev.read_config(0, &mut a);
139        std::thread::sleep(std::time::Duration::from_millis(2));
140        dev.read_config(0, &mut b);
141        let av = u32::from_le_bytes(a);
142        let bv = u32::from_le_bytes(b);
143        assert!(bv >= av);
144    }
145
146    #[test]
147    fn test_should_return_zero_on_invalid_offset_or_length() {
148        let dev = BootTimerDevice::new();
149        let mut buf = [0xAAu8; 8];
150        dev.read_config(0x10, &mut buf);
151        assert_eq!(buf, [0u8; 8]);
152    }
153}