diff --git a/src/snapshot-editor/src/edit_vmstate.rs b/src/snapshot-editor/src/edit_vmstate.rs index a96f84db6a0..ba5b8370096 100644 --- a/src/snapshot-editor/src/edit_vmstate.rs +++ b/src/snapshot-editor/src/edit_vmstate.rs @@ -99,7 +99,7 @@ mod tests { const KVM_REG_SIZE_U32: u64 = 0x20000000000000; use vmm::arch::aarch64::regs::Aarch64RegisterRef; - use vmm::vstate::vcpu::aarch64::VcpuState; + use vmm::arch::aarch64::vcpu::VcpuState; let vcpu_state = VcpuState { regs: { @@ -158,7 +158,7 @@ mod tests { const KVM_REG_SIZE_U32: u64 = 0x20000000000000; use vmm::arch::aarch64::regs::Aarch64RegisterRef; - use vmm::vstate::vcpu::aarch64::VcpuState; + use vmm::arch::aarch64::vcpu::VcpuState; let vcpu_state = VcpuState { regs: { diff --git a/src/vmm/src/arch/aarch64/kvm.rs b/src/vmm/src/arch/aarch64/kvm.rs new file mode 100644 index 00000000000..12720f5066f --- /dev/null +++ b/src/vmm/src/arch/aarch64/kvm.rs @@ -0,0 +1,64 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::convert::Infallible; + +use kvm_ioctls::Kvm as KvmFd; + +use crate::cpu_config::templates::KvmCapability; + +/// ['Kvm'] initialization can't fail for Aarch64 +pub type KvmArchError = Infallible; + +/// Optional capabilities. +#[derive(Debug, Default)] +pub struct OptionalCapabilities { + /// KVM_CAP_COUNTER_OFFSET + pub counter_offset: bool, +} + +/// Struct with kvm fd and kvm associated parameters. +#[derive(Debug)] +pub struct Kvm { + /// KVM fd. + pub fd: KvmFd, + /// Maximum number of memory slots allowed by KVM. + pub max_memslots: usize, + /// Additional capabilities that were specified in cpu template. + pub kvm_cap_modifiers: Vec, +} + +impl Kvm { + pub(crate) const DEFAULT_CAPABILITIES: [u32; 7] = [ + kvm_bindings::KVM_CAP_IOEVENTFD, + kvm_bindings::KVM_CAP_IRQFD, + kvm_bindings::KVM_CAP_USER_MEMORY, + kvm_bindings::KVM_CAP_ARM_PSCI_0_2, + kvm_bindings::KVM_CAP_DEVICE_CTRL, + kvm_bindings::KVM_CAP_MP_STATE, + kvm_bindings::KVM_CAP_ONE_REG, + ]; + + /// Initialize [`Kvm`] type for Aarch64 architecture + pub fn init_arch( + fd: KvmFd, + max_memslots: usize, + kvm_cap_modifiers: Vec, + ) -> Result { + Ok(Self { + fd, + max_memslots, + kvm_cap_modifiers, + }) + } + + /// Returns struct with optional capabilities statuses. + pub fn optional_capabilities(&self) -> OptionalCapabilities { + OptionalCapabilities { + counter_offset: self + .fd + .check_extension_raw(kvm_bindings::KVM_CAP_COUNTER_OFFSET.into()) + != 0, + } + } +} diff --git a/src/vmm/src/arch/aarch64/mod.rs b/src/vmm/src/arch/aarch64/mod.rs index a409f330c56..7ab1685cf69 100644 --- a/src/vmm/src/arch/aarch64/mod.rs +++ b/src/vmm/src/arch/aarch64/mod.rs @@ -5,12 +5,16 @@ pub(crate) mod cache_info; mod fdt; /// Module for the global interrupt controller configuration. pub mod gic; +/// Architecture specific KVM-related code +pub mod kvm; /// Layout for this aarch64 system. pub mod layout; /// Logic for configuring aarch64 registers. pub mod regs; -/// Helper methods for VcpuFd. +/// Architecture specific vCPU code pub mod vcpu; +/// Architecture specific VM state code +pub mod vm; use std::cmp::min; use std::collections::HashMap; diff --git a/src/vmm/src/arch/aarch64/vcpu.rs b/src/vmm/src/arch/aarch64/vcpu.rs index d46b9b56720..a126ab1a3a5 100644 --- a/src/vmm/src/arch/aarch64/vcpu.rs +++ b/src/vmm/src/arch/aarch64/vcpu.rs @@ -5,20 +5,30 @@ // Use of this source code is governed by a BSD-style license that can be // found in the THIRD-PARTY file. +use std::fmt::{Debug, Write}; use std::mem::offset_of; use std::path::PathBuf; use kvm_bindings::*; -use kvm_ioctls::VcpuFd; +use kvm_ioctls::{VcpuExit, VcpuFd, VmFd}; +use serde::{Deserialize, Serialize}; use super::get_fdt_addr; use super::regs::*; -use crate::vstate::kvm::OptionalCapabilities; -use crate::vstate::memory::GuestMemoryMmap; +use crate::arch::EntryPoint; +use crate::arch::aarch64::kvm::OptionalCapabilities; +use crate::arch::aarch64::regs::{Aarch64RegisterVec, KVM_REG_ARM64_SVE_VLS}; +use crate::cpu_config::aarch64::custom_cpu_template::VcpuFeatures; +use crate::cpu_config::templates::CpuConfiguration; +use crate::logger::{IncMetric, METRICS, error}; +use crate::vcpu::{VcpuConfig, VcpuError}; +use crate::vstate::memory::{Address, GuestMemoryMmap}; +use crate::vstate::vcpu::VcpuEmulation; +use crate::vstate::vm::Vm; /// Errors thrown while setting aarch64 registers. #[derive(Debug, PartialEq, Eq, thiserror::Error, displaydoc::Display)] -pub enum VcpuError { +pub enum VcpuArchError { /// Failed to get register {0}: {1} GetOneReg(u64, kvm_ioctls::Error), /// Failed to set register {0}: {1} @@ -41,11 +51,11 @@ pub enum VcpuError { /// # Arguments /// /// * `regs` - reference [`Aarch64RegisterVec`] structure with all registers of a VCPU. -pub fn get_manufacturer_id_from_state(regs: &Aarch64RegisterVec) -> Result { +pub fn get_manufacturer_id_from_state(regs: &Aarch64RegisterVec) -> Result { let midr_el1 = regs.iter().find(|reg| reg.id == MIDR_EL1); match midr_el1 { Some(register) => Ok(((register.value::() >> 24) & 0xFF) as u32), - None => Err(VcpuError::GetMidrEl1( + None => Err(VcpuArchError::GetMidrEl1( "Failed to find MIDR_EL1 in vCPU state!".to_string(), )), } @@ -53,16 +63,17 @@ pub fn get_manufacturer_id_from_state(regs: &Aarch64RegisterVec) -> Result Result { +pub fn get_manufacturer_id_from_host() -> Result { let midr_el1_path = &PathBuf::from("/sys/devices/system/cpu/cpu0/regs/identification/midr_el1".to_string()); let midr_el1 = std::fs::read_to_string(midr_el1_path).map_err(|err| { - VcpuError::GetMidrEl1(format!("Failed to get MIDR_EL1 from host path: {err}")) + VcpuArchError::GetMidrEl1(format!("Failed to get MIDR_EL1 from host path: {err}")) })?; let midr_el1_trimmed = midr_el1.trim_end().trim_start_matches("0x"); - let manufacturer_id = u32::from_str_radix(midr_el1_trimmed, 16) - .map_err(|err| VcpuError::GetMidrEl1(format!("Invalid MIDR_EL1 found on host: {err}",)))?; + let manufacturer_id = u32::from_str_radix(midr_el1_trimmed, 16).map_err(|err| { + VcpuArchError::GetMidrEl1(format!("Invalid MIDR_EL1 found on host: {err}",)) + })?; Ok(manufacturer_id >> 24) } @@ -80,7 +91,7 @@ pub fn setup_boot_regs( boot_ip: u64, mem: &GuestMemoryMmap, optional_capabilities: &OptionalCapabilities, -) -> Result<(), VcpuError> { +) -> Result<(), VcpuArchError> { let kreg_off = offset_of!(kvm_regs, regs); // Get the register index of the PSTATE (Processor State) register. @@ -88,7 +99,7 @@ pub fn setup_boot_regs( let id = arm64_core_reg_id!(KVM_REG_SIZE_U64, pstate); vcpufd .set_one_reg(id, &PSTATE_FAULT_BITS_64.to_le_bytes()) - .map_err(|err| VcpuError::SetOneReg(id, err))?; + .map_err(|err| VcpuArchError::SetOneReg(id, err))?; // Other vCPUs are powered off initially awaiting PSCI wakeup. if cpu_id == 0 { @@ -97,7 +108,7 @@ pub fn setup_boot_regs( let id = arm64_core_reg_id!(KVM_REG_SIZE_U64, pc); vcpufd .set_one_reg(id, &boot_ip.to_le_bytes()) - .map_err(|err| VcpuError::SetOneReg(id, err))?; + .map_err(|err| VcpuArchError::SetOneReg(id, err))?; // Last mandatory thing to set -> the address pointing to the FDT (also called DTB). // "The device tree blob (dtb) must be placed on an 8-byte boundary and must @@ -107,7 +118,7 @@ pub fn setup_boot_regs( let id = arm64_core_reg_id!(KVM_REG_SIZE_U64, regs0); vcpufd .set_one_reg(id, &get_fdt_addr(mem).to_le_bytes()) - .map_err(|err| VcpuError::SetOneReg(id, err))?; + .map_err(|err| VcpuArchError::SetOneReg(id, err))?; // Reset the physical counter for the guest. This way we avoid guest reading // host physical counter. @@ -123,18 +134,18 @@ pub fn setup_boot_regs( if optional_capabilities.counter_offset { vcpufd .set_one_reg(KVM_REG_ARM_PTIMER_CNT, &[0; 8]) - .map_err(|err| VcpuError::SetOneReg(id, err))?; + .map_err(|err| VcpuArchError::SetOneReg(id, err))?; } } Ok(()) } /// Read the MPIDR - Multiprocessor Affinity Register. -pub fn get_mpidr(vcpufd: &VcpuFd) -> Result { +pub fn get_mpidr(vcpufd: &VcpuFd) -> Result { // MPIDR register is 64 bit wide on aarch64 let mut mpidr = [0_u8; 8]; match vcpufd.get_one_reg(MPIDR_EL1, &mut mpidr) { - Err(err) => Err(VcpuError::GetOneReg(MPIDR_EL1, err)), + Err(err) => Err(VcpuArchError::GetOneReg(MPIDR_EL1, err)), Ok(_) => Ok(u64::from_le_bytes(mpidr)), } } @@ -144,7 +155,10 @@ pub fn get_mpidr(vcpufd: &VcpuFd) -> Result { /// # Arguments /// /// * `regs` - Input/Output vector of registers. -pub fn get_all_registers(vcpufd: &VcpuFd, state: &mut Aarch64RegisterVec) -> Result<(), VcpuError> { +pub fn get_all_registers( + vcpufd: &VcpuFd, + state: &mut Aarch64RegisterVec, +) -> Result<(), VcpuArchError> { get_registers(vcpufd, &get_all_registers_ids(vcpufd)?, state) } @@ -158,12 +172,12 @@ pub fn get_registers( vcpufd: &VcpuFd, ids: &[u64], regs: &mut Aarch64RegisterVec, -) -> Result<(), VcpuError> { +) -> Result<(), VcpuArchError> { let mut big_reg = [0_u8; 256]; for id in ids.iter() { let reg_size = vcpufd .get_one_reg(*id, &mut big_reg) - .map_err(|e| VcpuError::GetOneReg(*id, e))?; + .map_err(|e| VcpuArchError::GetOneReg(*id, e))?; let reg_ref = Aarch64RegisterRef::new(*id, &big_reg[0..reg_size]); regs.push(reg_ref); } @@ -171,10 +185,10 @@ pub fn get_registers( } /// Returns all registers ids, including core and system -pub fn get_all_registers_ids(vcpufd: &VcpuFd) -> Result, VcpuError> { +pub fn get_all_registers_ids(vcpufd: &VcpuFd) -> Result, VcpuArchError> { // Call KVM_GET_REG_LIST to get all registers available to the guest. For ArmV8 there are // less than 500 registers expected, resize to the reported size when necessary. - let mut reg_list = RegList::new(500).map_err(VcpuError::Fam)?; + let mut reg_list = RegList::new(500).map_err(VcpuArchError::Fam)?; match vcpufd.get_reg_list(&mut reg_list) { Ok(_) => Ok(reg_list.as_slice().to_vec()), @@ -187,14 +201,14 @@ pub fn get_all_registers_ids(vcpufd: &VcpuFd) -> Result, VcpuError> { .try_into() // Safe to unwrap as Firecracker only targets 64-bit machines. .unwrap(); - reg_list = RegList::new(size).map_err(VcpuError::Fam)?; + reg_list = RegList::new(size).map_err(VcpuArchError::Fam)?; vcpufd .get_reg_list(&mut reg_list) - .map_err(VcpuError::GetRegList)?; + .map_err(VcpuArchError::GetRegList)?; Ok(reg_list.as_slice().to_vec()) } - _ => Err(VcpuError::GetRegList(e)), + _ => Err(VcpuArchError::GetRegList(e)), }, } } @@ -204,10 +218,10 @@ pub fn get_all_registers_ids(vcpufd: &VcpuFd) -> Result, VcpuError> { /// # Arguments /// /// * `reg` - Register to be set. -pub fn set_register(vcpufd: &VcpuFd, reg: Aarch64RegisterRef) -> Result<(), VcpuError> { +pub fn set_register(vcpufd: &VcpuFd, reg: Aarch64RegisterRef) -> Result<(), VcpuArchError> { vcpufd .set_one_reg(reg.id, reg.as_slice()) - .map_err(|e| VcpuError::SetOneReg(reg.id, e))?; + .map_err(|e| VcpuArchError::SetOneReg(reg.id, e))?; Ok(()) } @@ -216,8 +230,8 @@ pub fn set_register(vcpufd: &VcpuFd, reg: Aarch64RegisterRef) -> Result<(), Vcpu /// # Arguments /// /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. -pub fn get_mpstate(vcpufd: &VcpuFd) -> Result { - vcpufd.get_mp_state().map_err(VcpuError::GetMp) +pub fn get_mpstate(vcpufd: &VcpuFd) -> Result { + vcpufd.get_mp_state().map_err(VcpuArchError::GetMp) } /// Set the state of the system registers. @@ -226,18 +240,480 @@ pub fn get_mpstate(vcpufd: &VcpuFd) -> Result { /// /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. /// * `state` - Structure for returning the state of the system registers. -pub fn set_mpstate(vcpufd: &VcpuFd, state: kvm_mp_state) -> Result<(), VcpuError> { - vcpufd.set_mp_state(state).map_err(VcpuError::SetMp) +pub fn set_mpstate(vcpufd: &VcpuFd, state: kvm_mp_state) -> Result<(), VcpuArchError> { + vcpufd.set_mp_state(state).map_err(VcpuArchError::SetMp) +} + +/// Errors associated with the wrappers over KVM ioctls. +#[derive(Debug, PartialEq, Eq, thiserror::Error, displaydoc::Display)] +pub enum KvmVcpuError { + /// Error configuring the vcpu registers: {0} + ConfigureRegisters(VcpuArchError), + /// Error creating vcpu: {0} + CreateVcpu(kvm_ioctls::Error), + /// Failed to dump CPU configuration: {0} + DumpCpuConfig(VcpuArchError), + /// Error getting the vcpu preferred target: {0} + GetPreferredTarget(kvm_ioctls::Error), + /// Error initializing the vcpu: {0} + Init(kvm_ioctls::Error), + /// Error applying template: {0} + ApplyCpuTemplate(VcpuArchError), + /// Failed to restore the state of the vcpu: {0} + RestoreState(VcpuArchError), + /// Failed to save the state of the vcpu: {0} + SaveState(VcpuArchError), +} + +/// Error type for [`KvmVcpu::configure`]. +pub type KvmVcpuConfigureError = KvmVcpuError; + +/// A wrapper around creating and using a kvm aarch64 vcpu. +#[derive(Debug)] +pub struct KvmVcpu { + /// Index of vcpu. + pub index: u8, + /// KVM vcpu fd. + pub fd: VcpuFd, + /// Vcpu peripherals, such as buses + pub peripherals: Peripherals, + mpidr: u64, + kvi: kvm_vcpu_init, +} + +/// Vcpu peripherals +#[derive(Default, Debug)] +pub struct Peripherals { + /// mmio bus. + pub mmio_bus: Option, +} + +impl KvmVcpu { + /// Constructs a new kvm vcpu with arch specific functionality. + /// + /// # Arguments + /// + /// * `index` - Represents the 0-based CPU index between [0, max vcpus). + /// * `vm` - The vm to which this vcpu will get attached. + pub fn new(index: u8, vm: &Vm) -> Result { + let kvm_vcpu = vm + .fd() + .create_vcpu(index.into()) + .map_err(KvmVcpuError::CreateVcpu)?; + + let mut kvi = Self::default_kvi(vm.fd())?; + // Secondary vcpus must be powered off for boot process. + if 0 < index { + kvi.features[0] |= 1 << KVM_ARM_VCPU_POWER_OFF; + } + + Ok(KvmVcpu { + index, + fd: kvm_vcpu, + peripherals: Default::default(), + mpidr: 0, + kvi, + }) + } + + /// Gets the MPIDR register value. + pub fn get_mpidr(&self) -> u64 { + self.mpidr + } + + /// Configures an aarch64 specific vcpu for booting Linux. + /// + /// # Arguments + /// + /// * `guest_mem` - The guest memory used by this microvm. + /// * `kernel_entry_point` - Specifies the boot protocol and offset from `guest_mem` at which + /// the kernel starts. + /// * `vcpu_config` - The vCPU configuration. + pub fn configure( + &mut self, + guest_mem: &GuestMemoryMmap, + kernel_entry_point: EntryPoint, + vcpu_config: &VcpuConfig, + optional_capabilities: &OptionalCapabilities, + ) -> Result<(), KvmVcpuError> { + for reg in vcpu_config.cpu_config.regs.iter() { + self.fd.set_one_reg(reg.id, reg.as_slice()).map_err(|err| { + KvmVcpuError::ApplyCpuTemplate(VcpuArchError::SetOneReg(reg.id, err)) + })?; + } + + setup_boot_regs( + &self.fd, + self.index, + kernel_entry_point.entry_addr.raw_value(), + guest_mem, + optional_capabilities, + ) + .map_err(KvmVcpuError::ConfigureRegisters)?; + + self.mpidr = get_mpidr(&self.fd).map_err(KvmVcpuError::ConfigureRegisters)?; + + Ok(()) + } + + /// Initializes an aarch64 specific vcpu for booting Linux. + /// + /// # Arguments + /// + /// * `vm_fd` - The kvm `VmFd` for this microvm. + pub fn init(&mut self, vcpu_features: &[VcpuFeatures]) -> Result<(), KvmVcpuError> { + for feature in vcpu_features.iter() { + let index = feature.index as usize; + self.kvi.features[index] = feature.bitmap.apply(self.kvi.features[index]); + } + + self.init_vcpu()?; + self.finalize_vcpu()?; + + Ok(()) + } + + /// Creates default kvi struct based on vcpu index. + pub fn default_kvi(vm_fd: &VmFd) -> Result { + let mut kvi = kvm_vcpu_init::default(); + // This reads back the kernel's preferred target type. + vm_fd + .get_preferred_target(&mut kvi) + .map_err(KvmVcpuError::GetPreferredTarget)?; + // We already checked that the capability is supported. + kvi.features[0] |= 1 << KVM_ARM_VCPU_PSCI_0_2; + + Ok(kvi) + } + + /// Save the KVM internal state. + pub fn save_state(&self) -> Result { + let mut state = VcpuState { + mp_state: get_mpstate(&self.fd).map_err(KvmVcpuError::SaveState)?, + ..Default::default() + }; + get_all_registers(&self.fd, &mut state.regs).map_err(KvmVcpuError::SaveState)?; + state.mpidr = get_mpidr(&self.fd).map_err(KvmVcpuError::SaveState)?; + + state.kvi = self.kvi; + // We don't save power off state in a snapshot, because + // it was only needed during uVM boot process. + // When uVM is restored, the kernel has already passed + // the boot state and turned secondary vcpus on. + state.kvi.features[0] &= !(1 << KVM_ARM_VCPU_POWER_OFF); + + Ok(state) + } + + /// Use provided state to populate KVM internal state. + pub fn restore_state(&mut self, state: &VcpuState) -> Result<(), KvmVcpuError> { + self.kvi = state.kvi; + + self.init_vcpu()?; + + // If KVM_REG_ARM64_SVE_VLS is present it needs to + // be set before vcpu is finalized. + if let Some(sve_vls_reg) = state + .regs + .iter() + .find(|reg| reg.id == KVM_REG_ARM64_SVE_VLS) + { + set_register(&self.fd, sve_vls_reg).map_err(KvmVcpuError::RestoreState)?; + } + + self.finalize_vcpu()?; + + // KVM_REG_ARM64_SVE_VLS needs to be skipped after vcpu is finalized. + // If it is present it is handled in the code above. + for reg in state + .regs + .iter() + .filter(|reg| reg.id != KVM_REG_ARM64_SVE_VLS) + { + set_register(&self.fd, reg).map_err(KvmVcpuError::RestoreState)?; + } + set_mpstate(&self.fd, state.mp_state).map_err(KvmVcpuError::RestoreState)?; + Ok(()) + } + + /// Dumps CPU configuration. + pub fn dump_cpu_config(&self) -> Result { + let reg_list = get_all_registers_ids(&self.fd).map_err(KvmVcpuError::DumpCpuConfig)?; + + let mut regs = Aarch64RegisterVec::default(); + get_registers(&self.fd, ®_list, &mut regs).map_err(KvmVcpuError::DumpCpuConfig)?; + + Ok(CpuConfiguration { regs }) + } + /// Initializes internal vcpufd. + fn init_vcpu(&self) -> Result<(), KvmVcpuError> { + self.fd.vcpu_init(&self.kvi).map_err(KvmVcpuError::Init)?; + Ok(()) + } + + /// Checks for SVE feature and calls `vcpu_finalize` if + /// it is enabled. + fn finalize_vcpu(&self) -> Result<(), KvmVcpuError> { + if (self.kvi.features[0] & (1 << KVM_ARM_VCPU_SVE)) != 0 { + // KVM_ARM_VCPU_SVE has value 4 so casting to i32 is safe. + #[allow(clippy::cast_possible_wrap)] + let feature = KVM_ARM_VCPU_SVE as i32; + self.fd.vcpu_finalize(&feature).unwrap(); + } + Ok(()) + } +} + +impl Peripherals { + /// Runs the vCPU in KVM context and handles the kvm exit reason. + /// + /// Returns error or enum specifying whether emulation was handled or interrupted. + pub fn run_arch_emulation(&self, exit: VcpuExit) -> Result { + METRICS.vcpu.failures.inc(); + // TODO: Are we sure we want to finish running a vcpu upon + // receiving a vm exit that is not necessarily an error? + error!("Unexpected exit reason on vcpu run: {:?}", exit); + Err(VcpuError::UnhandledKvmExit(format!("{:?}", exit))) + } +} + +/// Structure holding VCPU kvm state. +#[derive(Default, Clone, Serialize, Deserialize)] +pub struct VcpuState { + /// Multiprocessing state. + pub mp_state: kvm_mp_state, + /// Vcpu registers. + pub regs: Aarch64RegisterVec, + /// We will be using the mpidr for passing it to the VmState. + /// The VmState will give this away for saving restoring the icc and redistributor + /// registers. + pub mpidr: u64, + /// kvi states for vcpu initialization. + pub kvi: kvm_vcpu_init, +} + +impl Debug for VcpuState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "kvm_mp_state: {:#x}", self.mp_state.mp_state)?; + writeln!(f, "mpidr: {:#x}", self.mpidr)?; + for reg in self.regs.iter() { + writeln!( + f, + "{:#x} 0x{}", + reg.id, + reg.as_slice() + .iter() + .rev() + .fold(String::new(), |mut output, b| { + let _ = write!(output, "{b:x}"); + output + }) + )?; + } + Ok(()) + } } #[cfg(test)] mod tests { #![allow(clippy::undocumented_unsafe_blocks)] + use std::os::unix::io::AsRawFd; + + use kvm_bindings::{KVM_ARM_VCPU_PSCI_0_2, KVM_REG_SIZE_U64}; + use vm_memory::GuestAddress; use super::*; + use crate::arch::BootProtocol; use crate::arch::aarch64::layout; + use crate::arch::aarch64::regs::Aarch64RegisterRef; + use crate::cpu_config::aarch64::CpuConfiguration; + use crate::cpu_config::templates::RegisterValueFilter; use crate::test_utils::arch_mem; + use crate::vcpu::VcpuConfig; use crate::vstate::kvm::Kvm; + use crate::vstate::memory::GuestMemoryMmap; + use crate::vstate::vm::Vm; + use crate::vstate::vm::tests::setup_vm_with_memory; + + fn setup_vcpu(mem_size: usize) -> (Kvm, Vm, KvmVcpu, GuestMemoryMmap) { + let (kvm, mut vm, vm_mem) = setup_vm_with_memory(mem_size); + let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); + vcpu.init(&[]).unwrap(); + vm.setup_irqchip(1).unwrap(); + + (kvm, vm, vcpu, vm_mem) + } + + #[test] + fn test_create_vcpu() { + let (_, vm, _) = setup_vm_with_memory(0x1000); + + unsafe { libc::close(vm.fd().as_raw_fd()) }; + + let err = KvmVcpu::new(0, &vm); + assert_eq!( + err.err().unwrap().to_string(), + "Error creating vcpu: Bad file descriptor (os error 9)".to_string() + ); + + // dropping vm would double close the gic fd, so leak it + std::mem::forget(vm); + } + + #[test] + fn test_configure_vcpu() { + let (kvm, _, mut vcpu, vm_mem) = setup_vcpu(0x10000); + let optional_capabilities = kvm.optional_capabilities(); + + let vcpu_config = VcpuConfig { + vcpu_count: 1, + smt: false, + cpu_config: CpuConfiguration::default(), + }; + + vcpu.configure( + &vm_mem, + EntryPoint { + entry_addr: GuestAddress(crate::arch::get_kernel_start()), + protocol: BootProtocol::LinuxBoot, + }, + &vcpu_config, + &optional_capabilities, + ) + .unwrap(); + + unsafe { libc::close(vcpu.fd.as_raw_fd()) }; + + let err = vcpu.configure( + &vm_mem, + EntryPoint { + entry_addr: GuestAddress(crate::arch::get_kernel_start()), + protocol: BootProtocol::LinuxBoot, + }, + &vcpu_config, + &optional_capabilities, + ); + assert_eq!( + err.unwrap_err(), + KvmVcpuError::ConfigureRegisters(VcpuArchError::SetOneReg( + 0x6030000000100042, + kvm_ioctls::Error::new(9) + )) + ); + + // dropping vcpu would double close the gic fd, so leak it + std::mem::forget(vcpu); + } + + #[test] + fn test_init_vcpu() { + let (_, mut vm, _) = setup_vm_with_memory(0x1000); + let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); + vm.setup_irqchip(1).unwrap(); + + // KVM_ARM_VCPU_PSCI_0_2 is set by default. + // we check if we can remove it. + let vcpu_features = vec![VcpuFeatures { + index: 0, + bitmap: RegisterValueFilter { + filter: 1 << KVM_ARM_VCPU_PSCI_0_2, + value: 0, + }, + }]; + vcpu.init(&vcpu_features).unwrap(); + assert!((vcpu.kvi.features[0] & (1 << KVM_ARM_VCPU_PSCI_0_2)) == 0) + } + + #[test] + fn test_vcpu_save_restore_state() { + let (_, mut vm, _) = setup_vm_with_memory(0x1000); + let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); + vm.setup_irqchip(1).unwrap(); + + // Calling KVM_GET_REGLIST before KVM_VCPU_INIT will result in error. + let res = vcpu.save_state(); + assert!(matches!( + res.unwrap_err(), + KvmVcpuError::SaveState(VcpuArchError::GetRegList(_)) + )); + + // Try to restore the register using a faulty state. + let mut faulty_vcpu_state = VcpuState::default(); + + // Try faulty kvi state + let res = vcpu.restore_state(&faulty_vcpu_state); + assert!(matches!(res.unwrap_err(), KvmVcpuError::Init(_))); + + // Try faulty vcpu regs + faulty_vcpu_state.kvi = KvmVcpu::default_kvi(vm.fd()).unwrap(); + let mut regs = Aarch64RegisterVec::default(); + let mut reg = Aarch64RegisterRef::new(KVM_REG_SIZE_U64, &[0; 8]); + reg.id = 0; + regs.push(reg); + faulty_vcpu_state.regs = regs; + let res = vcpu.restore_state(&faulty_vcpu_state); + assert!(matches!( + res.unwrap_err(), + KvmVcpuError::RestoreState(VcpuArchError::SetOneReg(0, _)) + )); + + vcpu.init(&[]).unwrap(); + let state = vcpu.save_state().expect("Cannot save state of vcpu"); + assert!(!state.regs.is_empty()); + vcpu.restore_state(&state) + .expect("Cannot restore state of vcpu"); + } + + #[test] + fn test_dump_cpu_config_before_init() { + // Test `dump_cpu_config()` before `KVM_VCPU_INIT`. + // + // This should fail with ENOEXEC. + // https://elixir.bootlin.com/linux/v5.10.176/source/arch/arm64/kvm/arm.c#L1165 + let (_, mut vm, _) = setup_vm_with_memory(0x1000); + let vcpu = KvmVcpu::new(0, &vm).unwrap(); + vm.setup_irqchip(1).unwrap(); + + vcpu.dump_cpu_config().unwrap_err(); + } + + #[test] + fn test_dump_cpu_config_after_init() { + // Test `dump_cpu_config()` after `KVM_VCPU_INIT`. + let (_, mut vm, _) = setup_vm_with_memory(0x1000); + let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); + vm.setup_irqchip(1).unwrap(); + vcpu.init(&[]).unwrap(); + + vcpu.dump_cpu_config().unwrap(); + } + + #[test] + fn test_setup_non_boot_vcpu() { + let (_, vm, _) = setup_vm_with_memory(0x1000); + let mut vcpu1 = KvmVcpu::new(0, &vm).unwrap(); + vcpu1.init(&[]).unwrap(); + let mut vcpu2 = KvmVcpu::new(1, &vm).unwrap(); + vcpu2.init(&[]).unwrap(); + } + + #[test] + fn test_get_valid_regs() { + // Test `get_regs()` with valid register IDs. + // - X0: 0x6030 0000 0010 0000 + // - X1: 0x6030 0000 0010 0002 + let (_, _, vcpu, _) = setup_vcpu(0x10000); + let reg_list = Vec::::from([0x6030000000100000, 0x6030000000100002]); + get_registers(&vcpu.fd, ®_list, &mut Aarch64RegisterVec::default()).unwrap(); + } + + #[test] + fn test_get_invalid_regs() { + // Test `get_regs()` with invalid register IDs. + let (_, _, vcpu, _) = setup_vcpu(0x10000); + let reg_list = Vec::::from([0x6030000000100001, 0x6030000000100003]); + get_registers(&vcpu.fd, ®_list, &mut Aarch64RegisterVec::default()).unwrap_err(); + } #[test] fn test_setup_regs() { @@ -250,7 +726,7 @@ mod tests { let res = setup_boot_regs(&vcpu, 0, 0x0, &mem, &optional_capabilities); assert!(matches!( res.unwrap_err(), - VcpuError::SetOneReg(0x6030000000100042, _) + VcpuArchError::SetOneReg(0x6030000000100042, _) )); let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default(); @@ -291,7 +767,7 @@ mod tests { let res = get_mpidr(&vcpu); assert!(matches!( res.unwrap_err(), - VcpuError::GetOneReg(MPIDR_EL1, _) + VcpuArchError::GetOneReg(MPIDR_EL1, _) )); vcpu.vcpu_init(&kvi).unwrap(); @@ -309,7 +785,7 @@ mod tests { // Must fail when vcpu is not initialized yet. let mut regs = Aarch64RegisterVec::default(); let res = get_all_registers(&vcpu, &mut regs); - assert!(matches!(res.unwrap_err(), VcpuError::GetRegList(_))); + assert!(matches!(res.unwrap_err(), VcpuArchError::GetRegList(_))); vcpu.vcpu_init(&kvi).unwrap(); get_all_registers(&vcpu, &mut regs).unwrap(); @@ -334,10 +810,10 @@ mod tests { unsafe { libc::close(vcpu.as_raw_fd()) }; let res = get_mpstate(&vcpu); - assert!(matches!(res, Err(VcpuError::GetMp(_))), "{:?}", res); + assert!(matches!(res, Err(VcpuArchError::GetMp(_))), "{:?}", res); let res = set_mpstate(&vcpu, kvm_mp_state::default()); - assert!(matches!(res, Err(VcpuError::SetMp(_))), "{:?}", res); + assert!(matches!(res, Err(VcpuArchError::SetMp(_))), "{:?}", res); // dropping vcpu would double close the fd, so leak it std::mem::forget(vcpu); diff --git a/src/vmm/src/vstate/vm/aarch64.rs b/src/vmm/src/arch/aarch64/vm.rs similarity index 90% rename from src/vmm/src/vstate/vm/aarch64.rs rename to src/vmm/src/arch/aarch64/vm.rs index 5f22e1432ee..d1b6f07663f 100644 --- a/src/vmm/src/vstate/vm/aarch64.rs +++ b/src/vmm/src/arch/aarch64/vm.rs @@ -4,14 +4,15 @@ use kvm_ioctls::VmFd; use serde::{Deserialize, Serialize}; -use super::VmError; use crate::Kvm; use crate::arch::aarch64::gic::GicState; +use crate::vstate::vm::VmError; /// Structure representing the current architecture's understand of what a "virtual machine" is. #[derive(Debug)] pub struct ArchVm { - pub(super) fd: VmFd, + /// KVM file descriptor of microVM + pub fd: VmFd, // On aarch64 we need to keep around the fd obtained by creating the VGIC device. irqchip_handle: Option, } @@ -37,11 +38,13 @@ impl ArchVm { }) } - pub(super) fn arch_pre_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> { + /// Pre-vCPU creation setup. + pub fn arch_pre_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> { Ok(()) } - pub(super) fn arch_post_create_vcpus(&mut self, nr_vcpus: u8) -> Result<(), ArchVmError> { + /// Post-vCPU creation setup. + pub fn arch_post_create_vcpus(&mut self, nr_vcpus: u8) -> Result<(), ArchVmError> { // On aarch64, the vCPUs need to be created (i.e call KVM_CREATE_VCPU) before setting up the // IRQ chip because the `KVM_CREATE_VCPU` ioctl will return error if the IRQCHIP // was already initialized. diff --git a/src/vmm/src/arch/mod.rs b/src/vmm/src/arch/mod.rs index 41530737089..dd3ae178127 100644 --- a/src/vmm/src/arch/mod.rs +++ b/src/vmm/src/arch/mod.rs @@ -12,6 +12,12 @@ use vm_memory::GuestAddress; #[cfg(target_arch = "aarch64")] pub mod aarch64; +#[cfg(target_arch = "aarch64")] +pub use aarch64::kvm::{Kvm, KvmArchError, OptionalCapabilities}; +#[cfg(target_arch = "aarch64")] +pub use aarch64::vcpu::*; +#[cfg(target_arch = "aarch64")] +pub use aarch64::vm::{ArchVm, ArchVmError, VmState}; #[cfg(target_arch = "aarch64")] pub use aarch64::{ ConfigurationError, MMIO_MEM_SIZE, MMIO_MEM_START, arch_memory_regions, configure_system, @@ -24,7 +30,13 @@ pub use aarch64::{ pub mod x86_64; #[cfg(target_arch = "x86_64")] -pub use crate::arch::x86_64::{ +pub use x86_64::kvm::{Kvm, KvmArchError}; +#[cfg(target_arch = "x86_64")] +pub use x86_64::vcpu::*; +#[cfg(target_arch = "x86_64")] +pub use x86_64::vm::{ArchVm, ArchVmError, VmState}; +#[cfg(target_arch = "x86_64")] +pub use x86_64::{ ConfigurationError, MMIO_MEM_SIZE, MMIO_MEM_START, arch_memory_regions, configure_system, get_kernel_start, initrd_load_addr, layout::APIC_ADDR, layout::CMDLINE_MAX_SIZE, layout::IOAPIC_ADDR, layout::IRQ_BASE, layout::IRQ_MAX, layout::SYSTEM_MEM_SIZE, @@ -79,7 +91,7 @@ impl fmt::Display for DeviceType { } } -/// Suported boot protocols for +/// Supported boot protocols for #[derive(Debug, Copy, Clone, PartialEq)] pub enum BootProtocol { /// Linux 64-bit boot protocol diff --git a/src/vmm/src/arch/x86_64/kvm.rs b/src/vmm/src/arch/x86_64/kvm.rs new file mode 100644 index 00000000000..4d77b0fdbb6 --- /dev/null +++ b/src/vmm/src/arch/x86_64/kvm.rs @@ -0,0 +1,74 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use kvm_bindings::{CpuId, KVM_MAX_CPUID_ENTRIES, MsrList}; +use kvm_ioctls::Kvm as KvmFd; + +use crate::arch::x86_64::xstate::{XstateError, request_dynamic_xstate_features}; +use crate::cpu_config::templates::KvmCapability; + +/// Architecture specific error for KVM initialization +#[derive(Debug, thiserror::Error, displaydoc::Display)] +pub enum KvmArchError { + /// Failed to get supported cpuid: {0} + GetSupportedCpuId(kvm_ioctls::Error), + /// Failed to request permission for dynamic XSTATE features: {0} + XstateFeatures(XstateError), +} + +/// Struct with kvm fd and kvm associated parameters. +#[derive(Debug)] +pub struct Kvm { + /// KVM fd. + pub fd: KvmFd, + /// Maximum number of memory slots allowed by KVM. + pub max_memslots: usize, + /// Additional capabilities that were specified in cpu template. + pub kvm_cap_modifiers: Vec, + /// Supported CpuIds. + pub supported_cpuid: CpuId, +} + +impl Kvm { + pub(crate) const DEFAULT_CAPABILITIES: [u32; 14] = [ + kvm_bindings::KVM_CAP_IRQCHIP, + kvm_bindings::KVM_CAP_IOEVENTFD, + kvm_bindings::KVM_CAP_IRQFD, + kvm_bindings::KVM_CAP_USER_MEMORY, + kvm_bindings::KVM_CAP_SET_TSS_ADDR, + kvm_bindings::KVM_CAP_PIT2, + kvm_bindings::KVM_CAP_PIT_STATE2, + kvm_bindings::KVM_CAP_ADJUST_CLOCK, + kvm_bindings::KVM_CAP_DEBUGREGS, + kvm_bindings::KVM_CAP_MP_STATE, + kvm_bindings::KVM_CAP_VCPU_EVENTS, + kvm_bindings::KVM_CAP_XCRS, + kvm_bindings::KVM_CAP_XSAVE, + kvm_bindings::KVM_CAP_EXT_CPUID, + ]; + + /// Initialize [`Kvm`] type for x86_64 architecture + pub fn init_arch( + fd: KvmFd, + max_memslots: usize, + kvm_cap_modifiers: Vec, + ) -> Result { + request_dynamic_xstate_features().map_err(KvmArchError::XstateFeatures)?; + + let supported_cpuid = fd + .get_supported_cpuid(KVM_MAX_CPUID_ENTRIES) + .map_err(KvmArchError::GetSupportedCpuId)?; + + Ok(Kvm { + fd, + max_memslots, + kvm_cap_modifiers, + supported_cpuid, + }) + } + + /// Msrs needed to be saved on snapshot creation. + pub fn msrs_to_save(&self) -> Result { + crate::arch::x86_64::msr::get_msrs_to_save(&self.fd) + } +} diff --git a/src/vmm/src/arch/x86_64/mod.rs b/src/vmm/src/arch/x86_64/mod.rs index f10fd3446d8..24a7de7ec8a 100644 --- a/src/vmm/src/arch/x86_64/mod.rs +++ b/src/vmm/src/arch/x86_64/mod.rs @@ -12,6 +12,8 @@ pub mod cpu_model; mod gdt; /// Contains logic for setting up Advanced Programmable Interrupt Controller (local version). pub mod interrupts; +/// Architecture specific KVM-related code +pub mod kvm; /// Layout for the x86_64 system. pub mod layout; mod mptable; @@ -19,6 +21,10 @@ mod mptable; pub mod msr; /// Logic for configuring x86_64 registers. pub mod regs; +/// Architecture specific vCPU code +pub mod vcpu; +/// Architecture specific VM state code +pub mod vm; /// Logic for configuring XSTATE features. pub mod xstate; @@ -221,7 +227,7 @@ fn configure_pvh( // boot_params. This will be stored at PVH_INFO_START address, and %rbx // will be initialized to contain PVH_INFO_START prior to starting the // guest, as required by the PVH ABI. - #[allow(clippy::cast_possible_truncation)] // the vec lenghts are single digit integers + #[allow(clippy::cast_possible_truncation)] // the vec lengths are single digit integers let mut start_info = hvm_start_info { magic: XEN_HVM_START_MAGIC_VALUE, version: 1, diff --git a/src/vmm/src/vstate/vcpu/x86_64.rs b/src/vmm/src/arch/x86_64/vcpu.rs similarity index 99% rename from src/vmm/src/vstate/vcpu/x86_64.rs rename to src/vmm/src/arch/x86_64/vcpu.rs index d4d6df12c16..3cf53cdefc1 100644 --- a/src/vmm/src/vstate/vcpu/x86_64.rs +++ b/src/vmm/src/arch/x86_64/vcpu.rs @@ -25,7 +25,7 @@ use crate::arch::x86_64::regs::{SetupFpuError, SetupRegistersError, SetupSpecial use crate::cpu_config::x86_64::{CpuConfiguration, cpuid}; use crate::logger::{IncMetric, METRICS}; use crate::vstate::memory::GuestMemoryMmap; -use crate::vstate::vcpu::{VcpuConfig, VcpuEmulation}; +use crate::vstate::vcpu::{VcpuConfig, VcpuEmulation, VcpuError}; use crate::vstate::vm::Vm; // Tolerance for TSC frequency expected variation. @@ -145,7 +145,7 @@ pub struct KvmVcpu { /// KVM vcpu fd. pub fd: VcpuFd, /// Vcpu peripherals, such as buses - pub(super) peripherals: Peripherals, + pub peripherals: Peripherals, /// The list of MSRs to include in a VM snapshot, in the same order as KVM returned them /// from KVM_GET_MSR_INDEX_LIST msrs_to_save: Vec, @@ -157,7 +157,7 @@ pub struct KvmVcpu { /// Vcpu peripherals #[derive(Default, Debug)] -pub(super) struct Peripherals { +pub struct Peripherals { /// Pio bus. pub pio_bus: Option, /// Mmio bus. @@ -334,7 +334,7 @@ impl KvmVcpu { /// /// # Errors /// - /// When [`kvm_ioctls::VcpuFd::get_tsc_khz`] errrors. + /// When [`kvm_ioctls::VcpuFd::get_tsc_khz`] errors. pub fn get_tsc_khz(&self) -> Result { let res = self.fd.get_tsc_khz()?; Ok(res) @@ -477,7 +477,7 @@ impl KvmVcpu { /// # Arguments /// /// * `msr_index_iter`: Iterator over MSR indices. - /// * `chunk_size`: Lenght of a chunk. + /// * `chunk_size`: Length of a chunk. /// /// # Errors /// @@ -504,7 +504,7 @@ impl KvmVcpu { .map_err(KvmVcpuError::VcpuGetMsrs)?; // GET_MSRS returns a number of successfully set msrs. // If number of set msrs is not equal to the length of - // `msrs`, then the value retuned by GET_MSRS can act + // `msrs`, then the value returned by GET_MSRS can act // as an index to the problematic msr. if nmsrs != chunk_size { Err(KvmVcpuError::VcpuGetMsr(msrs.as_slice()[nmsrs].index)) @@ -621,7 +621,7 @@ impl KvmVcpu { // in the state. If they are different, we need to // scale the TSC to the freq found in the state. // We accept values within a tolerance of 250 parts - // per million beacuse it is common for TSC frequency + // per million because it is common for TSC frequency // to differ due to calibration at boot time. let diff = (i64::from(self.get_tsc_khz()?) - i64::from(state_tsc_freq)).abs(); // Cannot overflow since u32::MAX * 250 < i64::MAX @@ -705,7 +705,7 @@ impl Peripherals { /// Runs the vCPU in KVM context and handles the kvm exit reason. /// /// Returns error or enum specifying whether emulation was handled or interrupted. - pub fn run_arch_emulation(&self, exit: VcpuExit) -> Result { + pub fn run_arch_emulation(&self, exit: VcpuExit) -> Result { match exit { VcpuExit::IoIn(addr, data) => { if let Some(pio_bus) = &self.pio_bus { @@ -728,7 +728,7 @@ impl Peripherals { // TODO: Are we sure we want to finish running a vcpu upon // receiving a vm exit that is not necessarily an error? error!("Unexpected exit reason on vcpu run: {:?}", unexpected_exit); - Err(super::VcpuError::UnhandledKvmExit(format!( + Err(VcpuError::UnhandledKvmExit(format!( "{:?}", unexpected_exit ))) diff --git a/src/vmm/src/vstate/vm/x86_64.rs b/src/vmm/src/arch/x86_64/vm.rs similarity index 97% rename from src/vmm/src/vstate/vm/x86_64.rs rename to src/vmm/src/arch/x86_64/vm.rs index 69bad36c09e..ebce8ef02d0 100644 --- a/src/vmm/src/vstate/vm/x86_64.rs +++ b/src/vmm/src/arch/x86_64/vm.rs @@ -48,7 +48,8 @@ pub enum ArchVmError { /// Structure representing the current architecture's understand of what a "virtual machine" is. #[derive(Debug)] pub struct ArchVm { - pub(super) fd: VmFd, + /// KVM file descriptor of microVM + pub fd: VmFd, msrs_to_save: MsrList, /// Size in bytes requiring to hold the dynamically-sized `kvm_xsave` struct. /// @@ -93,12 +94,14 @@ impl ArchVm { }) } - pub(super) fn arch_pre_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> { + /// Pre-vCPU creation setup. + pub fn arch_pre_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> { // For x86_64 we need to create the interrupt controller before calling `KVM_CREATE_VCPUS` self.setup_irqchip() } - pub(super) fn arch_post_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> { + /// Post-vCPU creation setup. + pub fn arch_post_create_vcpus(&mut self, _: u8) -> Result<(), ArchVmError> { Ok(()) } diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 476bbc365c9..ae9cab9e51c 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -420,10 +420,10 @@ pub enum BuildMicrovmFromSnapshotError { TscFrequencyNotPresent, #[cfg(target_arch = "x86_64")] /// Could not get TSC to check if TSC scaling was required with the snapshot: {0} - GetTsc(#[from] crate::vstate::vcpu::GetTscError), + GetTsc(#[from] crate::arch::GetTscError), #[cfg(target_arch = "x86_64")] /// Could not set TSC scaling within the snapshot: {0} - SetTsc(#[from] crate::vstate::vcpu::SetTscError), + SetTsc(#[from] crate::arch::SetTscError), /// Failed to restore microVM state: {0} RestoreState(#[from] crate::vstate::vm::ArchVmError), /// Failed to update microVM configuration: {0} diff --git a/src/vmm/src/cpu_config/aarch64/mod.rs b/src/vmm/src/cpu_config/aarch64/mod.rs index d11a4f8c0ab..26ca48572ac 100644 --- a/src/vmm/src/cpu_config/aarch64/mod.rs +++ b/src/vmm/src/cpu_config/aarch64/mod.rs @@ -10,12 +10,12 @@ pub mod test_utils; use super::templates::CustomCpuTemplate; use crate::arch::aarch64::regs::{Aarch64RegisterVec, RegSize}; -use crate::arch::aarch64::vcpu::VcpuError as ArchError; +use crate::arch::aarch64::vcpu::VcpuArchError; /// Errors thrown while configuring templates. #[derive(Debug, PartialEq, Eq, thiserror::Error)] #[error("Failed to create a guest cpu configuration: {0}")] -pub struct CpuConfigurationError(#[from] pub ArchError); +pub struct CpuConfigurationError(#[from] pub VcpuArchError); /// CPU configuration for aarch64 #[derive(Debug, Default, Clone, PartialEq, Eq)] diff --git a/src/vmm/src/gdb/target.rs b/src/vmm/src/gdb/target.rs index c9d71e1f14a..c3293db1607 100644 --- a/src/vmm/src/gdb/target.rs +++ b/src/vmm/src/gdb/target.rs @@ -33,7 +33,7 @@ use vm_memory::{Bytes, GuestAddress, GuestMemoryError}; use super::arch; use crate::arch::GUEST_PAGE_SIZE; #[cfg(target_arch = "aarch64")] -use crate::arch::aarch64::vcpu::VcpuError as AarchVcpuError; +use crate::arch::aarch64::vcpu::VcpuArchError as AarchVcpuError; use crate::logger::{error, info}; use crate::utils::u64_to_usize; use crate::vstate::vcpu::VcpuSendEventError; diff --git a/src/vmm/src/vstate/kvm.rs b/src/vmm/src/vstate/kvm.rs index 7c44cfb91cb..b89d0e1a0d8 100644 --- a/src/vmm/src/vstate/kvm.rs +++ b/src/vmm/src/vstate/kvm.rs @@ -2,13 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use kvm_bindings::KVM_API_VERSION; -#[cfg(target_arch = "x86_64")] -use kvm_bindings::{CpuId, KVM_MAX_CPUID_ENTRIES, MsrList}; use kvm_ioctls::Kvm as KvmFd; use serde::{Deserialize, Serialize}; -#[cfg(target_arch = "x86_64")] -use crate::arch::x86_64::xstate::{XstateError, request_dynamic_xstate_features}; +pub use crate::arch::{Kvm, KvmArchError}; use crate::cpu_config::templates::KvmCapability; use crate::vstate::memory::{GuestMemory, GuestMemoryMmap}; @@ -24,29 +21,10 @@ pub enum KvmError { /** Error creating KVM object: {0} Make sure the user launching the firecracker process is \ configured on the /dev/kvm file's ACL. */ Kvm(kvm_ioctls::Error), - #[cfg(target_arch = "x86_64")] - /// Failed to get supported cpuid: {0} - GetSupportedCpuId(kvm_ioctls::Error), /// The number of configured slots is bigger than the maximum reported by KVM NotEnoughMemorySlots, - #[cfg(target_arch = "x86_64")] - /// Failed to request permission for dynamic XSTATE features: {0} - XstateFeatures(XstateError), -} - -/// Struct with kvm fd and kvm associated paramenters. -#[derive(Debug)] -pub struct Kvm { - /// KVM fd. - pub fd: KvmFd, - /// Maximum number of memory slots allowed by KVM. - pub max_memslots: usize, - /// Additional capabilities that were specified in cpu template. - pub kvm_cap_modifiers: Vec, - - #[cfg(target_arch = "x86_64")] - /// Supported CpuIds. - pub supported_cpuid: CpuId, + /// Architecture specific error: {0} + ArchError(#[from] KvmArchError) } impl Kvm { @@ -67,36 +45,7 @@ impl Kvm { let max_memslots = kvm_fd.get_nr_memslots(); - #[cfg(target_arch = "aarch64")] - { - Ok(Self { - fd: kvm_fd, - max_memslots, - kvm_cap_modifiers, - }) - } - - #[cfg(target_arch = "x86_64")] - { - request_dynamic_xstate_features().map_err(KvmError::XstateFeatures)?; - - let supported_cpuid = kvm_fd - .get_supported_cpuid(KVM_MAX_CPUID_ENTRIES) - .map_err(KvmError::GetSupportedCpuId)?; - - Ok(Kvm { - fd: kvm_fd, - max_memslots, - kvm_cap_modifiers, - supported_cpuid, - }) - } - } - - /// Msrs needed to be saved on snapshot creation. - #[cfg(target_arch = "x86_64")] - pub fn msrs_to_save(&self) -> Result { - crate::arch::x86_64::msr::get_msrs_to_save(&self.fd) + Ok(Kvm::init_arch(kvm_fd, max_memslots, kvm_cap_modifiers)?) } /// Check guest memory does not have more regions than kvm allows. @@ -144,55 +93,6 @@ impl Kvm { } } } -#[cfg(target_arch = "aarch64")] -/// Optional capabilities. -#[derive(Debug, Default)] -pub struct OptionalCapabilities { - /// KVM_CAP_COUNTER_OFFSET - pub counter_offset: bool, -} -#[cfg(target_arch = "aarch64")] -impl Kvm { - const DEFAULT_CAPABILITIES: [u32; 7] = [ - kvm_bindings::KVM_CAP_IOEVENTFD, - kvm_bindings::KVM_CAP_IRQFD, - kvm_bindings::KVM_CAP_USER_MEMORY, - kvm_bindings::KVM_CAP_ARM_PSCI_0_2, - kvm_bindings::KVM_CAP_DEVICE_CTRL, - kvm_bindings::KVM_CAP_MP_STATE, - kvm_bindings::KVM_CAP_ONE_REG, - ]; - - /// Returns struct with optional capabilities statuses. - pub fn optional_capabilities(&self) -> OptionalCapabilities { - OptionalCapabilities { - counter_offset: self - .fd - .check_extension_raw(kvm_bindings::KVM_CAP_COUNTER_OFFSET.into()) - != 0, - } - } -} - -#[cfg(target_arch = "x86_64")] -impl Kvm { - const DEFAULT_CAPABILITIES: [u32; 14] = [ - kvm_bindings::KVM_CAP_IRQCHIP, - kvm_bindings::KVM_CAP_IOEVENTFD, - kvm_bindings::KVM_CAP_IRQFD, - kvm_bindings::KVM_CAP_USER_MEMORY, - kvm_bindings::KVM_CAP_SET_TSS_ADDR, - kvm_bindings::KVM_CAP_PIT2, - kvm_bindings::KVM_CAP_PIT_STATE2, - kvm_bindings::KVM_CAP_ADJUST_CLOCK, - kvm_bindings::KVM_CAP_DEBUGREGS, - kvm_bindings::KVM_CAP_MP_STATE, - kvm_bindings::KVM_CAP_VCPU_EVENTS, - kvm_bindings::KVM_CAP_XCRS, - kvm_bindings::KVM_CAP_XSAVE, - kvm_bindings::KVM_CAP_EXT_CPUID, - ]; -} /// Structure holding an general specific VM state. #[derive(Debug, Default, Serialize, Deserialize)] diff --git a/src/vmm/src/vstate/vcpu/mod.rs b/src/vmm/src/vstate/vcpu.rs similarity index 99% rename from src/vmm/src/vstate/vcpu/mod.rs rename to src/vmm/src/vstate/vcpu.rs index a04177cd938..d54d7de759b 100644 --- a/src/vmm/src/vstate/vcpu/mod.rs +++ b/src/vmm/src/vstate/vcpu.rs @@ -23,6 +23,7 @@ use vmm_sys_util::errno; use vmm_sys_util::eventfd::EventFd; use crate::FcExitCode; +pub use crate::arch::{KvmVcpu, KvmVcpuConfigureError, KvmVcpuError, Peripherals, VcpuState}; use crate::cpu_config::templates::{CpuConfiguration, GuestConfigError}; #[cfg(feature = "gdb")] use crate::gdb::target::{GdbTargetError, get_raw_tid}; @@ -32,18 +33,6 @@ use crate::utils::signal::{Killable, register_signal_handler, sigrtmin}; use crate::utils::sm::StateMachine; use crate::vstate::vm::Vm; -/// Module with aarch64 vCPU implementation. -#[cfg(target_arch = "aarch64")] -pub mod aarch64; -/// Module with x86_64 vCPU implementation. -#[cfg(target_arch = "x86_64")] -pub mod x86_64; - -#[cfg(target_arch = "aarch64")] -pub use aarch64::{KvmVcpuError, *}; -#[cfg(target_arch = "x86_64")] -pub use x86_64::{KvmVcpuError, *}; - /// Signal number (SIGRTMIN) used to kick Vcpus. pub const VCPU_RTSIG_OFFSET: i32 = 0; diff --git a/src/vmm/src/vstate/vcpu/aarch64.rs b/src/vmm/src/vstate/vcpu/aarch64.rs deleted file mode 100644 index 98972c796d2..00000000000 --- a/src/vmm/src/vstate/vcpu/aarch64.rs +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -// -// Portions Copyright 2017 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the THIRD-PARTY file. - -use std::fmt::{Debug, Write}; - -use kvm_bindings::{ - KVM_ARM_VCPU_POWER_OFF, KVM_ARM_VCPU_PSCI_0_2, KVM_ARM_VCPU_SVE, kvm_mp_state, kvm_vcpu_init, -}; -use kvm_ioctls::*; -use serde::{Deserialize, Serialize}; - -use crate::arch::EntryPoint; -use crate::arch::aarch64::regs::{Aarch64RegisterVec, KVM_REG_ARM64_SVE_VLS}; -use crate::arch::aarch64::vcpu::{ - VcpuError as ArchError, get_all_registers, get_all_registers_ids, get_mpidr, get_mpstate, - get_registers, set_mpstate, set_register, setup_boot_regs, -}; -use crate::cpu_config::aarch64::custom_cpu_template::VcpuFeatures; -use crate::cpu_config::templates::CpuConfiguration; -use crate::logger::{IncMetric, METRICS, error}; -use crate::vcpu::{VcpuConfig, VcpuError}; -use crate::vstate::kvm::OptionalCapabilities; -use crate::vstate::memory::{Address, GuestMemoryMmap}; -use crate::vstate::vcpu::VcpuEmulation; -use crate::vstate::vm::Vm; - -/// Errors associated with the wrappers over KVM ioctls. -#[derive(Debug, PartialEq, Eq, thiserror::Error, displaydoc::Display)] -pub enum KvmVcpuError { - /// Error configuring the vcpu registers: {0} - ConfigureRegisters(ArchError), - /// Error creating vcpu: {0} - CreateVcpu(kvm_ioctls::Error), - /// Failed to dump CPU configuration: {0} - DumpCpuConfig(ArchError), - /// Error getting the vcpu preferred target: {0} - GetPreferredTarget(kvm_ioctls::Error), - /// Error initializing the vcpu: {0} - Init(kvm_ioctls::Error), - /// Error applying template: {0} - ApplyCpuTemplate(ArchError), - /// Failed to restore the state of the vcpu: {0} - RestoreState(ArchError), - /// Failed to save the state of the vcpu: {0} - SaveState(ArchError), -} - -/// Error type for [`KvmVcpu::configure`]. -pub type KvmVcpuConfigureError = KvmVcpuError; - -/// A wrapper around creating and using a kvm aarch64 vcpu. -#[derive(Debug)] -pub struct KvmVcpu { - /// Index of vcpu. - pub index: u8, - /// KVM vcpu fd. - pub fd: VcpuFd, - /// Vcpu peripherals, such as buses - pub(super) peripherals: Peripherals, - mpidr: u64, - kvi: kvm_vcpu_init, -} - -/// Vcpu peripherals -#[derive(Default, Debug)] -pub(super) struct Peripherals { - /// mmio bus. - pub mmio_bus: Option, -} - -impl KvmVcpu { - /// Constructs a new kvm vcpu with arch specific functionality. - /// - /// # Arguments - /// - /// * `index` - Represents the 0-based CPU index between [0, max vcpus). - /// * `vm` - The vm to which this vcpu will get attached. - pub fn new(index: u8, vm: &Vm) -> Result { - let kvm_vcpu = vm - .fd() - .create_vcpu(index.into()) - .map_err(KvmVcpuError::CreateVcpu)?; - - let mut kvi = Self::default_kvi(vm.fd())?; - // Secondary vcpus must be powered off for boot process. - if 0 < index { - kvi.features[0] |= 1 << KVM_ARM_VCPU_POWER_OFF; - } - - Ok(KvmVcpu { - index, - fd: kvm_vcpu, - peripherals: Default::default(), - mpidr: 0, - kvi, - }) - } - - /// Gets the MPIDR register value. - pub fn get_mpidr(&self) -> u64 { - self.mpidr - } - - /// Configures an aarch64 specific vcpu for booting Linux. - /// - /// # Arguments - /// - /// * `guest_mem` - The guest memory used by this microvm. - /// * `kernel_entry_point` - Specifies the boot protocol and offset from `guest_mem` at which - /// the kernel starts. - /// * `vcpu_config` - The vCPU configuration. - pub fn configure( - &mut self, - guest_mem: &GuestMemoryMmap, - kernel_entry_point: EntryPoint, - vcpu_config: &VcpuConfig, - optional_capabilities: &OptionalCapabilities, - ) -> Result<(), KvmVcpuError> { - for reg in vcpu_config.cpu_config.regs.iter() { - self.fd - .set_one_reg(reg.id, reg.as_slice()) - .map_err(|err| KvmVcpuError::ApplyCpuTemplate(ArchError::SetOneReg(reg.id, err)))?; - } - - setup_boot_regs( - &self.fd, - self.index, - kernel_entry_point.entry_addr.raw_value(), - guest_mem, - optional_capabilities, - ) - .map_err(KvmVcpuError::ConfigureRegisters)?; - - self.mpidr = get_mpidr(&self.fd).map_err(KvmVcpuError::ConfigureRegisters)?; - - Ok(()) - } - - /// Initializes an aarch64 specific vcpu for booting Linux. - /// - /// # Arguments - /// - /// * `vm_fd` - The kvm `VmFd` for this microvm. - pub fn init(&mut self, vcpu_features: &[VcpuFeatures]) -> Result<(), KvmVcpuError> { - for feature in vcpu_features.iter() { - let index = feature.index as usize; - self.kvi.features[index] = feature.bitmap.apply(self.kvi.features[index]); - } - - self.init_vcpu()?; - self.finalize_vcpu()?; - - Ok(()) - } - - /// Creates default kvi struct based on vcpu index. - pub fn default_kvi(vm_fd: &VmFd) -> Result { - let mut kvi = kvm_vcpu_init::default(); - // This reads back the kernel's preferred target type. - vm_fd - .get_preferred_target(&mut kvi) - .map_err(KvmVcpuError::GetPreferredTarget)?; - // We already checked that the capability is supported. - kvi.features[0] |= 1 << KVM_ARM_VCPU_PSCI_0_2; - - Ok(kvi) - } - - /// Save the KVM internal state. - pub fn save_state(&self) -> Result { - let mut state = VcpuState { - mp_state: get_mpstate(&self.fd).map_err(KvmVcpuError::SaveState)?, - ..Default::default() - }; - get_all_registers(&self.fd, &mut state.regs).map_err(KvmVcpuError::SaveState)?; - state.mpidr = get_mpidr(&self.fd).map_err(KvmVcpuError::SaveState)?; - - state.kvi = self.kvi; - // We don't save power off state in a snapshot, because - // it was only needed during uVM boot process. - // When uVM is restored, the kernel has already passed - // the boot state and turned secondary vcpus on. - state.kvi.features[0] &= !(1 << KVM_ARM_VCPU_POWER_OFF); - - Ok(state) - } - - /// Use provided state to populate KVM internal state. - pub fn restore_state(&mut self, state: &VcpuState) -> Result<(), KvmVcpuError> { - self.kvi = state.kvi; - - self.init_vcpu()?; - - // If KVM_REG_ARM64_SVE_VLS is present it needs to - // be set before vcpu is finalized. - if let Some(sve_vls_reg) = state - .regs - .iter() - .find(|reg| reg.id == KVM_REG_ARM64_SVE_VLS) - { - set_register(&self.fd, sve_vls_reg).map_err(KvmVcpuError::RestoreState)?; - } - - self.finalize_vcpu()?; - - // KVM_REG_ARM64_SVE_VLS needs to be skipped after vcpu is finalized. - // If it is present it is handled in the code above. - for reg in state - .regs - .iter() - .filter(|reg| reg.id != KVM_REG_ARM64_SVE_VLS) - { - set_register(&self.fd, reg).map_err(KvmVcpuError::RestoreState)?; - } - set_mpstate(&self.fd, state.mp_state).map_err(KvmVcpuError::RestoreState)?; - Ok(()) - } - - /// Dumps CPU configuration. - pub fn dump_cpu_config(&self) -> Result { - let reg_list = get_all_registers_ids(&self.fd).map_err(KvmVcpuError::DumpCpuConfig)?; - - let mut regs = Aarch64RegisterVec::default(); - get_registers(&self.fd, ®_list, &mut regs).map_err(KvmVcpuError::DumpCpuConfig)?; - - Ok(CpuConfiguration { regs }) - } - /// Initializes internal vcpufd. - fn init_vcpu(&self) -> Result<(), KvmVcpuError> { - self.fd.vcpu_init(&self.kvi).map_err(KvmVcpuError::Init)?; - Ok(()) - } - - /// Checks for SVE feature and calls `vcpu_finalize` if - /// it is enabled. - fn finalize_vcpu(&self) -> Result<(), KvmVcpuError> { - if (self.kvi.features[0] & (1 << KVM_ARM_VCPU_SVE)) != 0 { - // KVM_ARM_VCPU_SVE has value 4 so casting to i32 is safe. - #[allow(clippy::cast_possible_wrap)] - let feature = KVM_ARM_VCPU_SVE as i32; - self.fd.vcpu_finalize(&feature).unwrap(); - } - Ok(()) - } -} - -impl Peripherals { - /// Runs the vCPU in KVM context and handles the kvm exit reason. - /// - /// Returns error or enum specifying whether emulation was handled or interrupted. - pub fn run_arch_emulation(&self, exit: VcpuExit) -> Result { - METRICS.vcpu.failures.inc(); - // TODO: Are we sure we want to finish running a vcpu upon - // receiving a vm exit that is not necessarily an error? - error!("Unexpected exit reason on vcpu run: {:?}", exit); - Err(VcpuError::UnhandledKvmExit(format!("{:?}", exit))) - } -} - -/// Structure holding VCPU kvm state. -#[derive(Default, Clone, Serialize, Deserialize)] -pub struct VcpuState { - /// Multiprocessing state. - pub mp_state: kvm_mp_state, - /// Vcpu registers. - pub regs: Aarch64RegisterVec, - /// We will be using the mpidr for passing it to the VmState. - /// The VmState will give this away for saving restoring the icc and redistributor - /// registers. - pub mpidr: u64, - /// kvi states for vcpu initialization. - pub kvi: kvm_vcpu_init, -} - -impl Debug for VcpuState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "kvm_mp_state: {:#x}", self.mp_state.mp_state)?; - writeln!(f, "mpidr: {:#x}", self.mpidr)?; - for reg in self.regs.iter() { - writeln!( - f, - "{:#x} 0x{}", - reg.id, - reg.as_slice() - .iter() - .rev() - .fold(String::new(), |mut output, b| { - let _ = write!(output, "{b:x}"); - output - }) - )?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - #![allow(clippy::undocumented_unsafe_blocks)] - use std::os::unix::io::AsRawFd; - - use kvm_bindings::{KVM_ARM_VCPU_PSCI_0_2, KVM_REG_SIZE_U64}; - use vm_memory::GuestAddress; - - use super::*; - use crate::arch::BootProtocol; - use crate::arch::aarch64::regs::Aarch64RegisterRef; - use crate::cpu_config::aarch64::CpuConfiguration; - use crate::cpu_config::templates::RegisterValueFilter; - use crate::vcpu::VcpuConfig; - use crate::vstate::kvm::Kvm; - use crate::vstate::memory::GuestMemoryMmap; - use crate::vstate::vm::Vm; - use crate::vstate::vm::tests::setup_vm_with_memory; - - fn setup_vcpu(mem_size: usize) -> (Kvm, Vm, KvmVcpu, GuestMemoryMmap) { - let (kvm, mut vm, vm_mem) = setup_vm_with_memory(mem_size); - let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); - vcpu.init(&[]).unwrap(); - vm.setup_irqchip(1).unwrap(); - - (kvm, vm, vcpu, vm_mem) - } - - #[test] - fn test_create_vcpu() { - let (_, vm, _) = setup_vm_with_memory(0x1000); - - unsafe { libc::close(vm.fd().as_raw_fd()) }; - - let err = KvmVcpu::new(0, &vm); - assert_eq!( - err.err().unwrap().to_string(), - "Error creating vcpu: Bad file descriptor (os error 9)".to_string() - ); - - // dropping vm would double close the gic fd, so leak it - std::mem::forget(vm); - } - - #[test] - fn test_configure_vcpu() { - let (kvm, _, mut vcpu, vm_mem) = setup_vcpu(0x10000); - let optional_capabilities = kvm.optional_capabilities(); - - let vcpu_config = VcpuConfig { - vcpu_count: 1, - smt: false, - cpu_config: CpuConfiguration::default(), - }; - - vcpu.configure( - &vm_mem, - EntryPoint { - entry_addr: GuestAddress(crate::arch::get_kernel_start()), - protocol: BootProtocol::LinuxBoot, - }, - &vcpu_config, - &optional_capabilities, - ) - .unwrap(); - - unsafe { libc::close(vcpu.fd.as_raw_fd()) }; - - let err = vcpu.configure( - &vm_mem, - EntryPoint { - entry_addr: GuestAddress(crate::arch::get_kernel_start()), - protocol: BootProtocol::LinuxBoot, - }, - &vcpu_config, - &optional_capabilities, - ); - assert_eq!( - err.unwrap_err(), - KvmVcpuError::ConfigureRegisters(ArchError::SetOneReg( - 0x6030000000100042, - kvm_ioctls::Error::new(9) - )) - ); - - // dropping vcpu would double close the gic fd, so leak it - std::mem::forget(vcpu); - } - - #[test] - fn test_init_vcpu() { - let (_, mut vm, _) = setup_vm_with_memory(0x1000); - let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); - vm.setup_irqchip(1).unwrap(); - - // KVM_ARM_VCPU_PSCI_0_2 is set by default. - // we check if we can remove it. - let vcpu_features = vec![VcpuFeatures { - index: 0, - bitmap: RegisterValueFilter { - filter: 1 << KVM_ARM_VCPU_PSCI_0_2, - value: 0, - }, - }]; - vcpu.init(&vcpu_features).unwrap(); - assert!((vcpu.kvi.features[0] & (1 << KVM_ARM_VCPU_PSCI_0_2)) == 0) - } - - #[test] - fn test_vcpu_save_restore_state() { - let (_, mut vm, _) = setup_vm_with_memory(0x1000); - let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); - vm.setup_irqchip(1).unwrap(); - - // Calling KVM_GET_REGLIST before KVM_VCPU_INIT will result in error. - let res = vcpu.save_state(); - assert!(matches!( - res.unwrap_err(), - KvmVcpuError::SaveState(ArchError::GetRegList(_)) - )); - - // Try to restore the register using a faulty state. - let mut faulty_vcpu_state = VcpuState::default(); - - // Try faulty kvi state - let res = vcpu.restore_state(&faulty_vcpu_state); - assert!(matches!(res.unwrap_err(), KvmVcpuError::Init(_))); - - // Try faulty vcpu regs - faulty_vcpu_state.kvi = KvmVcpu::default_kvi(vm.fd()).unwrap(); - let mut regs = Aarch64RegisterVec::default(); - let mut reg = Aarch64RegisterRef::new(KVM_REG_SIZE_U64, &[0; 8]); - reg.id = 0; - regs.push(reg); - faulty_vcpu_state.regs = regs; - let res = vcpu.restore_state(&faulty_vcpu_state); - assert!(matches!( - res.unwrap_err(), - KvmVcpuError::RestoreState(ArchError::SetOneReg(0, _)) - )); - - vcpu.init(&[]).unwrap(); - let state = vcpu.save_state().expect("Cannot save state of vcpu"); - assert!(!state.regs.is_empty()); - vcpu.restore_state(&state) - .expect("Cannot restore state of vcpu"); - } - - #[test] - fn test_dump_cpu_config_before_init() { - // Test `dump_cpu_config()` before `KVM_VCPU_INIT`. - // - // This should fail with ENOEXEC. - // https://elixir.bootlin.com/linux/v5.10.176/source/arch/arm64/kvm/arm.c#L1165 - let (_, mut vm, _) = setup_vm_with_memory(0x1000); - let vcpu = KvmVcpu::new(0, &vm).unwrap(); - vm.setup_irqchip(1).unwrap(); - - vcpu.dump_cpu_config().unwrap_err(); - } - - #[test] - fn test_dump_cpu_config_after_init() { - // Test `dump_cpu_config()` after `KVM_VCPU_INIT`. - let (_, mut vm, _) = setup_vm_with_memory(0x1000); - let mut vcpu = KvmVcpu::new(0, &vm).unwrap(); - vm.setup_irqchip(1).unwrap(); - vcpu.init(&[]).unwrap(); - - vcpu.dump_cpu_config().unwrap(); - } - - #[test] - fn test_setup_non_boot_vcpu() { - let (_, vm, _) = setup_vm_with_memory(0x1000); - let mut vcpu1 = KvmVcpu::new(0, &vm).unwrap(); - vcpu1.init(&[]).unwrap(); - let mut vcpu2 = KvmVcpu::new(1, &vm).unwrap(); - vcpu2.init(&[]).unwrap(); - } - - #[test] - fn test_get_valid_regs() { - // Test `get_regs()` with valid register IDs. - // - X0: 0x6030 0000 0010 0000 - // - X1: 0x6030 0000 0010 0002 - let (_, _, vcpu, _) = setup_vcpu(0x10000); - let reg_list = Vec::::from([0x6030000000100000, 0x6030000000100002]); - get_registers(&vcpu.fd, ®_list, &mut Aarch64RegisterVec::default()).unwrap(); - } - - #[test] - fn test_get_invalid_regs() { - // Test `get_regs()` with invalid register IDs. - let (_, _, vcpu, _) = setup_vcpu(0x10000); - let reg_list = Vec::::from([0x6030000000100001, 0x6030000000100003]); - get_registers(&vcpu.fd, ®_list, &mut Aarch64RegisterVec::default()).unwrap_err(); - } -} diff --git a/src/vmm/src/vstate/vm/mod.rs b/src/vmm/src/vstate/vm.rs similarity index 95% rename from src/vmm/src/vstate/vm/mod.rs rename to src/vmm/src/vstate/vm.rs index 49c65bd92ed..4292c2f2f49 100644 --- a/src/vmm/src/vstate/vm/mod.rs +++ b/src/vmm/src/vstate/vm.rs @@ -9,19 +9,10 @@ use kvm_bindings::{KVM_MEM_LOG_DIRTY_PAGES, kvm_userspace_memory_region}; use kvm_ioctls::VmFd; use vmm_sys_util::eventfd::EventFd; +use crate::Vcpu; +pub use crate::arch::{ArchVm as Vm, ArchVmError, VmState}; use crate::logger::info; use crate::vstate::memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion}; - -#[cfg(target_arch = "x86_64")] -#[path = "x86_64.rs"] -mod arch; -#[cfg(target_arch = "aarch64")] -#[path = "aarch64.rs"] -mod arch; - -pub use arch::{ArchVm as Vm, ArchVmError, VmState}; - -use crate::Vcpu; use crate::vstate::vcpu::VcpuError; /// Errors associated with the wrappers over KVM ioctls. @@ -43,7 +34,8 @@ pub enum VmError { /// Contains Vm functions that are usable across CPU architectures impl Vm { - fn create_vm(kvm: &crate::vstate::kvm::Kvm) -> Result { + /// Create a KVM VM + pub fn create_vm(kvm: &crate::vstate::kvm::Kvm) -> Result { // It is known that KVM_CREATE_VM occasionally fails with EINTR on heavily loaded machines // with many VMs. // @@ -57,7 +49,7 @@ impl Vm { // KVM_CREATE_VM returns EINTR. // https://lore.kernel.org/qemu-devel/8735e0s1zw.wl-maz@kernel.org/ // - // To mitigate it, QEMU does an inifinite retry on EINTR that greatly improves reliabiliy: + // To mitigate it, QEMU does an infinite retry on EINTR that greatly improves reliabiliy: // - https://github.com/qemu/qemu/commit/94ccff133820552a859c0fb95e33a539e0b90a75 // - https://github.com/qemu/qemu/commit/bbde13cd14ad4eec18529ce0bf5876058464e124 //