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

veridian_kernel/virt/hypervisor/
passthrough.rs

1//! VirtIO Device Passthrough
2//!
3//! Device assignment, MMIO mapping, interrupt forwarding for passthrough
4//! devices.
5
6#[cfg(feature = "alloc")]
7use alloc::vec::Vec;
8
9use super::MAX_MSIX_VECTORS;
10use crate::virt::VmError;
11
12// ---------------------------------------------------------------------------
13// 2. VirtIO Device Passthrough
14// ---------------------------------------------------------------------------
15
16/// Passthrough device type
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum PassthroughDeviceType {
19    /// VirtIO network device
20    VirtioNet,
21    /// VirtIO block device
22    VirtioBlk,
23    /// VirtIO GPU device
24    VirtioGpu,
25    /// VirtIO sound device
26    VirtioSound,
27    /// Generic PCI device
28    GenericPci,
29}
30
31/// MSI-X vector remapping entry
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub struct MsixRemap {
34    /// Host vector number
35    pub host_vector: u16,
36    /// Guest vector number
37    pub guest_vector: u16,
38    /// Target vCPU for delivery
39    pub target_vcpu: u8,
40    /// Whether this remap is active
41    pub active: bool,
42}
43
44/// MMIO region mapping for passthrough device
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct MmioRegion {
47    /// Host physical address
48    pub host_phys: u64,
49    /// Guest physical address (mapped into guest EPT)
50    pub guest_phys: u64,
51    /// Region size in bytes
52    pub size: u64,
53    /// Whether the region is currently mapped
54    pub mapped: bool,
55}
56
57/// PCI BAR (Base Address Register) info
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct PciBar {
60    /// BAR index (0-5)
61    pub index: u8,
62    /// Base address
63    pub address: u64,
64    /// Size in bytes
65    pub size: u64,
66    /// Whether this is a memory BAR (vs I/O)
67    pub is_memory: bool,
68    /// Whether this is a 64-bit BAR
69    pub is_64bit: bool,
70    /// Whether this is prefetchable
71    pub prefetchable: bool,
72}
73
74/// PCI configuration space passthrough
75#[derive(Debug, Clone)]
76pub struct PciConfigPassthrough {
77    /// Vendor ID
78    pub vendor_id: u16,
79    /// Device ID
80    pub device_id: u16,
81    /// BDF (bus:device:function)
82    pub bdf: u32,
83    /// BARs
84    #[cfg(feature = "alloc")]
85    pub bars: Vec<PciBar>,
86    /// Emulated config space (256 bytes for type 0)
87    pub config_space: [u8; 256],
88    /// Which config registers are writable by guest
89    pub writable_mask: [u8; 256],
90}
91
92impl PciConfigPassthrough {
93    #[cfg(feature = "alloc")]
94    pub fn new(vendor_id: u16, device_id: u16, bdf: u32) -> Self {
95        let mut config_space = [0u8; 256];
96        // Set vendor/device ID
97        config_space[0] = vendor_id as u8;
98        config_space[1] = (vendor_id >> 8) as u8;
99        config_space[2] = device_id as u8;
100        config_space[3] = (device_id >> 8) as u8;
101
102        let mut writable_mask = [0u8; 256];
103        // Command register is writable
104        writable_mask[4] = 0xFF;
105        writable_mask[5] = 0xFF;
106        // BAR registers (0x10-0x27)
107        for item in writable_mask.iter_mut().take(0x27 + 1).skip(0x10) {
108            *item = 0xFF;
109        }
110
111        Self {
112            vendor_id,
113            device_id,
114            bdf,
115            bars: Vec::new(),
116            config_space,
117            writable_mask,
118        }
119    }
120
121    /// Read from config space
122    pub fn read_config(&self, offset: u8) -> u8 {
123        self.config_space[offset as usize]
124    }
125
126    /// Write to config space (respecting writable mask)
127    pub fn write_config(&mut self, offset: u8, value: u8) {
128        let mask = self.writable_mask[offset as usize];
129        let idx = offset as usize;
130        self.config_space[idx] = (self.config_space[idx] & !mask) | (value & mask);
131    }
132
133    /// Read 32-bit config space value
134    pub fn read_config32(&self, offset: u8) -> u32 {
135        let idx = (offset & 0xFC) as usize;
136        u32::from_le_bytes([
137            self.config_space[idx],
138            self.config_space[idx + 1],
139            self.config_space[idx + 2],
140            self.config_space[idx + 3],
141        ])
142    }
143}
144
145/// A passthrough device assigned to a guest VM
146#[cfg(feature = "alloc")]
147pub struct PassthroughDevice {
148    /// Device type
149    pub device_type: PassthroughDeviceType,
150    /// PCI configuration space
151    pub pci_config: PciConfigPassthrough,
152    /// MMIO regions mapped into guest
153    pub mmio_regions: Vec<MmioRegion>,
154    /// MSI-X vector remappings
155    pub msix_remaps: Vec<MsixRemap>,
156    /// Whether the device is currently assigned to a guest
157    pub assigned: bool,
158    /// VM ID that owns this device
159    pub owner_vm_id: u64,
160}
161
162#[cfg(feature = "alloc")]
163impl PassthroughDevice {
164    pub fn new(
165        device_type: PassthroughDeviceType,
166        vendor_id: u16,
167        device_id: u16,
168        bdf: u32,
169    ) -> Self {
170        Self {
171            device_type,
172            pci_config: PciConfigPassthrough::new(vendor_id, device_id, bdf),
173            mmio_regions: Vec::new(),
174            msix_remaps: Vec::new(),
175            assigned: false,
176            owner_vm_id: 0,
177        }
178    }
179
180    /// Assign device to a VM
181    pub fn assign_to_vm(&mut self, vm_id: u64) -> Result<(), VmError> {
182        if self.assigned {
183            return Err(VmError::DeviceError);
184        }
185        self.assigned = true;
186        self.owner_vm_id = vm_id;
187        Ok(())
188    }
189
190    /// Unassign device from VM (reset on guest exit)
191    pub fn unassign(&mut self) {
192        self.assigned = false;
193        self.owner_vm_id = 0;
194        // Reset MSI-X remaps
195        for remap in &mut self.msix_remaps {
196            remap.active = false;
197        }
198        // Unmap MMIO regions
199        for region in &mut self.mmio_regions {
200            region.mapped = false;
201        }
202    }
203
204    /// Add an MMIO region mapping
205    pub fn add_mmio_region(&mut self, host_phys: u64, guest_phys: u64, size: u64) {
206        self.mmio_regions.push(MmioRegion {
207            host_phys,
208            guest_phys,
209            size,
210            mapped: true,
211        });
212    }
213
214    /// Add an MSI-X remap entry
215    pub fn add_msix_remap(&mut self, host_vector: u16, guest_vector: u16, target_vcpu: u8) {
216        if self.msix_remaps.len() < MAX_MSIX_VECTORS {
217            self.msix_remaps.push(MsixRemap {
218                host_vector,
219                guest_vector,
220                target_vcpu,
221                active: true,
222            });
223        }
224    }
225
226    /// Look up guest vector for a host interrupt
227    pub fn remap_interrupt(&self, host_vector: u16) -> Option<(u16, u8)> {
228        for remap in &self.msix_remaps {
229            if remap.active && remap.host_vector == host_vector {
230                return Some((remap.guest_vector, remap.target_vcpu));
231            }
232        }
233        None
234    }
235
236    /// Reset device to initial state
237    pub fn reset(&mut self) {
238        self.pci_config.config_space[4] = 0; // Command register
239        self.pci_config.config_space[5] = 0;
240        for remap in &mut self.msix_remaps {
241            remap.active = false;
242        }
243    }
244
245    pub fn is_assigned(&self) -> bool {
246        self.assigned
247    }
248
249    pub fn mmio_region_count(&self) -> usize {
250        self.mmio_regions.len()
251    }
252
253    pub fn msix_remap_count(&self) -> usize {
254        self.msix_remaps.len()
255    }
256}