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_phys_contiguous(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    /// Return a snapshot of `(device_used_idx, driver_last_used_idx)` for diagnostics.
305    pub fn used_indices(&self) -> (u16, u16) {
306        // SAFETY: Atomic load from the used ring header.
307        let used_idx = unsafe { (*self.used_ptr).idx.load(Ordering::Acquire) };
308        (used_idx, self.last_used_idx)
309    }
310
311    /// Get the next used buffer
312    ///
313    /// Returns (descriptor_index, length_written)
314    pub fn get_used(&mut self) -> Option<(u16, u32)> {
315        // SAFETY: Atomic load
316        let used_idx = unsafe { (*self.used_ptr).idx.load(Ordering::Acquire) };
317
318        if self.last_used_idx == used_idx {
319            return None;
320        }
321
322        let ring_idx = (self.last_used_idx % self.queue_size) as usize;
323
324        // SAFETY: ring_idx is bounded by queue_size
325        let elem = unsafe { read_volatile(self.used_ring_ptr.add(ring_idx)) };
326
327        self.last_used_idx = self.last_used_idx.wrapping_add(1);
328
329        // We do NOT free the descriptor here immediately, because the caller might need to read the data.
330        // But in the current design, the caller is responsible for freeing.
331        // Wait, the previous implementation freed it here.
332        // Let's stick to the previous pattern: free the chain, return the Head ID.
333        // The implementation assumes the caller is done with the *descriptors*,
334        // but the data is in the buffers pointed to by the descriptors.
335        self.free_descriptor(elem.id as u16);
336
337        Some((elem.id as u16, elem.len))
338    }
339
340    /// Notify the device (should write to queue_notify register)
341    pub fn should_notify(&self) -> bool {
342        // Simple implementation: always notify
343        // Improved: Check VIRTQ_USED_F_NO_NOTIFY flag if we implemented negotiation
344        true
345    }
346}
347
348/// VirtIO device base
349///
350/// Common functionality for all VirtIO devices
351pub struct VirtioDevice {
352    /// PCI device
353    pub pci_dev: PciDevice,
354
355    /// I/O base address (BAR0 for legacy devices)
356    pub io_base: u16,
357}
358
359impl VirtioDevice {
360    /// Create a new VirtIO device from a PCI device
361    ///
362    /// # Safety
363    /// The PCI device must be a valid VirtIO device
364    pub unsafe fn new(pci_dev: PciDevice) -> Result<Self, &'static str> {
365        // Read BAR0 (I/O space for legacy VirtIO devices)
366        let bar0 = pci_dev.read_bar(0).ok_or("BAR0 not present")?;
367
368        let io_base = match bar0 {
369            Bar::Io { port } => port,
370            _ => return Err("BAR0 is not I/O space (legacy VirtIO required)"),
371        };
372
373        // Enable I/O space and bus mastering
374        pci_dev.enable_io_space();
375        pci_dev.enable_bus_master();
376
377        Ok(Self { pci_dev, io_base })
378    }
379
380    /// Read an 8-bit value from a device register
381    pub fn read_reg_u8(&self, offset: u16) -> u8 {
382        // SAFETY: I/O port access to VirtIO device registers
383        unsafe { crate::arch::x86_64::io::inb(self.io_base + offset) }
384    }
385
386    /// Read a 16-bit value from a device register
387    pub fn read_reg_u16(&self, offset: u16) -> u16 {
388        // SAFETY: I/O port access to VirtIO device registers
389        unsafe { crate::arch::x86_64::io::inw(self.io_base + offset) }
390    }
391
392    /// Read a 32-bit value from a device register
393    pub fn read_reg_u32(&self, offset: u16) -> u32 {
394        // SAFETY: I/O port access to VirtIO device registers
395        unsafe { crate::arch::x86_64::io::inl(self.io_base + offset) }
396    }
397
398    /// Write an 8-bit value to a device register
399    pub fn write_reg_u8(&self, offset: u16, value: u8) {
400        // SAFETY: I/O port access to VirtIO device registers
401        unsafe { crate::arch::x86_64::io::outb(self.io_base + offset, value) }
402    }
403
404    /// Write a 16-bit value to a device register
405    pub fn write_reg_u16(&self, offset: u16, value: u16) {
406        // SAFETY: I/O port access to VirtIO device registers
407        unsafe { crate::arch::x86_64::io::outw(self.io_base + offset, value) }
408    }
409
410    /// Write a 32-bit value to a device register
411    pub fn write_reg_u32(&self, offset: u16, value: u32) {
412        // SAFETY: I/O port access to VirtIO device registers
413        unsafe { crate::arch::x86_64::io::outl(self.io_base + offset, value) }
414    }
415
416    /// Read device features
417    pub fn read_device_features(&self) -> u32 {
418        self.read_reg_u32(0) // VIRTIO_PCI_HOST_FEATURES
419    }
420
421    /// Write guest features
422    pub fn write_guest_features(&self, features: u32) {
423        self.write_reg_u32(4, features); // VIRTIO_PCI_GUEST_FEATURES
424    }
425
426    /// Get device status
427    pub fn get_status(&self) -> u8 {
428        self.read_reg_u8(18) // VIRTIO_PCI_STATUS
429    }
430
431    /// Set device status
432    pub fn set_status(&self, status: u8) {
433        self.write_reg_u8(18, status); // VIRTIO_PCI_STATUS
434    }
435
436    /// Add status flags
437    pub fn add_status(&self, status: u8) {
438        let current = self.get_status();
439        self.set_status(current | status);
440    }
441
442    /// Reset the device
443    pub fn reset(&self) {
444        self.set_status(0);
445    }
446
447    /// Read ISR status (clears interrupt)
448    pub fn read_isr_status(&self) -> u8 {
449        self.read_reg_u8(19) // VIRTIO_PCI_ISR
450    }
451
452    /// Acknowledge interrupt (write 0 to ISR)
453    pub fn ack_interrupt(&self) {
454        // Reading ISR already clears it, but we can also write to acknowledge
455        let _ = self.read_reg_u8(19); // VIRTIO_PCI_ISR
456    }
457
458    /// Setup a virtqueue
459    pub fn setup_queue(&self, queue_index: u16, queue: &Virtqueue) {
460        // Select queue
461        self.write_reg_u16(14, queue_index); // VIRTIO_PCI_QUEUE_SEL
462
463        // Set queue addresses (page-aligned physical addresses >> 12)
464        let desc_pfn = (queue.desc_area() >> 12) as u32;
465        self.write_reg_u32(8, desc_pfn); // VIRTIO_PCI_QUEUE_PFN
466    }
467
468    /// Read the queue size exposed by the selected legacy PCI queue.
469    pub fn queue_max_size(&self, queue_index: u16) -> u16 {
470        self.write_reg_u16(14, queue_index); // VIRTIO_PCI_QUEUE_SEL
471        self.read_reg_u16(12) // VIRTIO_PCI_QUEUE_NUM
472    }
473
474    /// Notify a queue
475    pub fn notify_queue(&self, queue_index: u16) {
476        self.write_reg_u16(16, queue_index); // VIRTIO_PCI_QUEUE_NOTIFY
477    }
478}