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}