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}