Skip to main content

strat9_kernel/hardware/virtio/
common.rs

1//! Common VirtIO infrastructure
2//!
3//! Provides virtqueue management and device initialization logic
4//! shared across all VirtIO drivers.
5//!
6//! Reference: VirtIO spec v1.2, Section 2 (Basic Facilities of a Virtio Device)
7//! https://docs.oasis-open.org/virtio/virtio/v1.2/os/virtio-v1.2-os.html#_basic-facilities-of-a-virtio-device
8
9use super::{vring_flags, VirtqDesc};
10use crate::{
11    arch::x86_64::pci::{Bar, PciDevice},
12    memory::{self, PhysFrame},
13};
14use alloc::vec::Vec;
15use core::{
16    ptr::{read_volatile, write_volatile},
17    sync::atomic::{fence, AtomicU16, Ordering},
18};
19use endian_num::Le;
20
21/// VirtIO device features
22pub mod features {
23    pub const VIRTIO_F_RING_INDIRECT_DESC: u64 = 1 << 28;
24    pub const VIRTIO_F_RING_EVENT_IDX: u64 = 1 << 29;
25    pub const VIRTIO_F_VERSION_1: u64 = 1 << 32;
26    pub const VIRTIO_F_ACCESS_PLATFORM: u64 = 1 << 33;
27    pub const VIRTIO_F_RING_PACKED: u64 = 1 << 34;
28    pub const VIRTIO_F_IN_ORDER: u64 = 1 << 35;
29    pub const VIRTIO_F_ORDER_PLATFORM: u64 = 1 << 36;
30    pub const VIRTIO_F_SR_IOV: u64 = 1 << 37;
31    pub const VIRTIO_F_NOTIFICATION_DATA: u64 = 1 << 38;
32}
33
34/// Available ring structure (device -> driver notifications)
35#[repr(C)]
36pub struct VirtqAvail {
37    pub flags: AtomicU16,
38    pub idx: AtomicU16,
39    // ring follows (variable length)
40    // used_event follows ring (if VIRTIO_F_RING_EVENT_IDX)
41}
42
43/// Used ring element
44#[repr(C)]
45#[derive(Debug, Clone, Copy)]
46pub struct VirtqUsedElem {
47    /// Index of start of used descriptor chain
48    pub id: u32,
49    /// Total length of the descriptor chain
50    pub len: u32,
51}
52
53/// Used ring structure (driver -> device notifications)
54#[repr(C)]
55pub struct VirtqUsed {
56    pub flags: AtomicU16,
57    pub idx: AtomicU16,
58    // ring follows (variable length)
59    // avail_event follows ring (if VIRTIO_F_RING_EVENT_IDX)
60}
61
62/// A VirtIO virtqueue
63///
64/// This structure manages the split virtqueue format as described in
65/// VirtIO spec section 2.6 (Split Virtqueues).
66pub struct Virtqueue {
67    /// Queue size (must be power of 2)
68    queue_size: u16,
69
70    /// Contiguous legacy vring allocation backing desc+avail+used
71    _ring_area: PhysFrame,
72
73    /// Physical address of descriptor table
74    desc_area: u64,
75
76    /// Physical address of available ring
77    avail_area: u64,
78
79    /// Physical address of used ring
80    used_area: u64,
81
82    /// Virtual address of descriptor table
83    desc_ptr: *mut VirtqDesc,
84
85    /// Virtual address of available ring
86    avail_ptr: *mut VirtqAvail,
87
88    /// Virtual address of available ring entries
89    avail_ring_ptr: *mut u16,
90
91    /// Virtual address of used ring
92    used_ptr: *mut VirtqUsed,
93
94    /// Virtual address of used ring entries
95    used_ring_ptr: *mut VirtqUsedElem,
96
97    /// Free descriptor list (indices of free descriptors)
98    free_descriptors: Vec<u16>,
99
100    /// Last seen used index
101    last_used_idx: u16,
102
103    /// Next available index
104    next_avail_idx: u16,
105}
106
107// Send is safe because we manage synchronization via SpinLocks in usage
108unsafe impl Send for Virtqueue {}
109
110impl Virtqueue {
111    #[inline]
112    fn align_up(value: usize, align: usize) -> usize {
113        debug_assert!(align.is_power_of_two());
114        (value + align - 1) & !(align - 1)
115    }
116
117    /// Create a new virtqueue with the specified size
118    ///
119    /// # Safety
120    /// The caller must ensure that the allocated memory is properly mapped
121    /// and accessible.
122    pub unsafe fn new(queue_size: u16) -> Result<Self, &'static str> {
123        if !queue_size.is_power_of_two() {
124            return Err("Queue size must be power of 2");
125        }
126
127        let desc_size = queue_size as usize * core::mem::size_of::<VirtqDesc>();
128        let avail_size = 6 + queue_size as usize * 2;
129        let used_size = 6 + queue_size as usize * core::mem::size_of::<VirtqUsedElem>();
130        // Legacy virtio layout (Section 2.5.1): the device derives ring offsets
131        // from the descriptor-table PFN using PAGE_ALIGN.  The driver MUST
132        // match that same alignment or device and driver will read/write
133        // different memory locations.
134        let avail_offset = Self::align_up(desc_size, 4096); // PAGE_ALIGN(desc_size)
135        let used_offset = Self::align_up(avail_offset + avail_size, 4096); // PAGE_ALIGN(avail_end)
136        let total_size = used_offset + used_size;
137
138        // Critical: legacy QUEUE_PFN describes one contiguous vring region.
139        let ring_pages = (total_size + 4095) / 4096;
140        let ring_order = ring_pages.next_power_of_two().trailing_zeros() as u8;
141        let ring_area = crate::sync::with_irqs_disabled(|token| {
142            memory::allocate_phys_contiguous(token, ring_order)
143        })
144        .map_err(|_| "Failed to allocate virtqueue ring")?;
145        let ring_phys = ring_area.start_address.as_u64();
146        let desc_phys = ring_phys;
147        let avail_phys = ring_phys + avail_offset as u64;
148        let used_phys = ring_phys + used_offset as u64;
149
150        // SAFETY: we just allocated these frames; convert phys => virt via HHDM
151        // With Limine HHDM, all physical memory is already mapped, so we can
152        // directly use phys_to_virt without additional page table modifications.
153        // DO NOT call ensure_identity_map here - it can corrupt active page tables!
154
155        let desc_virt = crate::memory::phys_to_virt(desc_phys);
156        let avail_virt = crate::memory::phys_to_virt(avail_phys);
157        let used_virt = crate::memory::phys_to_virt(used_phys);
158
159        let desc_ptr = desc_virt as *mut VirtqDesc;
160        let avail_ptr = avail_virt as *mut VirtqAvail;
161        let avail_ring_ptr = (avail_virt + 4) as *mut u16;
162        let used_ptr = used_virt as *mut VirtqUsed;
163        let used_ring_ptr = (used_virt + 4) as *mut VirtqUsedElem;
164
165        // Zero out the memory
166        // SAFETY: we allocated these pages and they're mapped via HHDM
167        core::ptr::write_bytes(desc_ptr as *mut u8, 0, total_size);
168
169        // Initialize free descriptor list
170        let mut free_descriptors = Vec::with_capacity(queue_size as usize);
171        for i in (0..queue_size).rev() {
172            free_descriptors.push(i);
173        }
174
175        Ok(Self {
176            queue_size,
177            _ring_area: ring_area,
178            desc_area: desc_phys,
179            avail_area: avail_phys,
180            used_area: used_phys,
181            desc_ptr,
182            avail_ptr,
183            avail_ring_ptr,
184            used_ptr,
185            used_ring_ptr,
186            free_descriptors,
187            last_used_idx: 0,
188            next_avail_idx: 0,
189        })
190    }
191
192    /// Get the physical address of the descriptor table
193    pub fn desc_area(&self) -> u64 {
194        self.desc_area
195    }
196
197    /// Get the physical address of the available ring
198    pub fn avail_area(&self) -> u64 {
199        self.avail_area
200    }
201
202    /// Get the physical address of the used ring
203    pub fn used_area(&self) -> u64 {
204        self.used_area
205    }
206
207    /// Get the queue size (number of descriptors)
208    pub fn queue_size(&self) -> usize {
209        self.queue_size as usize
210    }
211
212    /// Get the queue size
213    pub fn size(&self) -> u16 {
214        self.queue_size
215    }
216
217    /// Allocate a descriptor chain
218    ///
219    /// Returns the head descriptor index
220    pub fn alloc_descriptor(&mut self) -> Option<u16> {
221        self.free_descriptors.pop()
222    }
223
224    /// Free a descriptor chain
225    ///
226    /// Walks the chain following NEXT flags and frees all descriptors
227    pub fn free_descriptor(&mut self, head: u16) {
228        let mut current = head;
229
230        loop {
231            // SAFETY: current is a valid descriptor index
232            let desc = unsafe { &*self.desc_ptr.add(current as usize) };
233            let has_next = desc.flags.to_ne() & vring_flags::NEXT != 0;
234            let next = desc.next.to_ne();
235
236            self.free_descriptors.push(current);
237
238            if !has_next {
239                break;
240            }
241            current = next;
242        }
243    }
244
245    /// Add a buffer to the virtqueue
246    ///
247    /// Returns the descriptor index (token) that can be used to track completion
248    ///
249    /// # Arguments
250    /// * `buffers`: A list of (physical_address, length, is_write_only)
251    pub fn add_buffer(&mut self, buffers: &[(u64, u32, bool)]) -> Result<u16, &'static str> {
252        if buffers.is_empty() {
253            return Err("Empty buffer list");
254        }
255
256        if buffers.len() > self.free_descriptors.len() {
257            return Err("Not enough free descriptors");
258        }
259
260        // Allocate descriptor chain
261        let head = self.alloc_descriptor().ok_or("No free descriptors")?;
262        let mut current = head;
263
264        for (i, &(addr, len, write)) in buffers.iter().enumerate() {
265            let is_last = i == buffers.len() - 1;
266
267            // SAFETY: current is a valid index regulated by alloc_descriptor
268            let desc = unsafe { &mut *self.desc_ptr.add(current as usize) };
269            desc.addr = Le::<u64>::from_ne(addr);
270            desc.len = Le::<u32>::from_ne(len);
271            desc.flags = Le::<u16>::from_ne(if write { vring_flags::WRITE } else { 0 });
272
273            if !is_last {
274                let next = self.alloc_descriptor().ok_or("No free descriptors")?;
275                desc.flags = Le::<u16>::from_ne(desc.flags.to_ne() | vring_flags::NEXT);
276                desc.next = Le::<u16>::from_ne(next);
277                current = next;
278            }
279        }
280
281        // Add to available ring
282        // SAFETY: Atomic load
283        let avail_idx = unsafe { (*self.avail_ptr).idx.load(Ordering::Acquire) };
284        let ring_idx = (avail_idx % self.queue_size) as usize;
285
286        // SAFETY: ring_idx is bounded by queue_size
287        unsafe {
288            write_volatile(self.avail_ring_ptr.add(ring_idx), head);
289        }
290
291        // Memory barrier before updating index to ensure device sees the descriptor table updates
292        fence(Ordering::Release);
293
294        // Update available index
295        // SAFETY: Atomic store
296        unsafe {
297            (*self.avail_ptr)
298                .idx
299                .store(avail_idx.wrapping_add(1), Ordering::Release);
300        }
301
302        self.next_avail_idx = avail_idx.wrapping_add(1);
303
304        Ok(head)
305    }
306
307    /// Check if there are any used buffers
308    pub fn has_used(&self) -> bool {
309        // SAFETY: Atomic load
310        let used_idx = unsafe { (*self.used_ptr).idx.load(Ordering::Acquire) };
311        self.last_used_idx != used_idx
312    }
313
314    /// Return a snapshot of `(device_used_idx, driver_last_used_idx)` for diagnostics.
315    pub fn used_indices(&self) -> (u16, u16) {
316        // SAFETY: Atomic load from the used ring header.
317        let used_idx = unsafe { (*self.used_ptr).idx.load(Ordering::Acquire) };
318        (used_idx, self.last_used_idx)
319    }
320
321    /// Get the next used buffer
322    ///
323    /// Returns (descriptor_index, length_written)
324    pub fn get_used(&mut self) -> Option<(u16, u32)> {
325        // SAFETY: Atomic load
326        let used_idx = unsafe { (*self.used_ptr).idx.load(Ordering::Acquire) };
327
328        if self.last_used_idx == used_idx {
329            return None;
330        }
331
332        let ring_idx = (self.last_used_idx % self.queue_size) as usize;
333
334        // SAFETY: ring_idx is bounded by queue_size
335        let elem = unsafe { read_volatile(self.used_ring_ptr.add(ring_idx)) };
336
337        self.last_used_idx = self.last_used_idx.wrapping_add(1);
338
339        // We do NOT free the descriptor here immediately, because the caller might need to read the data.
340        // But in the current design, the caller is responsible for freeing.
341        // Wait, the previous implementation freed it here.
342        // Let's stick to the previous pattern: free the chain, return the Head ID.
343        // The implementation assumes the caller is done with the *descriptors*,
344        // but the data is in the buffers pointed to by the descriptors.
345        self.free_descriptor(elem.id as u16);
346
347        Some((elem.id as u16, elem.len))
348    }
349
350    /// Notify the device (should write to queue_notify register)
351    pub fn should_notify(&self) -> bool {
352        // Simple implementation: always notify
353        // Improved: Check VIRTQ_USED_F_NO_NOTIFY flag if we implemented negotiation
354        true
355    }
356}
357
358/// VirtIO device base
359///
360/// Common functionality for all VirtIO devices
361pub struct VirtioDevice {
362    /// PCI device
363    pub pci_dev: PciDevice,
364
365    /// I/O base address (BAR0 for legacy devices)
366    pub io_base: u16,
367}
368
369impl VirtioDevice {
370    /// Create a new VirtIO device from a PCI device
371    ///
372    /// # Safety
373    /// The PCI device must be a valid VirtIO device
374    pub unsafe fn new(pci_dev: PciDevice) -> Result<Self, &'static str> {
375        // Read BAR0 (I/O space for legacy VirtIO devices)
376        let bar0 = pci_dev.read_bar(0).ok_or("BAR0 not present")?;
377
378        let io_base = match bar0 {
379            Bar::Io { port } => port,
380            _ => return Err("BAR0 is not I/O space (legacy VirtIO required)"),
381        };
382
383        // Enable I/O space and bus mastering
384        pci_dev.enable_io_space();
385        pci_dev.enable_bus_master();
386
387        Ok(Self { pci_dev, io_base })
388    }
389
390    /// Read an 8-bit value from a device register
391    pub fn read_reg_u8(&self, offset: u16) -> u8 {
392        // SAFETY: I/O port access to VirtIO device registers
393        unsafe { crate::arch::x86_64::io::inb(self.io_base + offset) }
394    }
395
396    /// Read a 16-bit value from a device register
397    pub fn read_reg_u16(&self, offset: u16) -> u16 {
398        // SAFETY: I/O port access to VirtIO device registers
399        unsafe { crate::arch::x86_64::io::inw(self.io_base + offset) }
400    }
401
402    /// Read a 32-bit value from a device register
403    pub fn read_reg_u32(&self, offset: u16) -> u32 {
404        // SAFETY: I/O port access to VirtIO device registers
405        unsafe { crate::arch::x86_64::io::inl(self.io_base + offset) }
406    }
407
408    /// Write an 8-bit value to a device register
409    pub fn write_reg_u8(&self, offset: u16, value: u8) {
410        // SAFETY: I/O port access to VirtIO device registers
411        unsafe { crate::arch::x86_64::io::outb(self.io_base + offset, value) }
412    }
413
414    /// Write a 16-bit value to a device register
415    pub fn write_reg_u16(&self, offset: u16, value: u16) {
416        // SAFETY: I/O port access to VirtIO device registers
417        unsafe { crate::arch::x86_64::io::outw(self.io_base + offset, value) }
418    }
419
420    /// Write a 32-bit value to a device register
421    pub fn write_reg_u32(&self, offset: u16, value: u32) {
422        // SAFETY: I/O port access to VirtIO device registers
423        unsafe { crate::arch::x86_64::io::outl(self.io_base + offset, value) }
424    }
425
426    /// Read device features
427    pub fn read_device_features(&self) -> u32 {
428        self.read_reg_u32(0) // VIRTIO_PCI_HOST_FEATURES
429    }
430
431    /// Write guest features
432    pub fn write_guest_features(&self, features: u32) {
433        self.write_reg_u32(4, features); // VIRTIO_PCI_GUEST_FEATURES
434    }
435
436    /// Get device status
437    pub fn get_status(&self) -> u8 {
438        self.read_reg_u8(18) // VIRTIO_PCI_STATUS
439    }
440
441    /// Set device status
442    pub fn set_status(&self, status: u8) {
443        self.write_reg_u8(18, status); // VIRTIO_PCI_STATUS
444    }
445
446    /// Add status flags
447    pub fn add_status(&self, status: u8) {
448        let current = self.get_status();
449        self.set_status(current | status);
450    }
451
452    /// Reset the device
453    pub fn reset(&self) {
454        self.set_status(0);
455    }
456
457    /// Read ISR status (clears interrupt)
458    pub fn read_isr_status(&self) -> u8 {
459        self.read_reg_u8(19) // VIRTIO_PCI_ISR
460    }
461
462    /// Acknowledge interrupt (write 0 to ISR)
463    pub fn ack_interrupt(&self) {
464        // Reading ISR already clears it, but we can also write to acknowledge
465        let _ = self.read_reg_u8(19); // VIRTIO_PCI_ISR
466    }
467
468    /// Setup a virtqueue
469    pub fn setup_queue(&self, queue_index: u16, queue: &Virtqueue) {
470        // Select queue
471        self.write_reg_u16(14, queue_index); // VIRTIO_PCI_QUEUE_SEL
472
473        // Read max queue size; warn if our size exceeds it
474        let max = self.read_reg_u16(12); // VIRTIO_PCI_QUEUE_NUM
475        if max != 0 && (queue.queue_size() as u16) > max {
476            log::warn!(
477                "virtio: queue {} size {} > device max {}",
478                queue_index,
479                queue.queue_size(),
480                max,
481            );
482        }
483
484        // Set queue addresses (page-aligned physical addresses >> 12)
485        let desc_pfn = (queue.desc_area() >> 12) as u32;
486        self.write_reg_u32(8, desc_pfn); // VIRTIO_PCI_QUEUE_PFN
487
488        log::info!(
489            "virtio: queue {} set up (size={}, pfn={:#x})",
490            queue_index,
491            queue.queue_size(),
492            desc_pfn,
493        );
494    }
495
496    /// Read the queue size exposed by the selected legacy PCI queue.
497    pub fn queue_max_size(&self, queue_index: u16) -> u16 {
498        self.write_reg_u16(14, queue_index); // VIRTIO_PCI_QUEUE_SEL
499        self.read_reg_u16(12) // VIRTIO_PCI_QUEUE_NUM
500    }
501
502    /// Notify a queue
503    pub fn notify_queue(&self, queue_index: u16) {
504        // Write as 32-bit: some QEMU/config combos ignore 16-bit writes
505        // to the QueueNotify register.
506        self.write_reg_u32(16, queue_index as u32); // VIRTIO_PCI_QUEUE_NOTIFY
507    }
508}