vmm/vstate/
kvm.rs

1// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use kvm_bindings::KVM_API_VERSION;
5use kvm_ioctls::Kvm as KvmFd;
6use serde::{Deserialize, Serialize};
7
8pub use crate::arch::{Kvm, KvmArchError};
9use crate::cpu_config::templates::KvmCapability;
10
11/// Errors associated with the wrappers over KVM ioctls.
12/// Needs `rustfmt::skip` to make multiline comments work
13#[rustfmt::skip]
14#[derive(Debug, thiserror::Error, displaydoc::Display)]
15pub enum KvmError {
16    /// The host kernel reports an invalid KVM API version: {0}
17    ApiVersion(i32),
18    /// Missing KVM capabilities: {0:#x?}
19    Capabilities(u32),
20    /**  Error creating KVM object: {0} Make sure the user launching the firecracker process is \
21    configured on the /dev/kvm file's ACL. */
22    Kvm(kvm_ioctls::Error),
23    /// Architecture specific error: {0}
24    ArchError(#[from] KvmArchError)
25}
26
27impl Kvm {
28    /// Create `Kvm` struct.
29    pub fn new(kvm_cap_modifiers: Vec<KvmCapability>) -> Result<Self, KvmError> {
30        let kvm_fd = KvmFd::new().map_err(KvmError::Kvm)?;
31
32        // Check that KVM has the correct version.
33        // Safe to cast because this is a constant.
34        #[allow(clippy::cast_possible_wrap)]
35        if kvm_fd.get_api_version() != KVM_API_VERSION as i32 {
36            return Err(KvmError::ApiVersion(kvm_fd.get_api_version()));
37        }
38
39        let total_caps = Self::combine_capabilities(&kvm_cap_modifiers);
40        // Check that all desired capabilities are supported.
41        Self::check_capabilities(&kvm_fd, &total_caps).map_err(KvmError::Capabilities)?;
42
43        Ok(Kvm::init_arch(kvm_fd, kvm_cap_modifiers)?)
44    }
45
46    fn combine_capabilities(kvm_cap_modifiers: &[KvmCapability]) -> Vec<u32> {
47        let mut total_caps = Self::DEFAULT_CAPABILITIES.to_vec();
48        for modifier in kvm_cap_modifiers.iter() {
49            match modifier {
50                KvmCapability::Add(cap) => {
51                    if !total_caps.contains(cap) {
52                        total_caps.push(*cap);
53                    }
54                }
55                KvmCapability::Remove(cap) => {
56                    if let Some(pos) = total_caps.iter().position(|c| c == cap) {
57                        total_caps.swap_remove(pos);
58                    }
59                }
60            }
61        }
62        total_caps
63    }
64
65    fn check_capabilities(kvm_fd: &KvmFd, capabilities: &[u32]) -> Result<(), u32> {
66        for cap in capabilities {
67            // If capability is not supported kernel will return 0.
68            if kvm_fd.check_extension_raw(u64::from(*cap)) == 0 {
69                return Err(*cap);
70            }
71        }
72        Ok(())
73    }
74
75    /// Saves and returns the Kvm state.
76    pub fn save_state(&self) -> KvmState {
77        KvmState {
78            kvm_cap_modifiers: self.kvm_cap_modifiers.clone(),
79        }
80    }
81
82    /// Returns the maximal number of memslots allowed in a [`Vm`]
83    pub fn max_nr_memslots(&self) -> u32 {
84        self.fd
85            .get_nr_memslots()
86            .try_into()
87            .expect("Number of vcpus reported by KVM exceeds u32::MAX")
88    }
89}
90
91/// Structure holding an general specific VM state.
92#[derive(Debug, Default, Serialize, Deserialize)]
93pub struct KvmState {
94    /// Additional capabilities that were specified in cpu template.
95    pub kvm_cap_modifiers: Vec<KvmCapability>,
96}
97
98#[cfg(test)]
99pub(crate) mod tests {
100    use super::*;
101
102    #[test]
103    fn test_combine_capabilities() {
104        // Default caps for x86_64 and aarch64 both have KVM_CAP_IOEVENTFD and don't have
105        // KVM_CAP_IOMMU caps.
106        let additional_capabilities = vec![
107            KvmCapability::Add(kvm_bindings::KVM_CAP_IOMMU),
108            KvmCapability::Remove(kvm_bindings::KVM_CAP_IOEVENTFD),
109        ];
110
111        let combined_caps = Kvm::combine_capabilities(&additional_capabilities);
112        assert!(combined_caps.contains(&kvm_bindings::KVM_CAP_IOMMU));
113        assert!(!combined_caps.contains(&kvm_bindings::KVM_CAP_IOEVENTFD));
114    }
115}