Skip to main content

arcbox_virtio_core/
lib.rs

1//! # arcbox-virtio-core
2//!
3//! Foundational types and traits shared by every ArcBox VirtIO device
4//! crate. Lives at the bottom of the dependency graph so each
5//! per-device crate (`arcbox-virtio-net`, `arcbox-virtio-block`, …)
6//! and the umbrella `arcbox-virtio` crate can pull from one place
7//! without circular references.
8//!
9//! Contents:
10//! - [`VirtioDevice`]: the device-implementation trait.
11//! - [`DeviceCtx`]: shared guest-memory + IRQ-trigger context that
12//!   each device holds at runtime.
13//! - [`QueueConfig`]: per-virtqueue configuration snapshot built by
14//!   the VMM from MMIO state and handed to a device for processing.
15//! - [`VirtioDeviceId`] / [`DeviceStatus`]: device-type IDs and
16//!   transport status flags.
17//! - [`GuestMemWriter`]: GPA-keyed accessor over guest RAM.
18//! - [`Result`] / [`VirtioError`]: error types shared by all devices.
19
20#![allow(clippy::ptr_as_ptr)]
21#![allow(clippy::borrow_as_ptr)]
22#![allow(clippy::unnecessary_cast)]
23#![allow(clippy::cognitive_complexity)]
24#![allow(clippy::map_unwrap_or)]
25#![allow(clippy::useless_vec)]
26#![allow(clippy::unnecessary_wraps)]
27#![allow(clippy::redundant_clone)]
28#![allow(clippy::unnecessary_map_or)]
29#![allow(clippy::missing_fields_in_debug)]
30#![allow(clippy::needless_lifetimes)]
31#![allow(clippy::needless_collect)]
32#![allow(mismatched_lifetime_syntaxes)]
33
34pub mod error;
35pub mod guest_mem;
36pub mod queue;
37pub mod queue_guest;
38
39pub use error::{Result, VirtioError};
40pub use guest_mem::GuestMemWriter;
41pub use queue::{AvailRing, Descriptor, UsedRing, VirtQueue};
42
43// Re-export commonly used virtio constants from virtio-bindings so
44// downstream crates can reach them without an extra direct dependency.
45pub use virtio_bindings;
46
47/// Per-device context shared between the VMM and each `VirtioDevice` for
48/// accessing guest physical memory and injecting interrupts.
49///
50/// Every device that needs to read descriptors, write completions to
51/// device-owned buffers, or raise a VirtIO used-ring / config-change
52/// interrupt holds a `DeviceCtx`. Construction happens inside the VMM
53/// at device-registration time, so by the time guest I/O begins both
54/// fields are populated — devices store this by value (no `Option`).
55///
56/// `mem` is a shared-lifetime accessor over the single guest RAM mmap.
57/// `raise_irq` is pre-bound to the device's GSI and MMIO state; the
58/// only argument is the interrupt reason (`INT_VRING` = 1 for used-ring
59/// notifications, `INT_CONFIG` = 2 for config-space changes).
60#[derive(Clone)]
61pub struct DeviceCtx {
62    /// Guest physical memory accessor.
63    pub mem: std::sync::Arc<GuestMemWriter>,
64    /// Pre-bound interrupt trigger: updates the device's MMIO
65    /// `interrupt_status` register and then asserts the device's GSI
66    /// on the platform IRQ chip.
67    pub raise_irq: std::sync::Arc<dyn Fn(u32) + Send + Sync>,
68}
69
70impl std::fmt::Debug for DeviceCtx {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        f.debug_struct("DeviceCtx")
73            .field("mem_len", &self.mem.len())
74            .field("mem_gpa_base", &self.mem.gpa_base())
75            .field("raise_irq", &"<fn>")
76            .finish()
77    }
78}
79
80/// `VirtIO` device type IDs.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82#[repr(u32)]
83pub enum VirtioDeviceId {
84    /// Network device.
85    Net = 1,
86    /// Block device.
87    Block = 2,
88    /// Console device.
89    Console = 3,
90    /// Entropy source.
91    Rng = 4,
92    /// Balloon device.
93    Balloon = 5,
94    /// SCSI host.
95    Scsi = 8,
96    /// Filesystem device.
97    Fs = 26,
98    /// Socket device.
99    Vsock = 19,
100}
101
102/// `VirtIO` device status flags.
103///
104/// Values sourced from `virtio_bindings::virtio_config`.
105#[derive(Debug, Clone, Copy)]
106pub struct DeviceStatus(u8);
107
108impl DeviceStatus {
109    /// Device acknowledged.
110    pub const ACKNOWLEDGE: u8 = virtio_bindings::virtio_config::VIRTIO_CONFIG_S_ACKNOWLEDGE as u8;
111    /// Driver loaded.
112    pub const DRIVER: u8 = virtio_bindings::virtio_config::VIRTIO_CONFIG_S_DRIVER as u8;
113    /// Driver is ready.
114    pub const DRIVER_OK: u8 = virtio_bindings::virtio_config::VIRTIO_CONFIG_S_DRIVER_OK as u8;
115    /// Feature negotiation complete.
116    pub const FEATURES_OK: u8 = virtio_bindings::virtio_config::VIRTIO_CONFIG_S_FEATURES_OK as u8;
117    /// Device needs reset.
118    pub const DEVICE_NEEDS_RESET: u8 =
119        virtio_bindings::virtio_config::VIRTIO_CONFIG_S_NEEDS_RESET as u8;
120    /// Driver failed.
121    pub const FAILED: u8 = virtio_bindings::virtio_config::VIRTIO_CONFIG_S_FAILED as u8;
122
123    /// Creates a new device status.
124    #[must_use]
125    pub const fn new(status: u8) -> Self {
126        Self(status)
127    }
128
129    /// Returns the raw status value.
130    #[must_use]
131    pub const fn raw(&self) -> u8 {
132        self.0
133    }
134
135    /// Checks if a flag is set.
136    #[must_use]
137    pub const fn has(&self, flag: u8) -> bool {
138        self.0 & flag != 0
139    }
140
141    /// Sets a flag.
142    pub const fn set(&mut self, flag: u8) {
143        self.0 |= flag;
144    }
145
146    /// Clears a flag.
147    pub const fn clear(&mut self, flag: u8) {
148        self.0 &= !flag;
149    }
150}
151
152/// Trait for `VirtIO` devices.
153pub trait VirtioDevice: Send + Sync {
154    /// Returns the device type ID.
155    fn device_id(&self) -> VirtioDeviceId;
156
157    /// Returns the device features.
158    fn features(&self) -> u64;
159
160    /// Acknowledges features from the driver.
161    fn ack_features(&mut self, features: u64);
162
163    /// Reads from the device configuration space.
164    fn read_config(&self, offset: u64, data: &mut [u8]);
165
166    /// Writes to the device configuration space.
167    fn write_config(&mut self, offset: u64, data: &[u8]);
168
169    /// Activates the device.
170    fn activate(&mut self) -> Result<()>;
171
172    /// Resets the device.
173    fn reset(&mut self);
174
175    /// Processes pending buffers in the specified queue.
176    ///
177    /// `memory` is the entire guest physical memory as a mutable byte slice.
178    /// `queue_config` provides the guest physical addresses of the virtqueue
179    /// rings so the device can read descriptors directly from guest memory.
180    ///
181    /// Returns a list of `(descriptor_head_index, bytes_written)` completions,
182    /// or an empty vec if no processing occurred.
183    ///
184    /// The default implementation does nothing. Devices that support
185    /// queue-based processing (blk, fs, net, vsock) should override this.
186    fn process_queue(
187        &mut self,
188        queue_idx: u16,
189        memory: &mut [u8],
190        queue_config: &QueueConfig,
191    ) -> Result<Vec<(u16, u32)>> {
192        let _ = (queue_idx, memory, queue_config);
193        Ok(Vec::new())
194    }
195}
196
197/// Configuration for a single virtqueue, as set by the guest driver via
198/// MMIO transport writes.
199///
200/// All addresses are guest physical addresses (GPAs). The `memory` slice
201/// passed to [`VirtioDevice::process_queue`] starts at `gpa_base`, so
202/// devices must subtract `gpa_base` to convert a GPA to a slice index.
203///
204/// Device-specific state (e.g. the vsock host connection manager) is
205/// not plumbed through this struct — devices that need such state hold
206/// it directly via their own `bind_*` setters.
207#[derive(Debug, Default)]
208pub struct QueueConfig {
209    /// Guest physical address of the descriptor table.
210    pub desc_addr: u64,
211    /// Guest physical address of the available (driver) ring.
212    pub avail_addr: u64,
213    /// Guest physical address of the used (device) ring.
214    pub used_addr: u64,
215    /// Queue size (number of descriptors).
216    pub size: u16,
217    /// Whether the queue is ready.
218    pub ready: bool,
219    /// GPA of the start of the guest memory slice. Devices must subtract
220    /// this from descriptor addresses to get slice offsets.
221    pub gpa_base: u64,
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227    use virtio_bindings::virtio_config;
228    use virtio_bindings::virtio_ids;
229
230    /// Verify that our `VirtioDeviceId` enum values match the canonical
231    /// constants from the `virtio-bindings` crate (linux kernel headers).
232    #[test]
233    fn device_id_values_match_virtio_bindings() {
234        assert_eq!(VirtioDeviceId::Net as u32, virtio_ids::VIRTIO_ID_NET);
235        assert_eq!(VirtioDeviceId::Block as u32, virtio_ids::VIRTIO_ID_BLOCK);
236        assert_eq!(
237            VirtioDeviceId::Console as u32,
238            virtio_ids::VIRTIO_ID_CONSOLE
239        );
240        assert_eq!(VirtioDeviceId::Rng as u32, virtio_ids::VIRTIO_ID_RNG);
241        assert_eq!(
242            VirtioDeviceId::Balloon as u32,
243            virtio_ids::VIRTIO_ID_BALLOON
244        );
245        assert_eq!(VirtioDeviceId::Scsi as u32, virtio_ids::VIRTIO_ID_SCSI);
246        assert_eq!(VirtioDeviceId::Fs as u32, virtio_ids::VIRTIO_ID_FS);
247        assert_eq!(VirtioDeviceId::Vsock as u32, virtio_ids::VIRTIO_ID_VSOCK);
248    }
249
250    /// Verify that our `DeviceStatus` constants match the canonical values.
251    #[test]
252    fn device_status_values_match_virtio_bindings() {
253        assert_eq!(
254            DeviceStatus::ACKNOWLEDGE as u32,
255            virtio_config::VIRTIO_CONFIG_S_ACKNOWLEDGE
256        );
257        assert_eq!(
258            DeviceStatus::DRIVER as u32,
259            virtio_config::VIRTIO_CONFIG_S_DRIVER
260        );
261        assert_eq!(
262            DeviceStatus::DRIVER_OK as u32,
263            virtio_config::VIRTIO_CONFIG_S_DRIVER_OK
264        );
265        assert_eq!(
266            DeviceStatus::FEATURES_OK as u32,
267            virtio_config::VIRTIO_CONFIG_S_FEATURES_OK
268        );
269        assert_eq!(
270            DeviceStatus::DEVICE_NEEDS_RESET as u32,
271            virtio_config::VIRTIO_CONFIG_S_NEEDS_RESET
272        );
273        assert_eq!(
274            DeviceStatus::FAILED as u32,
275            virtio_config::VIRTIO_CONFIG_S_FAILED
276        );
277    }
278}