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

veridian_kernel/virt/hypervisor/
snapshot.rs

1//! VM Snapshots
2//!
3//! Complete state capture/restore with memory and device state.
4
5#[cfg(feature = "alloc")]
6use alloc::{string::String, vec::Vec};
7
8use super::{
9    lapic::{LvtEntry, VirtualLapic},
10    migration::SerializedVmcs,
11    GuestRegisters, PAGE_SIZE, SNAPSHOT_MAGIC, SNAPSHOT_VERSION,
12};
13
14// ---------------------------------------------------------------------------
15// 6. VM Snapshots
16// ---------------------------------------------------------------------------
17
18/// Snapshot region type identifier
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u32)]
21pub enum SnapshotRegionType {
22    /// VMCS state
23    VmcsState = 1,
24    /// General-purpose registers
25    GeneralRegisters = 2,
26    /// MSR values
27    MsrValues = 3,
28    /// Guest memory page
29    MemoryPage = 4,
30    /// Virtual device state
31    DeviceState = 5,
32    /// LAPIC state
33    LapicState = 6,
34    /// vCPU state
35    VcpuState = 7,
36}
37
38/// Snapshot file header
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[repr(C)]
41pub struct SnapshotHeader {
42    /// Magic number (SNAPSHOT_MAGIC)
43    pub magic: u32,
44    /// Format version
45    pub version: u32,
46    /// VM ID
47    pub vm_id: u64,
48    /// Number of vCPUs
49    pub vcpu_count: u32,
50    /// Total memory pages
51    pub memory_pages: u64,
52    /// Number of regions in the snapshot
53    pub region_count: u32,
54    /// Total snapshot size in bytes
55    pub total_size: u64,
56    /// Timestamp (kernel ticks at snapshot time)
57    pub timestamp: u64,
58    /// Checksum (simple XOR-based)
59    pub checksum: u64,
60}
61
62impl Default for SnapshotHeader {
63    fn default() -> Self {
64        Self {
65            magic: SNAPSHOT_MAGIC,
66            version: SNAPSHOT_VERSION,
67            vm_id: 0,
68            vcpu_count: 0,
69            memory_pages: 0,
70            region_count: 0,
71            total_size: 0,
72            timestamp: 0,
73            checksum: 0,
74        }
75    }
76}
77
78impl SnapshotHeader {
79    pub fn is_valid(&self) -> bool {
80        self.magic == SNAPSHOT_MAGIC && self.version == SNAPSHOT_VERSION
81    }
82
83    /// Compute simple XOR checksum over header fields (excluding checksum
84    /// itself)
85    pub fn compute_checksum(&self) -> u64 {
86        let mut ck: u64 = 0;
87        ck ^= self.magic as u64;
88        ck ^= self.version as u64;
89        ck ^= self.vm_id;
90        ck ^= self.vcpu_count as u64;
91        ck ^= self.memory_pages;
92        ck ^= self.region_count as u64;
93        ck ^= self.total_size;
94        ck ^= self.timestamp;
95        ck
96    }
97}
98
99/// Snapshot region descriptor
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101#[repr(C)]
102pub struct SnapshotRegionDescriptor {
103    /// Region type
104    pub region_type: u32,
105    /// vCPU index (for per-CPU state)
106    pub vcpu_index: u32,
107    /// Offset from start of snapshot data
108    pub offset: u64,
109    /// Size of this region in bytes
110    pub size: u64,
111}
112
113/// MSR entry for snapshot
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[repr(C)]
116pub struct MsrEntry {
117    pub index: u32,
118    pub _reserved: u32,
119    pub value: u64,
120}
121
122/// Common MSR indices for snapshot
123#[allow(unused)]
124pub struct CommonMsrs;
125
126#[allow(unused)]
127impl CommonMsrs {
128    pub const IA32_EFER: u32 = 0xC000_0080;
129    pub const IA32_STAR: u32 = 0xC000_0081;
130    pub const IA32_LSTAR: u32 = 0xC000_0082;
131    pub const IA32_CSTAR: u32 = 0xC000_0083;
132    pub const IA32_FMASK: u32 = 0xC000_0084;
133    pub const IA32_FS_BASE: u32 = 0xC000_0100;
134    pub const IA32_GS_BASE: u32 = 0xC000_0101;
135    pub const IA32_KERNEL_GS_BASE: u32 = 0xC000_0102;
136    pub const IA32_TSC_AUX: u32 = 0xC000_0103;
137    pub const IA32_SYSENTER_CS: u32 = 0x174;
138    pub const IA32_SYSENTER_ESP: u32 = 0x175;
139    pub const IA32_SYSENTER_EIP: u32 = 0x176;
140    pub const IA32_PAT: u32 = 0x277;
141    pub const IA32_DEBUGCTL: u32 = 0x1D9;
142    pub const IA32_APIC_BASE: u32 = 0x1B;
143}
144
145/// Complete VM snapshot (in-memory representation)
146#[cfg(feature = "alloc")]
147pub struct VmSnapshot {
148    /// Snapshot header
149    pub header: SnapshotHeader,
150    /// Region descriptors
151    pub regions: Vec<SnapshotRegionDescriptor>,
152    /// Serialized VMCS per vCPU
153    pub vmcs_states: Vec<SerializedVmcs>,
154    /// General register state per vCPU
155    pub register_states: Vec<GuestRegisters>,
156    /// MSR values per vCPU
157    pub msr_states: Vec<Vec<MsrEntry>>,
158    /// LAPIC state per vCPU (serialized as raw register values)
159    pub lapic_states: Vec<LapicSnapshot>,
160    /// Dirty page indices (pages included in snapshot)
161    pub memory_page_indices: Vec<u64>,
162    /// Device state blobs
163    pub device_states: Vec<DeviceStateBlob>,
164}
165
166/// Serialized LAPIC state for snapshot
167#[derive(Debug, Clone)]
168pub struct LapicSnapshot {
169    pub id: u32,
170    pub tpr: u32,
171    pub svr: u32,
172    pub isr: [u32; 8],
173    pub irr: [u32; 8],
174    pub tmr: [u32; 8],
175    pub lvt_timer_raw: u32,
176    pub lvt_thermal_raw: u32,
177    pub lvt_perfmon_raw: u32,
178    pub lvt_lint0_raw: u32,
179    pub lvt_lint1_raw: u32,
180    pub lvt_error_raw: u32,
181    pub timer_initial_count: u32,
182    pub timer_current_count: u32,
183    pub timer_divide_config: u32,
184    pub tsc_deadline: u64,
185    pub icr_low: u32,
186    pub icr_high: u32,
187    pub ldr: u32,
188    pub dfr: u32,
189    pub enabled: bool,
190}
191
192impl LapicSnapshot {
193    pub fn from_lapic(lapic: &VirtualLapic) -> Self {
194        Self {
195            id: lapic.id,
196            tpr: lapic.tpr,
197            svr: lapic.svr,
198            isr: lapic.isr,
199            irr: lapic.irr,
200            tmr: lapic.tmr,
201            lvt_timer_raw: lapic.lvt_timer.raw,
202            lvt_thermal_raw: lapic.lvt_thermal.raw,
203            lvt_perfmon_raw: lapic.lvt_perfmon.raw,
204            lvt_lint0_raw: lapic.lvt_lint0.raw,
205            lvt_lint1_raw: lapic.lvt_lint1.raw,
206            lvt_error_raw: lapic.lvt_error.raw,
207            timer_initial_count: lapic.timer_initial_count,
208            timer_current_count: lapic.timer_current_count,
209            timer_divide_config: lapic.timer_divide_config,
210            tsc_deadline: lapic.tsc_deadline,
211            icr_low: lapic.icr_low,
212            icr_high: lapic.icr_high,
213            ldr: lapic.ldr,
214            dfr: lapic.dfr,
215            enabled: lapic.enabled,
216        }
217    }
218
219    /// Restore LAPIC from snapshot
220    pub fn restore_to_lapic(&self, lapic: &mut VirtualLapic) {
221        lapic.id = self.id;
222        lapic.tpr = self.tpr;
223        lapic.svr = self.svr;
224        lapic.isr = self.isr;
225        lapic.irr = self.irr;
226        lapic.tmr = self.tmr;
227        lapic.lvt_timer = LvtEntry {
228            raw: self.lvt_timer_raw,
229        };
230        lapic.lvt_thermal = LvtEntry {
231            raw: self.lvt_thermal_raw,
232        };
233        lapic.lvt_perfmon = LvtEntry {
234            raw: self.lvt_perfmon_raw,
235        };
236        lapic.lvt_lint0 = LvtEntry {
237            raw: self.lvt_lint0_raw,
238        };
239        lapic.lvt_lint1 = LvtEntry {
240            raw: self.lvt_lint1_raw,
241        };
242        lapic.lvt_error = LvtEntry {
243            raw: self.lvt_error_raw,
244        };
245        lapic.timer_initial_count = self.timer_initial_count;
246        lapic.timer_current_count = self.timer_current_count;
247        lapic.timer_divide_config = self.timer_divide_config;
248        lapic.tsc_deadline = self.tsc_deadline;
249        lapic.icr_low = self.icr_low;
250        lapic.icr_high = self.icr_high;
251        lapic.ldr = self.ldr;
252        lapic.dfr = self.dfr;
253        lapic.enabled = self.enabled;
254    }
255}
256
257/// Serialized device state blob
258#[cfg(feature = "alloc")]
259#[derive(Debug, Clone)]
260pub struct DeviceStateBlob {
261    /// Device name
262    pub name: String,
263    /// Serialized state bytes
264    pub data: Vec<u8>,
265}
266
267#[cfg(feature = "alloc")]
268impl VmSnapshot {
269    /// Create a new empty snapshot
270    pub fn new(vm_id: u64, vcpu_count: u32, memory_pages: u64, timestamp: u64) -> Self {
271        let header = SnapshotHeader {
272            vm_id,
273            vcpu_count,
274            memory_pages,
275            timestamp,
276            ..Default::default()
277        };
278
279        Self {
280            header,
281            regions: Vec::new(),
282            vmcs_states: Vec::new(),
283            register_states: Vec::new(),
284            msr_states: Vec::new(),
285            lapic_states: Vec::new(),
286            memory_page_indices: Vec::new(),
287            device_states: Vec::new(),
288        }
289    }
290
291    /// Add register state for a vCPU
292    pub fn add_register_state(&mut self, vcpu_idx: u32, regs: GuestRegisters) {
293        self.register_states.push(regs);
294        self.regions.push(SnapshotRegionDescriptor {
295            region_type: SnapshotRegionType::GeneralRegisters as u32,
296            vcpu_index: vcpu_idx,
297            offset: 0, // Computed during serialization
298            size: core::mem::size_of::<GuestRegisters>() as u64,
299        });
300        self.header.region_count += 1;
301    }
302
303    /// Add VMCS state for a vCPU
304    pub fn add_vmcs_state(&mut self, vcpu_idx: u32, vmcs: SerializedVmcs) {
305        let field_size = vmcs.field_count() as u64 * 12; // 4 bytes encoding + 8 bytes value
306        self.vmcs_states.push(vmcs);
307        self.regions.push(SnapshotRegionDescriptor {
308            region_type: SnapshotRegionType::VmcsState as u32,
309            vcpu_index: vcpu_idx,
310            offset: 0,
311            size: field_size,
312        });
313        self.header.region_count += 1;
314    }
315
316    /// Add MSR state for a vCPU
317    pub fn add_msr_state(&mut self, vcpu_idx: u32, msrs: Vec<MsrEntry>) {
318        let size = msrs.len() as u64 * 16; // 16 bytes per MsrEntry
319        self.msr_states.push(msrs);
320        self.regions.push(SnapshotRegionDescriptor {
321            region_type: SnapshotRegionType::MsrValues as u32,
322            vcpu_index: vcpu_idx,
323            offset: 0,
324            size,
325        });
326        self.header.region_count += 1;
327    }
328
329    /// Add LAPIC state for a vCPU
330    pub fn add_lapic_state(&mut self, vcpu_idx: u32, lapic: &VirtualLapic) {
331        self.lapic_states.push(LapicSnapshot::from_lapic(lapic));
332        self.regions.push(SnapshotRegionDescriptor {
333            region_type: SnapshotRegionType::LapicState as u32,
334            vcpu_index: vcpu_idx,
335            offset: 0,
336            size: core::mem::size_of::<LapicSnapshot>() as u64,
337        });
338        self.header.region_count += 1;
339    }
340
341    /// Add a memory page to the snapshot
342    pub fn add_memory_page(&mut self, page_index: u64) {
343        self.memory_page_indices.push(page_index);
344        self.regions.push(SnapshotRegionDescriptor {
345            region_type: SnapshotRegionType::MemoryPage as u32,
346            vcpu_index: 0,
347            offset: 0,
348            size: PAGE_SIZE,
349        });
350        self.header.region_count += 1;
351    }
352
353    /// Add device state blob
354    pub fn add_device_state(&mut self, name: String, data: Vec<u8>) {
355        let size = data.len() as u64;
356        self.device_states.push(DeviceStateBlob { name, data });
357        self.regions.push(SnapshotRegionDescriptor {
358            region_type: SnapshotRegionType::DeviceState as u32,
359            vcpu_index: 0,
360            offset: 0,
361            size,
362        });
363        self.header.region_count += 1;
364    }
365
366    /// Finalize snapshot: compute total size and checksum
367    pub fn finalize(&mut self) {
368        let mut total: u64 = core::mem::size_of::<SnapshotHeader>() as u64;
369
370        // Region descriptors
371        total = total.saturating_add(
372            (self.regions.len() as u64)
373                .checked_mul(core::mem::size_of::<SnapshotRegionDescriptor>() as u64)
374                .unwrap_or(0),
375        );
376
377        // Region data
378        for region in &self.regions {
379            total = total.saturating_add(region.size);
380        }
381
382        self.header.total_size = total;
383        self.header.checksum = self.header.compute_checksum();
384    }
385
386    /// Validate snapshot header
387    pub fn validate(&self) -> bool {
388        self.header.is_valid() && self.header.checksum == self.header.compute_checksum()
389    }
390
391    pub fn region_count(&self) -> usize {
392        self.regions.len()
393    }
394
395    pub fn memory_page_count(&self) -> usize {
396        self.memory_page_indices.len()
397    }
398
399    pub fn vcpu_state_count(&self) -> usize {
400        self.register_states.len()
401    }
402
403    pub fn device_state_count(&self) -> usize {
404        self.device_states.len()
405    }
406}
407
408/// Restore a VirtualLapic from a snapshot
409#[cfg(feature = "alloc")]
410pub fn restore_lapic_from_snapshot(snapshot: &VmSnapshot, vcpu_idx: usize) -> Option<VirtualLapic> {
411    let lapic_snap = snapshot.lapic_states.get(vcpu_idx)?;
412    let mut lapic = VirtualLapic::new(lapic_snap.id);
413    lapic_snap.restore_to_lapic(&mut lapic);
414    Some(lapic)
415}