Skip to main content

squib_virtio/
device.rs

1//! Per-device-type trait surface.
2//!
3//! Every virtio device frontend (block, net, vsock, etc.) implements
4//! [`VirtioDevice`]. The transport in [`crate::transport`] owns the device
5//! behind `Arc<Mutex<dyn VirtioDevice>>` and forwards register reads, queue
6//! notifications, and lifecycle events to it.
7//!
8//! Squib's trait is intentionally smaller than upstream Firecracker's:
9//! we don't expose an `EventFd` per queue (we use the GIC pulse directly),
10//! we don't surface `MutEventSubscriber` (Tokio drives the event loop), and
11//! we don't ship `prepare_save` until snapshots land (Phase 5).
12
13use std::sync::Arc;
14
15use squib_core::GuestMemory;
16use thiserror::Error;
17
18use crate::{device_id::VirtioDeviceType, interrupt::IrqLine, queue::Queue};
19
20/// Errors a device can surface during activation. Activation runs once per
21/// device, on the driver's `Status |= DRIVER_OK` write.
22#[derive(Debug, Error)]
23#[non_exhaustive]
24pub enum ActivateError {
25    /// Device backend (host file, socket) is unavailable.
26    #[error("device backend unavailable: {0}")]
27    BackendUnavailable(String),
28    /// Driver acked features that are mutually exclusive with the device's
29    /// configured backend.
30    #[error("incompatible feature set: {0}")]
31    IncompatibleFeatures(&'static str),
32    /// Device-specific activation error; carries a free-form message so
33    /// device modules don't need their own error subtypes.
34    #[error("device activation failed: {0}")]
35    Other(String),
36}
37
38/// Per-device-type contract.
39///
40/// Implementors hold their own state behind a `parking_lot::Mutex` (the
41/// transport already serializes access through `Arc<Mutex<dyn VirtioDevice>>`,
42/// so the inner state needs no second lock). Implementors MUST NOT block on
43/// I/O inside any method other than `process_queue` — every other method runs
44/// under the per-device mutex and a stall freezes the bus.
45pub trait VirtioDevice: Send + std::fmt::Debug {
46    /// Device-type discriminant for the MMIO `DeviceID` register.
47    fn device_type(&self) -> VirtioDeviceType;
48
49    /// Bitmap of features the device offers. The transport ANDs this with
50    /// the driver's `acked_features` and surfaces the negotiated set.
51    fn avail_features(&self) -> u64;
52
53    /// Bitmap of features the driver has acknowledged.
54    fn acked_features(&self) -> u64;
55
56    /// Set the driver-acked feature set. The transport calls this once per
57    /// MMIO `DriverFeatures` write; implementors typically just store the
58    /// value.
59    fn set_acked_features(&mut self, value: u64);
60
61    /// Per-queue maximum descriptor counts. The vector length determines how
62    /// many vrings the device exposes; the value at each index becomes
63    /// `QueueNumMax` for that queue.
64    fn queue_max_sizes(&self) -> &[u16];
65
66    /// Borrow the device's queue state.
67    fn queues(&self) -> &[Queue];
68
69    /// Borrow the device's queue state mutably (transport uses this when the
70    /// driver writes queue-config registers).
71    fn queues_mut(&mut self) -> &mut [Queue];
72
73    /// Read a slice of the device's config space at `offset`.
74    fn read_config(&self, offset: u64, data: &mut [u8]);
75
76    /// Write a slice into the device's config space at `offset`. Per the
77    /// virtio spec § 2.4.2, drivers SHOULD not write config-space after
78    /// `DRIVER_OK`; the transport gates on this and silently drops post-OK
79    /// writes via [`crate::transport::VirtioMmioTransport::accept_config_write`].
80    fn write_config(&mut self, offset: u64, data: &[u8]);
81
82    /// Take ownership of the resources needed to drive the device live.
83    /// Called exactly once on the driver's `DRIVER_OK` transition.
84    ///
85    /// # Errors
86    /// [`ActivateError`] for any backend / configuration failure.
87    fn activate(&mut self, mem: Arc<dyn GuestMemory>, irq: IrqLine) -> Result<(), ActivateError>;
88
89    /// `true` once `activate` has succeeded.
90    fn is_activated(&self) -> bool;
91
92    /// Handler invoked on a guest write to the MMIO `QueueNotify` register.
93    ///
94    /// `queue_index` matches `Queue::queues()[queue_index]`. Implementations
95    /// drain the queue's available ring, process the descriptors, and push
96    /// back into the used ring — typically by spawning the work on a Tokio
97    /// task and returning fast.
98    fn process_queue(&mut self, queue_index: u16);
99}