⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/drivers/virtio/
mod.rs

1//! Virtio subsystem -- transport layer and device drivers.
2//!
3//! This module provides the virtio transport abstraction for VeridianOS,
4//! supporting two transport backends:
5//!
6//! - **PCI transport** ([`VirtioPciTransport`]): Used on **x86_64**, where
7//!   virtio devices appear as PCI devices with vendor ID 0x1AF4 (Red Hat). The
8//!   driver accesses device registers via BAR0 I/O port space.
9//!
10//! - **MMIO transport** ([`mmio::VirtioMmioTransport`]): Used on **AArch64**
11//!   and **RISC-V**, where QEMU's `virt` machine exposes virtio devices as
12//!   memory-mapped regions starting at 0x0A00_0000.
13//!
14//! Both transports are unified behind the [`VirtioTransport`] enum, which
15//! provides a common interface for device initialization, feature negotiation,
16//! queue setup, and notification.
17//!
18//! # Architecture
19//!
20//! ```text
21//!   VirtioTransport (enum)
22//!     |-- Pci(VirtioPciTransport)      -- x86_64 via I/O ports (BAR0)
23//!     |-- Mmio(VirtioMmioTransport)    -- AArch64/RISC-V via MMIO
24//!     |
25//!     +-- VirtQueue (queue.rs)         -- split virtqueue (shared)
26//!     +-- VirtioBlkDevice (blk.rs)     -- block device driver (shared)
27//! ```
28//!
29//! # Legacy PCI Layout (BAR0 I/O Space)
30//!
31//! The PCI transport uses the legacy (transitional) virtio PCI interface as
32//! described in the virtio 1.0 specification, section 4.1:
33//!
34//! | Offset | Size | Name            |
35//! |--------|------|-----------------|
36//! | 0x00   | 4    | device_features |
37//! | 0x04   | 4    | guest_features  |
38//! | 0x08   | 4    | queue_address   |
39//! | 0x0C   | 2    | queue_size      |
40//! | 0x0E   | 2    | queue_select    |
41//! | 0x10   | 2    | queue_notify    |
42//! | 0x12   | 1    | device_status   |
43//! | 0x13   | 1    | isr_status      |
44//! | 0x14+  | var  | device config   |
45//!
46//! For the MMIO register layout, see [`mmio`].
47
48// Virtio transport layer -- PCI and MMIO backends for device drivers
49
50pub mod blk;
51pub mod mmio;
52pub mod queue;
53
54/// Virtio vendor ID (Red Hat, Inc.)
55pub const VIRTIO_VENDOR_ID: u16 = 0x1AF4;
56
57/// Virtio-blk PCI device IDs
58/// Legacy device ID (virtio 0.9 / transitional)
59pub const VIRTIO_BLK_DEVICE_ID_LEGACY: u16 = 0x1001;
60/// Modern device ID (virtio 1.0+, transitional)
61pub const VIRTIO_BLK_DEVICE_ID_MODERN: u16 = 0x1042;
62
63/// Unified transport enum for virtio-blk
64#[derive(Debug, Clone, Copy)]
65pub enum VirtioTransport {
66    Pci(VirtioPciTransport),
67    Mmio(crate::drivers::virtio::mmio::VirtioMmioTransport),
68}
69
70impl VirtioTransport {
71    pub fn begin_init(&self) {
72        match self {
73            Self::Pci(p) => p.begin_init(),
74            Self::Mmio(m) => m.begin_init(),
75        }
76    }
77
78    pub fn write_guest_features(&self, features: u32) {
79        match self {
80            Self::Pci(p) => p.write_guest_features(features),
81            Self::Mmio(m) => m.write_driver_features(features),
82        }
83    }
84
85    pub fn read_device_features(&self) -> u32 {
86        match self {
87            Self::Pci(p) => p.read_device_features(),
88            Self::Mmio(m) => m.read_device_features(),
89        }
90    }
91
92    pub fn set_features_ok(&self) -> bool {
93        match self {
94            Self::Pci(p) => p.set_features_ok(),
95            Self::Mmio(m) => m.set_features_ok(),
96        }
97    }
98
99    pub fn select_queue(&self, idx: u16) {
100        match self {
101            Self::Pci(p) => p.select_queue(idx),
102            Self::Mmio(m) => m.select_queue(idx),
103        }
104    }
105
106    pub fn read_queue_size(&self) -> u16 {
107        match self {
108            Self::Pci(p) => p.read_queue_size(),
109            Self::Mmio(m) => m.read_queue_size_max(),
110        }
111    }
112
113    pub fn write_queue_address(&self, pfn: u32) {
114        match self {
115            Self::Pci(p) => p.write_queue_address(pfn),
116            Self::Mmio(_) => {} // mmio uses 64-bit phys addresses via write_queue_phys
117        }
118    }
119
120    pub fn write_queue_phys(&self, desc: u64, avail: u64, used: u64) {
121        match self {
122            Self::Pci(_) => {}
123            Self::Mmio(m) => m.write_queue_phys(desc, avail, used),
124        }
125    }
126
127    pub fn set_queue_ready(&self) {
128        match self {
129            Self::Pci(_) => {}
130            Self::Mmio(m) => m.set_queue_ready(),
131        }
132    }
133
134    pub fn set_driver_ok(&self) {
135        match self {
136            Self::Pci(p) => p.set_driver_ok(),
137            Self::Mmio(m) => m.set_driver_ok(),
138        }
139    }
140
141    pub fn notify_queue(&self, idx: u16) {
142        match self {
143            Self::Pci(p) => p.notify_queue(idx),
144            Self::Mmio(m) => m.notify_queue(idx),
145        }
146    }
147
148    pub fn read_device_config_u64(&self, offset: u16) -> u64 {
149        match self {
150            Self::Pci(p) => p.read_device_config_u64(offset),
151            Self::Mmio(m) => m.read_config_u64(offset as usize),
152        }
153    }
154}
155
156/// Virtio device status flags (virtio spec 2.1)
157pub mod status {
158    /// Guest OS has found the device and recognized it as a valid virtio
159    /// device.
160    pub const ACKNOWLEDGE: u8 = 1;
161    /// Guest OS knows how to drive the device.
162    pub const DRIVER: u8 = 2;
163    /// Driver is ready (feature negotiation complete).
164    pub const DRIVER_OK: u8 = 4;
165    /// Feature negotiation is complete.
166    pub const FEATURES_OK: u8 = 8;
167    /// Something went wrong; device has given up on the driver.
168    pub const DEVICE_NEEDS_RESET: u8 = 64;
169    /// Driver has given up on the device.
170    pub const FAILED: u8 = 128;
171}
172
173/// Legacy virtio PCI register offsets (I/O space via BAR0)
174pub mod regs {
175    /// Device features (read-only, 32-bit)
176    pub const DEVICE_FEATURES: u16 = 0x00;
177    /// Guest (driver) features (read/write, 32-bit)
178    pub const GUEST_FEATURES: u16 = 0x04;
179    /// Queue address (PFN of virtqueue, 32-bit)
180    pub const QUEUE_ADDRESS: u16 = 0x08;
181    /// Queue size (number of entries, 16-bit, read-only)
182    pub const QUEUE_SIZE: u16 = 0x0C;
183    /// Queue select (16-bit, write selects active queue)
184    pub const QUEUE_SELECT: u16 = 0x0E;
185    /// Queue notify (16-bit, write kicks the selected queue)
186    pub const QUEUE_NOTIFY: u16 = 0x10;
187    /// Device status (8-bit)
188    pub const DEVICE_STATUS: u16 = 0x12;
189    /// ISR status (8-bit, read clears)
190    pub const ISR_STATUS: u16 = 0x13;
191    /// Start of device-specific configuration space
192    pub const DEVICE_CONFIG: u16 = 0x14;
193}
194
195/// Virtio PCI transport handle.
196///
197/// Wraps the BAR0 I/O port base address for a legacy virtio PCI device and
198/// provides typed accessors for the common virtio register set.
199#[derive(Debug, Clone, Copy)]
200pub struct VirtioPciTransport {
201    /// BAR0 I/O port base address
202    io_base: u16,
203}
204
205impl VirtioPciTransport {
206    /// Create a new transport from the BAR0 I/O base address.
207    pub fn new(io_base: u16) -> Self {
208        Self { io_base }
209    }
210
211    /// Get the I/O base address.
212    pub fn io_base(&self) -> u16 {
213        self.io_base
214    }
215
216    // ---- Register accessors ----
217
218    /// Read the device-offered feature bits (32-bit).
219    pub fn read_device_features(&self) -> u32 {
220        self.read32(regs::DEVICE_FEATURES)
221    }
222
223    /// Write the driver-accepted feature bits (32-bit).
224    pub fn write_guest_features(&self, features: u32) {
225        self.write32(regs::GUEST_FEATURES, features);
226    }
227
228    /// Read the queue size for the currently selected queue.
229    pub fn read_queue_size(&self) -> u16 {
230        self.read16(regs::QUEUE_SIZE)
231    }
232
233    /// Select a virtqueue by index.
234    pub fn select_queue(&self, index: u16) {
235        self.write16(regs::QUEUE_SELECT, index);
236    }
237
238    /// Set the physical page frame number (PFN) of the selected virtqueue.
239    ///
240    /// The device uses this to locate the virtqueue descriptor table, available
241    /// ring, and used ring in guest physical memory. The address is `pfn *
242    /// 4096`.
243    pub fn write_queue_address(&self, pfn: u32) {
244        self.write32(regs::QUEUE_ADDRESS, pfn);
245    }
246
247    /// Notify (kick) the device that new buffers are available in the given
248    /// queue.
249    pub fn notify_queue(&self, queue_index: u16) {
250        self.write16(regs::QUEUE_NOTIFY, queue_index);
251    }
252
253    /// Read the device status register.
254    pub fn read_status(&self) -> u8 {
255        self.read8(regs::DEVICE_STATUS)
256    }
257
258    /// Write the device status register.
259    pub fn write_status(&self, status: u8) {
260        self.write8(regs::DEVICE_STATUS, status);
261    }
262
263    /// Read the ISR status register (clears interrupt flag on read).
264    pub fn read_isr(&self) -> u8 {
265        self.read8(regs::ISR_STATUS)
266    }
267
268    /// Read a byte from device-specific configuration space.
269    pub fn read_device_config_u8(&self, offset: u16) -> u8 {
270        self.read8(regs::DEVICE_CONFIG + offset)
271    }
272
273    /// Read a 32-bit word from device-specific configuration space.
274    pub fn read_device_config_u32(&self, offset: u16) -> u32 {
275        self.read32(regs::DEVICE_CONFIG + offset)
276    }
277
278    /// Read a 64-bit value from device-specific configuration space (two 32-bit
279    /// reads).
280    pub fn read_device_config_u64(&self, offset: u16) -> u64 {
281        let low = self.read32(regs::DEVICE_CONFIG + offset) as u64;
282        let high = self.read32(regs::DEVICE_CONFIG + offset + 4) as u64;
283        low | (high << 32)
284    }
285
286    // ---- Device initialization protocol (virtio spec 3.1.1) ----
287
288    /// Reset the device by writing zero to the status register.
289    pub fn reset(&self) {
290        self.write_status(0);
291    }
292
293    /// Perform the standard legacy device initialization sequence.
294    ///
295    /// 1. Reset device
296    /// 2. Set ACKNOWLEDGE
297    /// 3. Set DRIVER
298    ///
299    /// After calling this, the driver should read device features, negotiate,
300    /// then call `set_features_ok()` and `set_driver_ok()`.
301    pub fn begin_init(&self) {
302        // Step 1: Reset
303        self.reset();
304
305        // Step 2: Acknowledge -- we recognize this as a virtio device
306        self.write_status(status::ACKNOWLEDGE);
307
308        // Step 3: Driver -- we know how to drive this device type
309        self.write_status(status::ACKNOWLEDGE | status::DRIVER);
310    }
311
312    /// Signal that feature negotiation is complete.
313    ///
314    /// Returns `true` if the device accepted FEATURES_OK; `false` means the
315    /// device does not support the selected feature subset and initialization
316    /// should be aborted.
317    pub fn set_features_ok(&self) -> bool {
318        let current = self.read_status();
319        self.write_status(current | status::FEATURES_OK);
320
321        // Re-read to confirm the device accepted
322        (self.read_status() & status::FEATURES_OK) != 0
323    }
324
325    /// Signal that the driver is fully initialized and ready.
326    pub fn set_driver_ok(&self) {
327        let current = self.read_status();
328        self.write_status(current | status::DRIVER_OK);
329    }
330
331    /// Mark the device as failed.
332    pub fn set_failed(&self) {
333        let current = self.read_status();
334        self.write_status(current | status::FAILED);
335    }
336
337    // ---- Low-level I/O port helpers ----
338
339    fn read8(&self, offset: u16) -> u8 {
340        // SAFETY: Reading a virtio PCI I/O register at io_base + offset. The
341        // io_base was obtained from a PCI BAR0 I/O space mapping. We are in
342        // kernel mode with full I/O privilege.
343        unsafe { crate::arch::inb(self.io_base + offset) }
344    }
345
346    fn write8(&self, offset: u16, value: u8) {
347        // SAFETY: Writing a virtio PCI I/O register. Same invariants as read8.
348        unsafe { crate::arch::outb(self.io_base + offset, value) }
349    }
350
351    fn read16(&self, offset: u16) -> u16 {
352        // SAFETY: Reading a 16-bit virtio PCI I/O register. Same invariants.
353        unsafe { crate::arch::inw(self.io_base + offset) }
354    }
355
356    fn write16(&self, offset: u16, value: u16) {
357        // SAFETY: Writing a 16-bit virtio PCI I/O register. Same invariants.
358        unsafe { crate::arch::outw(self.io_base + offset, value) }
359    }
360
361    fn read32(&self, offset: u16) -> u32 {
362        // SAFETY: Reading a 32-bit virtio PCI I/O register. Same invariants.
363        unsafe { crate::arch::inl(self.io_base + offset) }
364    }
365
366    fn write32(&self, offset: u16, value: u32) {
367        // SAFETY: Writing a 32-bit virtio PCI I/O register. Same invariants.
368        unsafe { crate::arch::outl(self.io_base + offset, value) }
369    }
370}