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