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