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

veridian_kernel/virt/hypervisor/
smp.rs

1//! Guest SMP Support
2//!
3//! Multi-vCPU VMs with per-vCPU VMCS, IPI, and SIPI emulation.
4
5#[cfg(feature = "alloc")]
6use alloc::{collections::BTreeMap, vec::Vec};
7
8use super::{GuestRegisters, MAX_VCPUS};
9use crate::virt::VmError;
10
11// ---------------------------------------------------------------------------
12// 4. Guest SMP Support
13// ---------------------------------------------------------------------------
14
15/// vCPU execution state
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum VcpuState {
18    /// Not yet started
19    #[default]
20    Created,
21    /// Running guest code
22    Running,
23    /// Halted (HLT instruction)
24    Halted,
25    /// Waiting for SIPI
26    WaitingForSipi,
27    /// Paused by hypervisor
28    Paused,
29    /// Stopped / destroyed
30    Stopped,
31}
32
33/// Inter-Processor Interrupt delivery mode
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum IpiDeliveryMode {
36    /// Fixed: deliver to specific vCPU
37    Fixed,
38    /// Lowest priority: deliver to lowest-priority vCPU
39    LowestPriority,
40    /// NMI: deliver NMI
41    Nmi,
42    /// INIT: send INIT signal
43    Init,
44    /// SIPI: Startup IPI (with vector for real-mode entry point)
45    Sipi,
46    /// ExtINT: external interrupt
47    ExtInt,
48}
49
50/// IPI message between vCPUs
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct IpiMessage {
53    /// Source vCPU ID
54    pub source: u8,
55    /// Destination vCPU ID (0xFF = broadcast)
56    pub destination: u8,
57    /// Delivery mode
58    pub delivery_mode: IpiDeliveryMode,
59    /// Vector number (for Fixed/SIPI)
60    pub vector: u8,
61    /// Level (0 = deassert, 1 = assert)
62    pub level: bool,
63    /// Trigger mode (true = level, false = edge)
64    pub trigger_level: bool,
65}
66
67/// Virtual CPU
68#[cfg(feature = "alloc")]
69pub struct VirtualCpu {
70    /// vCPU ID (0 = BSP, 1+ = APs)
71    pub id: u8,
72    /// Current execution state
73    pub state: VcpuState,
74    /// General-purpose registers
75    pub registers: GuestRegisters,
76    /// LAPIC ID for this vCPU
77    pub apic_id: u8,
78    /// Pending IPIs (queue)
79    pub pending_ipis: Vec<IpiMessage>,
80    /// Whether this is the bootstrap processor
81    pub is_bsp: bool,
82    /// Host thread affinity (which host CPU to schedule on)
83    pub host_affinity: Option<u32>,
84    /// SIPI vector (real-mode entry = vector * 0x1000)
85    pub sipi_vector: u8,
86    /// VMCS field values for this vCPU
87    pub vmcs_fields: BTreeMap<u32, u64>,
88}
89
90#[cfg(feature = "alloc")]
91impl VirtualCpu {
92    pub fn new(id: u8, is_bsp: bool) -> Self {
93        let initial_state = if is_bsp {
94            VcpuState::Created
95        } else {
96            VcpuState::WaitingForSipi
97        };
98
99        Self {
100            id,
101            state: initial_state,
102            registers: GuestRegisters::default(),
103            apic_id: id,
104            pending_ipis: Vec::new(),
105            is_bsp,
106            host_affinity: None,
107            sipi_vector: 0,
108            vmcs_fields: BTreeMap::new(),
109        }
110    }
111
112    /// Deliver an IPI to this vCPU
113    pub fn deliver_ipi(&mut self, ipi: IpiMessage) {
114        match ipi.delivery_mode {
115            IpiDeliveryMode::Init => {
116                // INIT resets vCPU to wait-for-SIPI state
117                self.state = VcpuState::WaitingForSipi;
118                self.registers = GuestRegisters::default();
119            }
120            IpiDeliveryMode::Sipi => {
121                if self.state == VcpuState::WaitingForSipi {
122                    // SIPI: entry point = vector * 0x1000 in real mode
123                    self.sipi_vector = ipi.vector;
124                    self.registers.rip = (ipi.vector as u64) << 12;
125                    self.state = VcpuState::Running;
126                }
127                // Ignore SIPI if not in wait-for-SIPI state
128            }
129            IpiDeliveryMode::Nmi => {
130                // Wake from HLT for NMI
131                if self.state == VcpuState::Halted {
132                    self.state = VcpuState::Running;
133                }
134                self.pending_ipis.push(ipi);
135            }
136            _ => {
137                if self.state == VcpuState::Halted {
138                    self.state = VcpuState::Running;
139                }
140                self.pending_ipis.push(ipi);
141            }
142        }
143    }
144
145    /// Pop next pending IPI
146    pub fn pop_ipi(&mut self) -> Option<IpiMessage> {
147        if self.pending_ipis.is_empty() {
148            None
149        } else {
150            Some(self.pending_ipis.remove(0))
151        }
152    }
153
154    /// Set host CPU affinity for scheduling
155    pub fn set_affinity(&mut self, host_cpu: u32) {
156        self.host_affinity = Some(host_cpu);
157    }
158
159    pub fn pending_ipi_count(&self) -> usize {
160        self.pending_ipis.len()
161    }
162
163    /// Halt the vCPU (from HLT instruction)
164    pub fn halt(&mut self) {
165        self.state = VcpuState::Halted;
166    }
167
168    /// Pause the vCPU (hypervisor request)
169    pub fn pause(&mut self) {
170        if self.state == VcpuState::Running {
171            self.state = VcpuState::Paused;
172        }
173    }
174
175    /// Resume the vCPU
176    pub fn resume(&mut self) {
177        if self.state == VcpuState::Paused {
178            self.state = VcpuState::Running;
179        }
180    }
181
182    /// Stop the vCPU permanently
183    pub fn stop(&mut self) {
184        self.state = VcpuState::Stopped;
185    }
186}
187
188/// Multi-vCPU VM
189#[cfg(feature = "alloc")]
190pub struct SmpVm {
191    /// VM identifier
192    pub vm_id: u64,
193    /// Virtual CPUs
194    pub vcpus: Vec<VirtualCpu>,
195    /// Maximum vCPUs allowed
196    pub max_vcpus: usize,
197}
198
199#[cfg(feature = "alloc")]
200impl SmpVm {
201    pub fn new(vm_id: u64, vcpu_count: usize) -> Result<Self, VmError> {
202        if vcpu_count == 0 || vcpu_count > MAX_VCPUS {
203            return Err(VmError::InvalidVmState);
204        }
205
206        let mut vcpus = Vec::with_capacity(vcpu_count);
207        for i in 0..vcpu_count {
208            vcpus.push(VirtualCpu::new(i as u8, i == 0));
209        }
210
211        Ok(Self {
212            vm_id,
213            vcpus,
214            max_vcpus: vcpu_count,
215        })
216    }
217
218    /// Send IPI from one vCPU to another
219    pub fn send_ipi(
220        &mut self,
221        source: u8,
222        dest: u8,
223        mode: IpiDeliveryMode,
224        vector: u8,
225    ) -> Result<(), VmError> {
226        if source as usize >= self.vcpus.len() {
227            return Err(VmError::InvalidVmState);
228        }
229
230        let ipi = IpiMessage {
231            source,
232            destination: dest,
233            delivery_mode: mode,
234            vector,
235            level: true,
236            trigger_level: false,
237        };
238
239        if dest == 0xFF {
240            // Broadcast (excluding self)
241            for vcpu in &mut self.vcpus {
242                if vcpu.id != source {
243                    vcpu.deliver_ipi(ipi);
244                }
245            }
246        } else {
247            let target = self.vcpus.iter_mut().find(|v| v.apic_id == dest);
248            if let Some(vcpu) = target {
249                vcpu.deliver_ipi(ipi);
250            } else {
251                return Err(VmError::InvalidVmState);
252            }
253        }
254
255        Ok(())
256    }
257
258    /// Emulate the AP startup sequence: BSP sends INIT then SIPI
259    pub fn startup_ap(&mut self, ap_id: u8, sipi_vector: u8) -> Result<(), VmError> {
260        // Send INIT
261        self.send_ipi(0, ap_id, IpiDeliveryMode::Init, 0)?;
262        // Send SIPI
263        self.send_ipi(0, ap_id, IpiDeliveryMode::Sipi, sipi_vector)?;
264        Ok(())
265    }
266
267    pub fn vcpu_count(&self) -> usize {
268        self.vcpus.len()
269    }
270
271    pub fn running_vcpu_count(&self) -> usize {
272        self.vcpus
273            .iter()
274            .filter(|v| v.state == VcpuState::Running)
275            .count()
276    }
277
278    /// Get a vCPU by ID
279    pub fn vcpu(&self, id: u8) -> Option<&VirtualCpu> {
280        self.vcpus.iter().find(|v| v.id == id)
281    }
282
283    /// Get a mutable reference to a vCPU by ID
284    pub fn vcpu_mut(&mut self, id: u8) -> Option<&mut VirtualCpu> {
285        self.vcpus.iter_mut().find(|v| v.id == id)
286    }
287
288    /// Pause all vCPUs
289    pub fn pause_all(&mut self) {
290        for vcpu in &mut self.vcpus {
291            vcpu.pause();
292        }
293    }
294
295    /// Resume all vCPUs
296    pub fn resume_all(&mut self) {
297        for vcpu in &mut self.vcpus {
298            vcpu.resume();
299        }
300    }
301}