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}