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

veridian_kernel/virt/
sriov.rs

1//! SR-IOV (Single Root I/O Virtualization) support
2//!
3//! Implements SR-IOV capability parsing, VF enable/disable, and VF-to-VM
4//! assignment for high-performance device sharing.
5//!
6//! Sprint W5-S8.
7
8#![allow(dead_code)]
9
10#[cfg(feature = "alloc")]
11extern crate alloc;
12
13#[cfg(feature = "alloc")]
14use alloc::{collections::BTreeMap, vec::Vec};
15
16use super::{vfio::PciAddress, VmError};
17
18// ---------------------------------------------------------------------------
19// Constants
20// ---------------------------------------------------------------------------
21
22/// SR-IOV PCI Extended Capability ID
23const SRIOV_CAP_ID: u16 = 0x0010;
24
25/// Maximum VFs per physical function
26const MAX_VFS: usize = 256;
27
28/// SR-IOV capability register offsets (from capability start)
29const SRIOV_CAP_OFFSET: u16 = 0x04;
30const SRIOV_CTRL_OFFSET: u16 = 0x08;
31const SRIOV_TOTAL_VFS_OFFSET: u16 = 0x0E;
32const SRIOV_NUM_VFS_OFFSET: u16 = 0x10;
33const SRIOV_VF_OFFSET_OFFSET: u16 = 0x14;
34const SRIOV_VF_STRIDE_OFFSET: u16 = 0x16;
35const SRIOV_VF_DEVICE_ID_OFFSET: u16 = 0x1A;
36
37/// SR-IOV control register bits
38const SRIOV_CTRL_VF_ENABLE: u16 = 0x0001;
39const SRIOV_CTRL_VF_MIGRATION: u16 = 0x0002;
40const SRIOV_CTRL_ARI_CAPABLE: u16 = 0x0010;
41
42// ---------------------------------------------------------------------------
43// SR-IOV Capability
44// ---------------------------------------------------------------------------
45
46/// Parsed SR-IOV capability from PCI config space
47#[derive(Debug, Clone, Copy)]
48pub struct SriovCapability {
49    /// Offset of the SR-IOV capability in PCI config space
50    pub offset: u16,
51    /// Total number of VFs supported by hardware
52    pub total_vfs: u16,
53    /// Currently enabled number of VFs
54    pub num_vfs: u16,
55    /// First VF offset (RID offset from PF)
56    pub vf_offset: u16,
57    /// VF stride (RID stride between consecutive VFs)
58    pub vf_stride: u16,
59    /// VF device ID
60    pub vf_device_id: u16,
61    /// SR-IOV capability flags
62    pub capabilities: u32,
63    /// Whether VF migration is supported
64    pub migration_capable: bool,
65    /// Whether ARI (Alternative Routing-ID Interpretation) is capable
66    pub ari_capable: bool,
67}
68
69impl SriovCapability {
70    /// Parse SR-IOV capability from a config space data buffer
71    ///
72    /// `data` should contain the SR-IOV capability structure starting at index
73    /// 0.
74    pub fn parse(data: &[u8], offset: u16) -> Result<Self, VmError> {
75        if data.len() < 0x24 {
76            return Err(VmError::DeviceError);
77        }
78
79        let capabilities = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
80        let ctrl = u16::from_le_bytes([data[8], data[9]]);
81        let total_vfs = u16::from_le_bytes([data[0x0E], data[0x0F]]);
82        let num_vfs = u16::from_le_bytes([data[0x10], data[0x11]]);
83        let vf_offset = u16::from_le_bytes([data[0x14], data[0x15]]);
84        let vf_stride = u16::from_le_bytes([data[0x16], data[0x17]]);
85        let vf_device_id = u16::from_le_bytes([data[0x1A], data[0x1B]]);
86
87        Ok(Self {
88            offset,
89            total_vfs,
90            num_vfs,
91            vf_offset,
92            vf_stride,
93            vf_device_id,
94            capabilities,
95            migration_capable: ctrl & SRIOV_CTRL_VF_MIGRATION != 0,
96            ari_capable: ctrl & SRIOV_CTRL_ARI_CAPABLE != 0,
97        })
98    }
99
100    /// Calculate the PCI address of a specific VF
101    pub fn vf_address(&self, pf: &PciAddress, vf_index: u16) -> Option<PciAddress> {
102        if vf_index >= self.total_vfs {
103            return None;
104        }
105        let pf_bdf = pf.to_bdf();
106        let vf_bdf = pf_bdf
107            .checked_add(self.vf_offset)?
108            .checked_add(self.vf_stride.checked_mul(vf_index)?)?;
109        Some(PciAddress::from_bdf(vf_bdf))
110    }
111
112    /// Get the SR-IOV extended capability ID
113    pub fn cap_id() -> u16 {
114        SRIOV_CAP_ID
115    }
116}
117
118// ---------------------------------------------------------------------------
119// Virtual Function
120// ---------------------------------------------------------------------------
121
122/// State of a Virtual Function
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
124pub enum VfState {
125    /// VF is not enabled
126    #[default]
127    Disabled,
128    /// VF is enabled but not assigned
129    Enabled,
130    /// VF is assigned to a VM
131    Assigned,
132}
133
134/// A single Virtual Function instance
135#[derive(Debug, Clone, Copy)]
136pub struct VirtualFunction {
137    /// VF index within the PF's VF space
138    pub vf_index: u16,
139    /// PCI address of this VF
140    pub pci_address: PciAddress,
141    /// Whether this VF is enabled
142    pub enabled: bool,
143    /// VM this VF is assigned to (None = not assigned)
144    pub assigned_vm: Option<u32>,
145    /// Current state
146    pub state: VfState,
147}
148
149impl VirtualFunction {
150    /// Create a new VF
151    pub fn new(vf_index: u16, pci_address: PciAddress) -> Self {
152        Self {
153            vf_index,
154            pci_address,
155            enabled: false,
156            assigned_vm: None,
157            state: VfState::Disabled,
158        }
159    }
160
161    /// Enable this VF
162    pub fn enable(&mut self) {
163        self.enabled = true;
164        self.state = VfState::Enabled;
165    }
166
167    /// Disable this VF
168    pub fn disable(&mut self) {
169        self.enabled = false;
170        self.assigned_vm = None;
171        self.state = VfState::Disabled;
172    }
173
174    /// Assign this VF to a VM
175    pub fn assign(&mut self, vm_id: u32) -> Result<(), VmError> {
176        if !self.enabled {
177            return Err(VmError::DeviceError);
178        }
179        if self.assigned_vm.is_some() {
180            return Err(VmError::DeviceError);
181        }
182        self.assigned_vm = Some(vm_id);
183        self.state = VfState::Assigned;
184        Ok(())
185    }
186
187    /// Unassign this VF from its VM
188    pub fn unassign(&mut self) {
189        self.assigned_vm = None;
190        if self.enabled {
191            self.state = VfState::Enabled;
192        } else {
193            self.state = VfState::Disabled;
194        }
195    }
196
197    /// Check if this VF is available for assignment
198    pub fn is_available(&self) -> bool {
199        self.enabled && self.assigned_vm.is_none()
200    }
201}
202
203// ---------------------------------------------------------------------------
204// SR-IOV Device (Physical Function)
205// ---------------------------------------------------------------------------
206
207/// An SR-IOV physical function with its virtual functions
208#[cfg(feature = "alloc")]
209pub struct SriovDevice {
210    /// PCI address of the physical function
211    pub pf_address: PciAddress,
212    /// Parsed SR-IOV capability
213    pub capability: SriovCapability,
214    /// Virtual functions
215    pub vfs: Vec<VirtualFunction>,
216    /// Whether VFs are currently enabled
217    pub vfs_enabled: bool,
218}
219
220#[cfg(feature = "alloc")]
221impl SriovDevice {
222    /// Create a new SR-IOV device from a PF and capability
223    pub fn new(pf_address: PciAddress, capability: SriovCapability) -> Self {
224        Self {
225            pf_address,
226            capability,
227            vfs: Vec::new(),
228            vfs_enabled: false,
229        }
230    }
231
232    /// Parse capability from config space data
233    pub fn parse_capability(
234        pf_address: PciAddress,
235        data: &[u8],
236        offset: u16,
237    ) -> Result<Self, VmError> {
238        let cap = SriovCapability::parse(data, offset)?;
239        Ok(Self::new(pf_address, cap))
240    }
241
242    /// Enable VFs (creates VF entries)
243    pub fn enable_vfs(&mut self, num_vfs: u16) -> Result<(), VmError> {
244        if num_vfs > self.capability.total_vfs || num_vfs as usize > MAX_VFS {
245            return Err(VmError::DeviceError);
246        }
247        if self.vfs_enabled {
248            return Err(VmError::VmxAlreadyEnabled);
249        }
250
251        self.vfs.clear();
252        for i in 0..num_vfs {
253            let vf_addr = self
254                .capability
255                .vf_address(&self.pf_address, i)
256                .ok_or(VmError::DeviceError)?;
257            let mut vf = VirtualFunction::new(i, vf_addr);
258            vf.enable();
259            self.vfs.push(vf);
260        }
261        self.capability.num_vfs = num_vfs;
262        self.vfs_enabled = true;
263        Ok(())
264    }
265
266    /// Disable all VFs
267    pub fn disable_vfs(&mut self) {
268        for vf in &mut self.vfs {
269            vf.disable();
270        }
271        self.vfs.clear();
272        self.capability.num_vfs = 0;
273        self.vfs_enabled = false;
274    }
275
276    /// Assign a VF to a VM
277    pub fn assign_vf(&mut self, vf_index: u16, vm_id: u32) -> Result<(), VmError> {
278        let vf = self
279            .vfs
280            .iter_mut()
281            .find(|v| v.vf_index == vf_index)
282            .ok_or(VmError::DeviceError)?;
283        vf.assign(vm_id)
284    }
285
286    /// Unassign a VF from its VM
287    pub fn unassign_vf(&mut self, vf_index: u16) -> Result<(), VmError> {
288        let vf = self
289            .vfs
290            .iter_mut()
291            .find(|v| v.vf_index == vf_index)
292            .ok_or(VmError::DeviceError)?;
293        vf.unassign();
294        Ok(())
295    }
296
297    /// Get a VF by index
298    pub fn vf(&self, vf_index: u16) -> Option<&VirtualFunction> {
299        self.vfs.iter().find(|v| v.vf_index == vf_index)
300    }
301
302    /// Get number of enabled VFs
303    pub fn num_enabled_vfs(&self) -> usize {
304        self.vfs.iter().filter(|v| v.enabled).count()
305    }
306
307    /// Get number of assigned VFs
308    pub fn num_assigned_vfs(&self) -> usize {
309        self.vfs.iter().filter(|v| v.assigned_vm.is_some()).count()
310    }
311
312    /// List available (unassigned) VFs
313    pub fn available_vfs(&self) -> Vec<u16> {
314        self.vfs
315            .iter()
316            .filter(|v| v.is_available())
317            .map(|v| v.vf_index)
318            .collect()
319    }
320}
321
322// ---------------------------------------------------------------------------
323// SR-IOV Manager
324// ---------------------------------------------------------------------------
325
326/// Manager for all SR-IOV devices in the system
327#[cfg(feature = "alloc")]
328pub struct SriovManager {
329    /// Known SR-IOV devices keyed by PF BDF
330    devices: BTreeMap<u16, SriovDevice>,
331}
332
333#[cfg(feature = "alloc")]
334impl Default for SriovManager {
335    fn default() -> Self {
336        Self::new()
337    }
338}
339
340#[cfg(feature = "alloc")]
341impl SriovManager {
342    /// Create a new SR-IOV manager
343    pub fn new() -> Self {
344        Self {
345            devices: BTreeMap::new(),
346        }
347    }
348
349    /// Discover and register an SR-IOV device
350    pub fn discover(&mut self, device: SriovDevice) {
351        let bdf = device.pf_address.to_bdf();
352        self.devices.insert(bdf, device);
353    }
354
355    /// Get a device by PF address
356    pub fn get_device(&self, pf: &PciAddress) -> Option<&SriovDevice> {
357        self.devices.get(&pf.to_bdf())
358    }
359
360    /// Get a mutable device by PF address
361    pub fn get_device_mut(&mut self, pf: &PciAddress) -> Option<&mut SriovDevice> {
362        self.devices.get_mut(&pf.to_bdf())
363    }
364
365    /// List all VFs across all devices
366    pub fn list_vfs(&self) -> Vec<(PciAddress, &VirtualFunction)> {
367        let mut result = Vec::new();
368        for dev in self.devices.values() {
369            for vf in &dev.vfs {
370                result.push((dev.pf_address, vf));
371            }
372        }
373        result
374    }
375
376    /// Get total number of registered SR-IOV devices
377    pub fn device_count(&self) -> usize {
378        self.devices.len()
379    }
380
381    /// Get total number of enabled VFs
382    pub fn total_vfs(&self) -> usize {
383        self.devices.values().map(|d| d.num_enabled_vfs()).sum()
384    }
385
386    /// Get total number of assigned VFs
387    pub fn total_assigned_vfs(&self) -> usize {
388        self.devices.values().map(|d| d.num_assigned_vfs()).sum()
389    }
390}
391
392// ---------------------------------------------------------------------------
393// Tests
394// ---------------------------------------------------------------------------
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399
400    /// Create a fake SR-IOV capability config space block
401    fn make_sriov_config(
402        total_vfs: u16,
403        vf_offset: u16,
404        vf_stride: u16,
405        vf_dev_id: u16,
406    ) -> [u8; 0x24] {
407        let mut data = [0u8; 0x24];
408        // Capabilities at offset 4 (u32)
409        data[4] = 0x01; // Some caps
410                        // Control at offset 8 (u16)
411        data[8] = 0x00;
412        // Total VFs at offset 0x0E
413        data[0x0E] = total_vfs as u8;
414        data[0x0F] = (total_vfs >> 8) as u8;
415        // Num VFs at offset 0x10
416        data[0x10] = 0;
417        // VF offset at 0x14
418        data[0x14] = vf_offset as u8;
419        data[0x15] = (vf_offset >> 8) as u8;
420        // VF stride at 0x16
421        data[0x16] = vf_stride as u8;
422        data[0x17] = (vf_stride >> 8) as u8;
423        // VF device ID at 0x1A
424        data[0x1A] = vf_dev_id as u8;
425        data[0x1B] = (vf_dev_id >> 8) as u8;
426        data
427    }
428
429    #[test]
430    fn test_sriov_capability_parse() {
431        let config = make_sriov_config(8, 1, 1, 0x1234);
432        let cap = SriovCapability::parse(&config, 0x100).unwrap();
433        assert_eq!(cap.total_vfs, 8);
434        assert_eq!(cap.vf_offset, 1);
435        assert_eq!(cap.vf_stride, 1);
436        assert_eq!(cap.vf_device_id, 0x1234);
437        assert_eq!(cap.offset, 0x100);
438    }
439
440    #[test]
441    fn test_sriov_capability_parse_too_short() {
442        let data = [0u8; 10];
443        assert!(SriovCapability::parse(&data, 0).is_err());
444    }
445
446    #[test]
447    fn test_sriov_vf_address() {
448        let config = make_sriov_config(4, 2, 1, 0);
449        let cap = SriovCapability::parse(&config, 0).unwrap();
450        let pf = PciAddress::new(0, 3, 0);
451        // PF BDF = 0x0018, VF0 = 0x0018+2 = 0x001A, VF1 = 0x001B, etc.
452        let vf0 = cap.vf_address(&pf, 0).unwrap();
453        assert_eq!(vf0.to_bdf(), pf.to_bdf() + 2);
454        let vf1 = cap.vf_address(&pf, 1).unwrap();
455        assert_eq!(vf1.to_bdf(), pf.to_bdf() + 3);
456    }
457
458    #[test]
459    fn test_virtual_function_lifecycle() {
460        let mut vf = VirtualFunction::new(0, PciAddress::new(0, 3, 2));
461        assert_eq!(vf.state, VfState::Disabled);
462        assert!(!vf.is_available());
463
464        vf.enable();
465        assert_eq!(vf.state, VfState::Enabled);
466        assert!(vf.is_available());
467
468        vf.assign(1).unwrap();
469        assert_eq!(vf.state, VfState::Assigned);
470        assert!(!vf.is_available());
471        assert_eq!(vf.assigned_vm, Some(1));
472
473        vf.unassign();
474        assert_eq!(vf.state, VfState::Enabled);
475        assert!(vf.is_available());
476
477        vf.disable();
478        assert_eq!(vf.state, VfState::Disabled);
479    }
480
481    #[test]
482    fn test_vf_assign_disabled() {
483        let mut vf = VirtualFunction::new(0, PciAddress::new(0, 0, 0));
484        assert!(vf.assign(1).is_err());
485    }
486
487    #[test]
488    fn test_vf_double_assign() {
489        let mut vf = VirtualFunction::new(0, PciAddress::new(0, 0, 0));
490        vf.enable();
491        vf.assign(1).unwrap();
492        assert!(vf.assign(2).is_err());
493    }
494
495    #[test]
496    fn test_sriov_device_enable_vfs() {
497        let config = make_sriov_config(8, 1, 1, 0x5678);
498        let cap = SriovCapability::parse(&config, 0).unwrap();
499        let pf = PciAddress::new(0, 5, 0);
500        let mut dev = SriovDevice::new(pf, cap);
501
502        dev.enable_vfs(4).unwrap();
503        assert_eq!(dev.num_enabled_vfs(), 4);
504        assert!(dev.vfs_enabled);
505    }
506
507    #[test]
508    fn test_sriov_device_enable_too_many() {
509        let config = make_sriov_config(4, 1, 1, 0);
510        let cap = SriovCapability::parse(&config, 0).unwrap();
511        let mut dev = SriovDevice::new(PciAddress::new(0, 0, 0), cap);
512        assert!(dev.enable_vfs(5).is_err());
513    }
514
515    #[test]
516    fn test_sriov_device_disable_vfs() {
517        let config = make_sriov_config(8, 1, 1, 0);
518        let cap = SriovCapability::parse(&config, 0).unwrap();
519        let mut dev = SriovDevice::new(PciAddress::new(0, 0, 0), cap);
520        dev.enable_vfs(4).unwrap();
521        dev.disable_vfs();
522        assert_eq!(dev.num_enabled_vfs(), 0);
523        assert!(!dev.vfs_enabled);
524    }
525
526    #[test]
527    fn test_sriov_device_assign_vf() {
528        let config = make_sriov_config(8, 1, 1, 0);
529        let cap = SriovCapability::parse(&config, 0).unwrap();
530        let mut dev = SriovDevice::new(PciAddress::new(0, 0, 0), cap);
531        dev.enable_vfs(4).unwrap();
532        dev.assign_vf(0, 42).unwrap();
533        assert_eq!(dev.num_assigned_vfs(), 1);
534        assert_eq!(dev.vf(0).unwrap().assigned_vm, Some(42));
535    }
536
537    #[test]
538    fn test_sriov_device_unassign_vf() {
539        let config = make_sriov_config(8, 1, 1, 0);
540        let cap = SriovCapability::parse(&config, 0).unwrap();
541        let mut dev = SriovDevice::new(PciAddress::new(0, 0, 0), cap);
542        dev.enable_vfs(4).unwrap();
543        dev.assign_vf(1, 10).unwrap();
544        dev.unassign_vf(1).unwrap();
545        assert!(dev.vf(1).unwrap().is_available());
546    }
547
548    #[test]
549    fn test_sriov_manager() {
550        let config = make_sriov_config(4, 1, 1, 0);
551        let cap = SriovCapability::parse(&config, 0).unwrap();
552        let pf = PciAddress::new(0, 5, 0);
553        let mut dev = SriovDevice::new(pf, cap);
554        dev.enable_vfs(2).unwrap();
555
556        let mut mgr = SriovManager::new();
557        mgr.discover(dev);
558        assert_eq!(mgr.device_count(), 1);
559        assert_eq!(mgr.total_vfs(), 2);
560        assert_eq!(mgr.total_assigned_vfs(), 0);
561
562        let vfs = mgr.list_vfs();
563        assert_eq!(vfs.len(), 2);
564    }
565}