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}