diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 882ba93..fda9a32 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -87,13 +87,30 @@ jobs:
env:
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
run: |
+ # Build documentation
cargo doc --no-deps --all-features
- printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
+
+ # Auto-detect documentation directory
+ # Check if doc exists in target/doc or target/*/doc
+ if [ -d "target/doc" ]; then
+ DOC_DIR="target/doc"
+ else
+ # Find doc directory under target/*/doc pattern
+ DOC_DIR=$(find target -type d -name doc -path "target/*/doc" | head -n 1)
+ if [ -z "$DOC_DIR" ]; then
+ echo "Error: Could not find documentation directory"
+ exit 1
+ fi
+ fi
+
+ echo "Documentation found in: $DOC_DIR"
+ printf '' $(cargo tree | head -1 | cut -d' ' -f1) > "${DOC_DIR}/index.html"
+ echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
- path: target/doc
+ path: ${{ env.DOC_DIR }}
deploy:
name: Deploy to GitHub Pages
diff --git a/Cargo.toml b/Cargo.toml
index 506b227..4f1ed96 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "axvm"
authors = ["aarkegz "]
-version = "0.2.0"
+version = "0.2.3"
edition = "2024"
categories = ["virtualization", "no-std"]
description = "Virtual Machine resource management crate for ArceOS's hypervisor variant."
@@ -12,7 +12,7 @@ license = "Apache-2.0"
[features]
default = ["vmx"]
vmx = []
-4-level-ept = ["arm_vcpu/4-level-ept"] # TODO: Realize 4-level-ept on x86_64 and riscv64.
+4-level-ept = ["axaddrspace/4-level-ept"] # TODO: Realize 4-level-ept on x86_64 and riscv64.
[dependencies]
log = "0.4"
@@ -20,27 +20,27 @@ cfg-if = "1.0"
spin = "0.9"
# System independent crates provided by ArceOS.
-axerrno = "0.1.0"
+axerrno = "0.2"
cpumask = "0.1.0"
# kspin = "0.1.0"
memory_addr = "0.4"
-page_table_entry = { version = "0.5", features = ["arm-el2"] }
-page_table_multiarch = "0.5"
-percpu = { version = "0.2.0", features = ["arm-el2"] }
+page_table_entry = { version = "0.6", features = ["arm-el2"] }
+page_table_multiarch = "0.6"
+percpu = { version = "0.2.3-preview.1", features = ["arm-el2"] }
# System dependent modules provided by ArceOS-Hypervisor.
-axvcpu = "0.1"
-axaddrspace = "0.1"
-axdevice = "0.2"
-axdevice_base = "0.1"
-axvmconfig = { version = "0.1", default-features = false }
+axvcpu = "0.2.2"
+axaddrspace = "0.1.5"
+axdevice = "0.2.1"
+axdevice_base = "=0.2.1"
+axvmconfig = { version = "0.2", default-features = false }
[target.'cfg(target_arch = "x86_64")'.dependencies]
-x86_vcpu = "0.1"
+x86_vcpu = "0.2.1"
[target.'cfg(target_arch = "riscv64")'.dependencies]
-riscv_vcpu = "0.1"
+riscv_vcpu = "0.2.1"
[target.'cfg(target_arch = "aarch64")'.dependencies]
-arm_vcpu = "0.1"
-arm_vgic = { version = "0.1", features = ["vgicv3"] }
+arm_vcpu = "0.2.1"
+arm_vgic = { version = "0.2.1", features = ["vgicv3"] }
diff --git a/src/config.rs b/src/config.rs
index afb35d1..73e23c3 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -17,18 +17,17 @@
use alloc::string::String;
use alloc::vec::Vec;
-use core::ops::Range;
use axaddrspace::GuestPhysAddr;
pub use axvmconfig::{
- AxVMCrateConfig, EmulatedDeviceConfig, PassThroughDeviceConfig, VMInterruptMode, VMType,
- VmMemConfig, VmMemMappingType,
+ AxVMCrateConfig, EmulatedDeviceConfig, PassThroughAddressConfig, PassThroughDeviceConfig,
+ VMInterruptMode, VMType, VmMemConfig, VmMemMappingType,
};
-/// A part of `AxVCpuConfig`, which represents an architecture-dependent `VCpu`.
-///
-/// The concrete type of configuration is defined in `AxArchVCpuImpl`.
+// /// A part of `AxVCpuConfig`, which represents an architecture-dependent `VCpu`.
+// ///
+// /// The concrete type of configuration is defined in `AxArchVCpuImpl`.
// #[derive(Clone, Copy, Debug, Default)]
// pub struct AxArchVCpuConfig {
// pub create_config: as AxArchVCpu>::CreateConfig,
@@ -45,7 +44,7 @@ pub struct AxVCpuConfig {
}
/// A part of `AxVMConfig`, which stores configuration attributes related to the load address of VM images.
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
pub struct VMImageConfig {
/// The load address in GPA for the kernel image.
pub kernel_load_gpa: GuestPhysAddr,
@@ -64,14 +63,15 @@ pub struct AxVMConfig {
name: String,
#[allow(dead_code)]
vm_type: VMType,
- cpu_num: usize,
- phys_cpu_ids: Option>,
- phys_cpu_sets: Option>,
- cpu_config: AxVCpuConfig,
- image_config: VMImageConfig,
- memory_regions: Vec,
+ pub(crate) phys_cpu_ls: PhysCpuList,
+ /// vCPU configuration.
+ pub cpu_config: AxVCpuConfig,
+ /// VM image configuration.
+ pub image_config: VMImageConfig,
emu_devices: Vec,
pass_through_devices: Vec,
+ excluded_devices: Vec>,
+ pass_through_addresses: Vec,
// TODO: improve interrupt passthrough
spi_list: Vec,
interrupt_mode: VMInterruptMode,
@@ -83,9 +83,11 @@ impl From for AxVMConfig {
id: cfg.base.id,
name: cfg.base.name,
vm_type: VMType::from(cfg.base.vm_type),
- cpu_num: cfg.base.cpu_num,
- phys_cpu_ids: cfg.base.phys_cpu_ids,
- phys_cpu_sets: cfg.base.phys_cpu_sets,
+ phys_cpu_ls: PhysCpuList {
+ cpu_num: cfg.base.cpu_num,
+ phys_cpu_ids: cfg.base.phys_cpu_ids,
+ phys_cpu_sets: cfg.base.phys_cpu_sets,
+ },
cpu_config: AxVCpuConfig {
bsp_entry: GuestPhysAddr::from(cfg.kernel.entry_point),
ap_entry: GuestPhysAddr::from(cfg.kernel.entry_point),
@@ -96,9 +98,11 @@ impl From for AxVMConfig {
dtb_load_gpa: cfg.kernel.dtb_load_addr.map(GuestPhysAddr::from),
ramdisk_load_gpa: cfg.kernel.ramdisk_load_addr.map(GuestPhysAddr::from),
},
- memory_regions: cfg.kernel.memory_regions,
+ // memory_regions: cfg.kernel.memory_regions,
emu_devices: cfg.devices.emu_devices,
pass_through_devices: cfg.devices.passthrough_devices,
+ excluded_devices: cfg.devices.excluded_devices,
+ pass_through_addresses: cfg.devices.passthrough_addresses,
spi_list: Vec::new(),
interrupt_mode: cfg.devices.interrupt_mode,
}
@@ -116,32 +120,6 @@ impl AxVMConfig {
self.name.clone()
}
- /// Returns vCpu id list and its corresponding pCpu affinity list, as well as its physical id.
- /// If the pCpu affinity is None, it means the vCpu will be allocated to any available pCpu randomly.
- /// if the pCPU id is not provided, the vCpu's physical id will be set as vCpu id.
- ///
- /// Returns a vector of tuples, each tuple contains:
- /// - The vCpu id.
- /// - The pCpu affinity mask, `None` if not set.
- /// - The physical id of the vCpu, equal to vCpu id if not provided.
- pub fn get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option, usize)> {
- let mut vcpu_pcpu_tuples = Vec::new();
- for vcpu_id in 0..self.cpu_num {
- vcpu_pcpu_tuples.push((vcpu_id, None, vcpu_id));
- }
- if let Some(phys_cpu_sets) = &self.phys_cpu_sets {
- for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() {
- vcpu_pcpu_tuples[vcpu_id].1 = Some(*pcpu_mask_bitmap);
- }
- }
- if let Some(phys_cpu_ids) = &self.phys_cpu_ids {
- for (vcpu_id, phys_id) in phys_cpu_ids.iter().enumerate() {
- vcpu_pcpu_tuples[vcpu_id].2 = *phys_id;
- }
- }
- vcpu_pcpu_tuples
- }
-
/// Returns configurations related to VM image load addresses.
pub fn image_config(&self) -> &VMImageConfig {
&self.image_config
@@ -159,22 +137,36 @@ impl AxVMConfig {
self.cpu_config.ap_entry
}
- /// Returns configurations related to VM memory regions.
- pub fn memory_regions(&self) -> &Vec {
- &self.memory_regions
+ /// Returns a mutable reference to the physical CPU list.
+ pub fn phys_cpu_ls_mut(&mut self) -> &mut PhysCpuList {
+ &mut self.phys_cpu_ls
}
- /// Adds a new memory region to the VM configuration.
- pub fn add_memory_region(&mut self, region: VmMemConfig) {
- self.memory_regions.push(region);
+ /// Returns the list of excluded devices.
+ pub fn excluded_devices(&self) -> &Vec> {
+ &self.excluded_devices
}
- /// Checks if the VM memory regions contain a specific range.
- pub fn contains_memory_range(&self, range: &Range) -> bool {
- self.memory_regions
- .iter()
- .any(|region| region.gpa <= range.start && region.gpa + region.size >= range.end)
+ /// Returns the list of passthrough address configurations.
+ pub fn pass_through_addresses(&self) -> &Vec {
+ &self.pass_through_addresses
}
+ // /// Returns configurations related to VM memory regions.
+ // pub fn memory_regions(&self) -> Vec {
+ // &self.memory_regions
+ // }
+
+ // /// Adds a new memory region to the VM configuration.
+ // pub fn add_memory_region(&mut self, region: VmMemConfig) {
+ // self.memory_regions.push(region);
+ // }
+
+ // /// Checks if the VM memory regions contain a specific range.
+ // pub fn contains_memory_range(&self, range: &Range) -> bool {
+ // self.memory_regions
+ // .iter()
+ // .any(|region| region.gpa <= range.start && region.gpa + region.size >= range.end)
+ // }
/// Returns configurations related to VM emulated devices.
pub fn emu_devices(&self) -> &Vec {
@@ -191,6 +183,16 @@ impl AxVMConfig {
self.pass_through_devices.push(device);
}
+ /// Removes passthrough device from the VM configuration.
+ pub fn remove_pass_through_device(&mut self, device: PassThroughDeviceConfig) {
+ self.pass_through_devices.retain(|d| d == &device);
+ }
+
+ /// Clears all passthrough devices from the VM configuration.
+ pub fn clear_pass_through_devices(&mut self) {
+ self.pass_through_devices.clear();
+ }
+
/// Adds a passthrough SPI to the VM configuration.
pub fn add_pass_through_spi(&mut self, spi: u32) {
self.spi_list.push(spi);
@@ -206,3 +208,89 @@ impl AxVMConfig {
self.interrupt_mode
}
}
+
+/// Represents the list of physical CPUs available for the VM.
+#[derive(Debug, Default, Clone)]
+pub struct PhysCpuList {
+ cpu_num: usize,
+ phys_cpu_ids: Option>,
+ phys_cpu_sets: Option>,
+}
+
+impl PhysCpuList {
+ /// Returns vCpu id list and its corresponding pCpu affinity list, as well as its physical id.
+ /// If the pCpu affinity is None, it means the vCpu will be allocated to any available pCpu randomly.
+ /// if the pCPU id is not provided, the vCpu's physical id will be set as vCpu id.
+ ///
+ /// Returns a vector of tuples, each tuple contains:
+ /// - The vCpu id.
+ /// - The pCpu affinity mask, `None` if not set.
+ /// - The physical id of the vCpu, equal to vCpu id if not provided.
+ pub fn get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option, usize)> {
+ let mut vcpu_pcpu_tuples = Vec::new();
+ #[cfg(target_arch = "riscv64")]
+ let mut pcpu_mask_flag = false;
+
+ if let Some(phys_cpu_ids) = &self.phys_cpu_ids
+ && self.cpu_num != phys_cpu_ids.len()
+ {
+ error!(
+ "ERROR!!!: cpu_num: {}, phys_cpu_ids: {:?}",
+ self.cpu_num, self.phys_cpu_ids
+ );
+ }
+
+ for vcpu_id in 0..self.cpu_num {
+ vcpu_pcpu_tuples.push((vcpu_id, None, vcpu_id));
+ }
+
+ #[cfg(target_arch = "riscv64")]
+ if let Some(phys_cpu_sets) = &self.phys_cpu_sets {
+ pcpu_mask_flag = true;
+ for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() {
+ vcpu_pcpu_tuples[vcpu_id].1 = Some(*pcpu_mask_bitmap);
+ }
+ }
+
+ #[cfg(not(target_arch = "riscv64"))]
+ if let Some(phys_cpu_sets) = &self.phys_cpu_sets {
+ for (vcpu_id, pcpu_mask_bitmap) in phys_cpu_sets.iter().enumerate() {
+ vcpu_pcpu_tuples[vcpu_id].1 = Some(*pcpu_mask_bitmap);
+ }
+ }
+
+ if let Some(phys_cpu_ids) = &self.phys_cpu_ids {
+ for (vcpu_id, phys_id) in phys_cpu_ids.iter().enumerate() {
+ vcpu_pcpu_tuples[vcpu_id].2 = *phys_id;
+ #[cfg(target_arch = "riscv64")]
+ {
+ if !pcpu_mask_flag {
+ // if don't assign pcpu mask yet, assign it manually
+ vcpu_pcpu_tuples[vcpu_id].1 = Some(1 << (*phys_id));
+ }
+ }
+ }
+ }
+ vcpu_pcpu_tuples
+ }
+
+ /// Returns the number of CPUs.
+ pub fn cpu_num(&self) -> usize {
+ self.cpu_num
+ }
+
+ /// Returns the physical CPU IDs.
+ pub fn phys_cpu_ids(&self) -> &Option> {
+ &self.phys_cpu_ids
+ }
+
+ /// Returns the physical CPU sets.
+ pub fn phys_cpu_sets(&self) -> &Option> {
+ &self.phys_cpu_sets
+ }
+
+ /// Sets the guest CPU sets.
+ pub fn set_guest_cpu_sets(&mut self, phys_cpu_sets: Vec) {
+ self.phys_cpu_sets = Some(phys_cpu_sets);
+ }
+}
diff --git a/src/hal.rs b/src/hal.rs
index 22e2496..787c074 100644
--- a/src/hal.rs
+++ b/src/hal.rs
@@ -20,14 +20,6 @@ pub trait AxVMHal: Sized {
/// The low-level **OS-dependent** helpers that must be provided for physical address management.
type PagingHandler: page_table_multiarch::PagingHandler;
- /// Allocates a memory region at the specified physical address.
- ///
- /// Returns `true` if the memory region is successfully allocated.
- fn alloc_memory_region_at(base: HostPhysAddr, size: usize) -> bool;
-
- /// Deallocates a memory region at the specified physical address.
- fn dealloc_memory_region_at(base: HostPhysAddr, size: usize);
-
/// Converts a virtual address to the corresponding physical address.
fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr;
diff --git a/src/lib.rs b/src/lib.rs
index 4ff0b60..e4e9d5c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,10 +13,6 @@
// limitations under the License.
#![no_std]
-#![feature(new_range_api)]
-// #![feature(concat_idents)]
-// #![feature(naked_functions)]
-// #![feature(const_trait_impl)]
//! This crate provides a minimal VM monitor (VMM) for running guest VMs.
//!
@@ -37,6 +33,8 @@ pub use hal::AxVMHal;
pub use vm::AxVCpuRef;
pub use vm::AxVM;
pub use vm::AxVMRef;
+pub use vm::VMMemoryRegion;
+pub use vm::VMStatus;
/// The architecture-independent per-CPU type.
pub type AxVMPerCpu = axvcpu::AxPerCpu>;
diff --git a/src/vm.rs b/src/vm.rs
index 264b1a2..12f62d9 100644
--- a/src/vm.rs
+++ b/src/vm.rs
@@ -16,30 +16,26 @@ use alloc::boxed::Box;
use alloc::format;
use alloc::sync::Arc;
use alloc::vec::Vec;
-#[cfg(target_arch = "aarch64")]
-use axvmconfig::VMInterruptMode;
-use core::sync::atomic::{AtomicBool, Ordering};
+use axaddrspace::HostVirtAddr;
+use axerrno::{AxError, AxResult, ax_err, ax_err_type};
+use core::alloc::Layout;
+use core::fmt;
use memory_addr::{align_down_4k, align_up_4k};
-
-use axerrno::{AxResult, ax_err, ax_err_type};
-use spin::Mutex;
+use spin::{Mutex, Once};
use axaddrspace::{AddrSpace, GuestPhysAddr, HostPhysAddr, MappingFlags, device::AccessWidth};
use axdevice::{AxVmDeviceConfig, AxVmDevices};
use axvcpu::{AxVCpu, AxVCpuExitReason, AxVCpuHal};
-
use cpumask::CpuMask;
-use crate::config::{AxVMConfig, VmMemMappingType};
+use crate::config::{AxVMConfig, PhysCpuList};
use crate::vcpu::AxArchVCpuImpl;
-
-#[cfg(not(target_arch = "x86_64"))]
-use crate::vcpu::AxVCpuCreateConfig;
-
use crate::{AxVMHal, has_hardware_support};
+#[cfg(target_arch = "riscv64")]
+use crate::vcpu::AxVCpuCreateConfig;
#[cfg(target_arch = "aarch64")]
-use crate::vcpu::get_sysreg_device;
+use crate::vcpu::{AxVCpuCreateConfig, get_sysreg_device};
const VM_ASPACE_BASE: usize = 0x0;
const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000;
@@ -55,8 +51,7 @@ pub type AxVCpuRef = Arc>;
pub type AxVMRef = Arc>; // we know the bound is not enforced here, we keep it for clarity
struct AxVMInnerConst {
- id: usize,
- config: AxVMConfig,
+ phys_cpu_ls: PhysCpuList,
vcpu_list: Box<[AxVCpuRef]>,
devices: AxVmDevices,
}
@@ -64,20 +59,96 @@ struct AxVMInnerConst {
unsafe impl Send for AxVMInnerConst {}
unsafe impl Sync for AxVMInnerConst {}
+/// Represents a memory region in a virtual machine.
+#[derive(Debug, Clone)]
+pub struct VMMemoryRegion {
+ /// Guest physical address.
+ pub gpa: GuestPhysAddr,
+ /// Host virtual address.
+ pub hva: HostVirtAddr,
+ /// Memory layout of the region.
+ pub layout: Layout,
+ /// Whether this region was allocated by the allocator and needs to be deallocated
+ pub needs_dealloc: bool,
+}
+
+impl VMMemoryRegion {
+ /// Returns the size of the memory region.
+ pub fn size(&self) -> usize {
+ self.layout.size()
+ }
+
+ /// Returns `true` if the guest physical address is identical to the host virtual address.
+ pub fn is_identical(&self) -> bool {
+ self.gpa.as_usize() == self.hva.as_usize()
+ }
+}
+
struct AxVMInnerMut {
// Todo: use more efficient lock.
- address_space: Mutex>,
+ address_space: AddrSpace,
+ memory_regions: Vec,
+ config: AxVMConfig,
+ vm_status: VMStatus,
_marker: core::marker::PhantomData,
}
+/// VM status enumeration representing the lifecycle states of a virtual machine
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum VMStatus {
+ /// VM is being created/loaded
+ Loading,
+ /// VM is loaded but not yet started
+ Loaded,
+ /// VM is currently running
+ Running,
+ /// VM is suspended (paused but can be resumed)
+ Suspended,
+ /// VM is in the process of shutting down
+ Stopping,
+ /// VM is stopped
+ Stopped,
+}
+
+impl VMStatus {
+ /// Get status as a string (lowercase)
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ VMStatus::Loading => "loading",
+ VMStatus::Loaded => "loaded",
+ VMStatus::Running => "running",
+ VMStatus::Suspended => "suspended",
+ VMStatus::Stopping => "stopping",
+ VMStatus::Stopped => "stopped",
+ }
+ }
+
+ /// Get status with emoji icon
+ pub fn as_str_with_icon(&self) -> &'static str {
+ match self {
+ VMStatus::Loading => "đ loading",
+ VMStatus::Loaded => "đĻ loaded",
+ VMStatus::Running => "đ running",
+ VMStatus::Suspended => "đ suspended",
+ VMStatus::Stopping => "âšī¸ stopping",
+ VMStatus::Stopped => "đ¤ stopped",
+ }
+ }
+}
+
+impl fmt::Display for VMStatus {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.as_str())
+ }
+}
+
const TEMP_MAX_VCPU_NUM: usize = 64;
/// A Virtual Machine.
pub struct AxVM {
- running: AtomicBool,
- shutting_down: AtomicBool,
- inner_const: AxVMInnerConst,
- inner_mut: AxVMInnerMut,
+ id: usize,
+ inner_const: Once>,
+ inner_mut: Mutex>,
}
impl AxVM {
@@ -85,122 +156,71 @@ impl AxVM {
/// Returns an error if the configuration is invalid.
/// The VM is not started until `boot` is called.
pub fn new(config: AxVMConfig) -> AxResult> {
- let vcpu_id_pcpu_sets = config.get_vcpu_affinities_pcpu_ids();
+ let address_space =
+ AddrSpace::new_empty(GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE)?;
- debug!(
- "id: {}, VCpuIdPCpuSets: {:#x?}",
- config.id(),
- vcpu_id_pcpu_sets
- );
+ let result = Arc::new(Self {
+ id: config.id(),
+ inner_const: Once::new(),
+ inner_mut: Mutex::new(AxVMInnerMut {
+ address_space,
+ config,
+ memory_regions: Vec::new(),
+ vm_status: VMStatus::Loading,
+ _marker: core::marker::PhantomData,
+ }),
+ });
+
+ info!("VM created: id={}", result.id());
+
+ Ok(result)
+ }
+
+ /// Returns the VM id.
+ #[inline]
+ pub fn id(&self) -> usize {
+ self.id
+ }
+
+ /// Sets up the VM before booting.
+ pub fn init(&self) -> AxResult {
+ let mut inner_mut = self.inner_mut.lock();
+
+ let dtb_addr = inner_mut.config.image_config().dtb_load_gpa;
+ let vcpu_id_pcpu_sets = inner_mut.config.phys_cpu_ls.get_vcpu_affinities_pcpu_ids();
+
+ info!("dtb_load_gpa: {:?}", dtb_addr);
+ debug!("id: {}, VCpuIdPCpuSets: {vcpu_id_pcpu_sets:#x?}", self.id());
let mut vcpu_list = Vec::with_capacity(vcpu_id_pcpu_sets.len());
for (vcpu_id, phys_cpu_set, _pcpu_id) in vcpu_id_pcpu_sets {
#[cfg(target_arch = "aarch64")]
let arch_config = AxVCpuCreateConfig {
mpidr_el1: _pcpu_id as _,
- dtb_addr: config
- .image_config()
- .dtb_load_gpa
- .unwrap_or_default()
- .as_usize(),
+ dtb_addr: dtb_addr.unwrap_or_default().as_usize(),
};
#[cfg(target_arch = "riscv64")]
let arch_config = AxVCpuCreateConfig {
hart_id: vcpu_id as _,
- dtb_addr: config
- .image_config()
- .dtb_load_gpa
- .unwrap_or(GuestPhysAddr::from_usize(0x9000_0000)),
+ dtb_addr: dtb_addr.unwrap_or_default().as_usize(),
};
- #[cfg(not(target_arch = "x86_64"))]
vcpu_list.push(Arc::new(VCpu::new(
- config.id(),
+ self.id(),
vcpu_id,
0, // Currently not used.
phys_cpu_set,
+ #[cfg(target_arch = "aarch64")]
arch_config,
- )?));
-
- #[cfg(target_arch = "x86_64")]
- vcpu_list.push(Arc::new(VCpu::new(
- config.id(),
- vcpu_id,
- 0, // Currently not used.
- phys_cpu_set,
+ #[cfg(target_arch = "riscv64")]
+ arch_config,
+ #[cfg(target_arch = "x86_64")]
(),
)?));
}
- let mut address_space =
- AddrSpace::new_empty(GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE)?;
-
- for mem_region in config.memory_regions() {
- let mapping_flags = MappingFlags::from_bits(mem_region.flags).ok_or_else(|| {
- ax_err_type!(
- InvalidInput,
- format!("Illegal flags {:?}", mem_region.flags)
- )
- })?;
-
- // Check mapping flags.
- if mapping_flags.contains(MappingFlags::DEVICE) {
- warn!(
- "Do not include DEVICE flag in memory region flags, it should be configured in pass_through_devices"
- );
- continue;
- }
-
- info!(
- "Setting up memory region: [{:#x}~{:#x}] {:?}",
- mem_region.gpa,
- mem_region.gpa + mem_region.size,
- mapping_flags
- );
-
- // Handle ram region.
- match mem_region.map_type {
- VmMemMappingType::MapIdentical => {
- if H::alloc_memory_region_at(
- HostPhysAddr::from(mem_region.gpa),
- mem_region.size,
- ) {
- } else {
- address_space.map_linear(
- GuestPhysAddr::from(mem_region.gpa),
- HostPhysAddr::from(mem_region.gpa),
- mem_region.size,
- mapping_flags,
- )?;
- warn!(
- "Failed to allocate memory region at {:#x} for VM [{}]",
- mem_region.gpa,
- config.id()
- );
- }
-
- address_space.map_linear(
- GuestPhysAddr::from(mem_region.gpa),
- HostPhysAddr::from(mem_region.gpa),
- mem_region.size,
- mapping_flags,
- )?;
- }
- VmMemMappingType::MapAlloc => {
- // Note: currently we use `map_alloc`,
- // which allocates real physical memory in units of physical page frames,
- // which may not be contiguous!!!
- address_space.map_alloc(
- GuestPhysAddr::from(mem_region.gpa),
- mem_region.size,
- mapping_flags,
- true,
- )?;
- }
- }
- }
let mut pt_dev_region = Vec::new();
- for pt_device in config.pass_through_devices() {
+ for pt_device in inner_mut.config.pass_through_devices() {
trace!(
"PT dev {:?} region: [{:#x}~{:#x}] -> [{:#x}~{:#x}]",
pt_device.name,
@@ -216,6 +236,16 @@ impl AxVM {
));
}
+ for pt_addr in inner_mut.config.pass_through_addresses() {
+ debug!(
+ "PT addr region: [{:#x}~{:#x}]",
+ pt_addr.base_gpa,
+ pt_addr.base_gpa + pt_addr.length,
+ );
+ // Align the base address and length to 4K boundaries.
+ pt_dev_region.push((align_down_4k(pt_addr.base_gpa), align_up_4k(pt_addr.length)));
+ }
+
pt_dev_region.sort_by_key(|(gpa, _)| *gpa);
// Merge overlapping regions.
@@ -237,7 +267,7 @@ impl AxVM {
});
for (gpa, len) in &pt_dev_region {
- address_space.map_linear(
+ inner_mut.address_space.map_linear(
GuestPhysAddr::from(*gpa),
HostPhysAddr::from(*gpa),
*len,
@@ -250,21 +280,20 @@ impl AxVM {
#[cfg(target_arch = "aarch64")]
let mut devices = axdevice::AxVmDevices::new(AxVmDeviceConfig {
- emu_configs: config.emu_devices().to_vec(),
+ emu_configs: inner_mut.config.emu_devices().to_vec(),
});
- #[cfg(target_arch = "aarch64")]
- let passthrough = config.interrupt_mode() == VMInterruptMode::Passthrough;
-
#[cfg(not(target_arch = "aarch64"))]
let devices = axdevice::AxVmDevices::new(AxVmDeviceConfig {
- emu_configs: config.emu_devices().to_vec(),
+ emu_configs: inner_mut.config.emu_devices().to_vec(),
});
#[cfg(target_arch = "aarch64")]
{
+ let passthrough =
+ inner_mut.config.interrupt_mode() == axvmconfig::VMInterruptMode::Passthrough;
if passthrough {
- let spis = config.pass_through_spis();
- let cpu_id = config.id() - 1; // FIXME: get the real CPU id.
+ let spis = inner_mut.config.pass_through_spis();
+ let cpu_id = self.id() - 1; // FIXME: get the real CPU id.
let mut gicd_found = false;
for device in devices.iter_mmio_dev() {
@@ -277,7 +306,7 @@ impl AxVM {
gicd.assign_irq(*spi + 32, cpu_id, (0, 0, 0, cpu_id as _))
}
- Ok(())
+ AxResult::Ok(())
},
) {
result?;
@@ -300,58 +329,55 @@ impl AxVM {
}
}
- let result = Arc::new(Self {
- running: AtomicBool::new(false),
- shutting_down: AtomicBool::new(false),
- inner_const: AxVMInnerConst {
- id: config.id(),
- config,
- vcpu_list: vcpu_list.into_boxed_slice(),
- devices,
- },
- inner_mut: AxVMInnerMut {
- address_space: Mutex::new(address_space),
- _marker: core::marker::PhantomData,
- },
+ self.inner_const.call_once(|| AxVMInnerConst {
+ phys_cpu_ls: inner_mut.config.phys_cpu_ls.clone(),
+ vcpu_list: vcpu_list.into_boxed_slice(),
+ devices,
});
- info!("VM created: id={}", result.id());
-
// Setup VCpus.
- #[cfg(target_arch = "aarch64")]
- for vcpu in result.vcpu_list() {
- let setup_config = crate::vcpu::AxVCpuSetupConfig {
- passthrough_interrupt: passthrough,
- passthrough_timer: passthrough,
+ for vcpu in self.vcpu_list() {
+ #[cfg(target_arch = "aarch64")]
+ let setup_config = {
+ let passthrough =
+ inner_mut.config.interrupt_mode() == axvmconfig::VMInterruptMode::Passthrough;
+ crate::vcpu::AxVCpuSetupConfig {
+ passthrough_interrupt: passthrough,
+ passthrough_timer: passthrough,
+ }
};
let entry = if vcpu.id() == 0 {
- result.inner_const.config.bsp_entry()
+ inner_mut.config.bsp_entry()
} else {
- result.inner_const.config.ap_entry()
+ inner_mut.config.ap_entry()
};
- vcpu.setup(entry, result.ept_root(), setup_config)?;
- }
- #[cfg(not(target_arch = "aarch64"))]
- for vcpu in result.vcpu_list() {
- let entry = if vcpu.id() == 0 {
- result.inner_const.config.bsp_entry()
- } else {
- result.inner_const.config.ap_entry()
- };
- vcpu.setup(entry, result.ept_root(), ())?;
- }
+ debug!("Setting up vCPU[{}] entry at {:#x}", vcpu.id(), entry);
- info!("VM setup: id={}", result.id());
+ vcpu.setup(
+ entry,
+ inner_mut.address_space.page_table_root(),
+ #[cfg(target_arch = "aarch64")]
+ setup_config,
+ #[cfg(not(target_arch = "aarch64"))]
+ (),
+ )?;
+ }
+ info!("VM setup: id={}", self.id());
+ Ok(())
+ }
- Ok(result)
+ /// Sets the VM status.
+ pub fn set_vm_status(&self, status: VMStatus) {
+ let mut inner_mut = self.inner_mut.lock();
+ inner_mut.vm_status = status;
}
- /// Returns the VM id.
- #[inline]
- pub const fn id(&self) -> usize {
- self.inner_const.id
+ /// Returns the current VM status.
+ pub fn vm_status(&self) -> VMStatus {
+ let inner_mut = self.inner_mut.lock();
+ inner_mut.vm_status
}
/// Retrieves the vCPU corresponding to the given vcpu_id for the VM.
@@ -363,19 +389,34 @@ impl AxVM {
/// Returns the number of vCPUs corresponding to the VM.
#[inline]
- pub const fn vcpu_num(&self) -> usize {
- self.inner_const.vcpu_list.len()
+ pub fn vcpu_num(&self) -> usize {
+ self.inner_const().vcpu_list.len()
+ }
+
+ fn inner_const(&self) -> &AxVMInnerConst {
+ self.inner_const
+ .get()
+ .expect("VM inner_const not initialized")
}
/// Returns a reference to the list of vCPUs corresponding to the VM.
#[inline]
pub fn vcpu_list(&self) -> &[AxVCpuRef] {
- &self.inner_const.vcpu_list
+ &self.inner_const().vcpu_list
}
/// Returns the base address of the two-stage address translation page table for the VM.
pub fn ept_root(&self) -> HostPhysAddr {
- self.inner_mut.address_space.lock().page_table_root()
+ self.inner_mut.lock().address_space.page_table_root()
+ }
+
+ /// Returns to the VM's configuration.
+ pub fn with_config(&self, f: F) -> R
+ where
+ F: FnOnce(&mut AxVMConfig) -> R,
+ {
+ let mut g = self.inner_mut.lock();
+ f(&mut g.config)
}
/// Returns guest VM image load region in `Vec<&'static mut [u8]>`,
@@ -391,19 +432,15 @@ impl AxVM {
image_load_gpa: GuestPhysAddr,
image_size: usize,
) -> AxResult> {
- let addr_space = self.inner_mut.address_space.lock();
- let image_load_hva = addr_space
+ let g = self.inner_mut.lock();
+ let image_load_hva = g
+ .address_space
.translated_byte_buffer(image_load_gpa, image_size)
.expect("Failed to translate kernel image load address");
Ok(image_load_hva)
}
- /// Returns if the VM is running.
- pub fn running(&self) -> bool {
- self.running.load(Ordering::Relaxed)
- }
-
- /// Boots the VM by setting the running flag as true.
+ /// Boots the VM by transitioning to Running state.
pub fn boot(&self) -> AxResult {
if !has_hardware_support() {
ax_err!(Unsupported, "Hardware does not support virtualization")
@@ -411,29 +448,44 @@ impl AxVM {
ax_err!(BadState, format!("VM[{}] is already running", self.id()))
} else {
info!("Booting VM[{}]", self.id());
- self.running.store(true, Ordering::Relaxed);
+ self.set_vm_status(VMStatus::Running);
Ok(())
}
}
- /// Returns if the VM is shutting down.
- pub fn shutting_down(&self) -> bool {
- self.shutting_down.load(Ordering::Relaxed)
+ /// Returns if the VM is running.
+ pub fn running(&self) -> bool {
+ self.vm_status() == VMStatus::Running
}
- /// Shuts down the VM by setting the shutting_down flag as true.
+ /// Returns if the VM is shutting down (in Stopping state).
+ pub fn stopping(&self) -> bool {
+ self.vm_status() == VMStatus::Stopping
+ }
+
+ /// Returns if the VM is suspended.
+ pub fn suspending(&self) -> bool {
+ self.vm_status() == VMStatus::Suspended
+ }
+
+ /// Returns if the VM is stopped.
+ pub fn stopped(&self) -> bool {
+ self.vm_status() == VMStatus::Stopped
+ }
+
+ /// Shuts down the VM by transitioning to Stopping state.
///
+ /// This method sets the VM status to Stopping, which signals all vCPUs to exit.
/// Currently, the "re-init" process of the VM is not implemented. Therefore, a VM can only be
/// booted once. And after the VM is shut down, it cannot be booted again.
pub fn shutdown(&self) -> AxResult {
- if self.shutting_down() {
- ax_err!(
- BadState,
- format!("VM[{}] is already shutting down", self.id())
- )
+ if self.stopping() {
+ ax_err!(BadState, format!("VM[{}] is already stopping", self.id()))
+ } else if self.stopped() {
+ ax_err!(BadState, format!("VM[{}] is already stopped", self.id()))
} else {
info!("Shutting down VM[{}]", self.id());
- self.shutting_down.store(true, Ordering::Relaxed);
+ self.set_vm_status(VMStatus::Stopping);
Ok(())
}
}
@@ -443,7 +495,7 @@ impl AxVM {
/// Returns this VM's emulated devices.
pub fn get_devices(&self) -> &AxVmDevices {
- &self.inner_const.devices
+ &self.inner_const().devices
}
/// Run a vCPU according to the given vcpu_id.
@@ -483,8 +535,12 @@ impl AxVM {
}
AxVCpuExitReason::IoRead { port, width } => {
let val = self.get_devices().handle_port_read(*port, *width)?;
+ #[cfg(not(target_arch = "riscv64"))]
vcpu.set_gpr(0, val); // The target is always eax/ax/al, todo: handle access_width correctly
+ #[cfg(target_arch = "riscv64")]
+ vcpu.set_gpr(riscv_vcpu::GprIndex::A0 as usize, val);
+
true
}
AxVCpuExitReason::IoWrite { port, width, data } => {
@@ -512,8 +568,8 @@ impl AxVM {
}
AxVCpuExitReason::NestedPageFault { addr, access_flags } => self
.inner_mut
- .address_space
.lock()
+ .address_space
.handle_page_fault(*addr, *access_flags),
_ => false,
};
@@ -549,11 +605,25 @@ impl AxVM {
Ok(())
}
- /// Returns a reference to the VM's configuration.
- pub fn config(&self) -> &AxVMConfig {
- &self.inner_const.config
+ /// Returns vCpu id list and its corresponding pCpu affinity list, as well as its physical id.
+ /// If the pCpu affinity is None, it means the vCpu will be allocated to any available pCpu randomly.
+ /// if the pCPU id is not provided, the vCpu's physical id will be set as vCpu id.
+ ///
+ /// Returns a vector of tuples, each tuple contains:
+ /// - The vCpu id.
+ /// - The pCpu affinity mask, `None` if not set.
+ /// - The physical id of the vCpu, equal to vCpu id if not provided.
+ pub fn get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option, usize)> {
+ self.inner_const()
+ .phys_cpu_ls
+ .get_vcpu_affinities_pcpu_ids()
}
+ // /// Returns a reference to the VM's configuration.
+ // pub fn config(&self) -> &AxVMConfig {
+ // &self.inner_const.config
+ // }
+
/// Maps a region of host physical memory to guest physical memory.
pub fn map_region(
&self,
@@ -561,16 +631,18 @@ impl AxVM {
hpa: HostPhysAddr,
size: usize,
flags: MappingFlags,
- ) -> AxResult<()> {
+ ) -> AxResult {
self.inner_mut
- .address_space
.lock()
- .map_linear(gpa, hpa, size, flags)
+ .address_space
+ .map_linear(gpa, hpa, size, flags)?;
+ Ok(())
}
/// Unmaps a region of guest physical memory.
- pub fn unmap_region(&self, gpa: GuestPhysAddr, size: usize) -> AxResult<()> {
- self.inner_mut.address_space.lock().unmap(gpa, size)
+ pub fn unmap_region(&self, gpa: GuestPhysAddr, size: usize) -> AxResult {
+ self.inner_mut.lock().address_space.unmap(gpa, size)?;
+ Ok(())
}
/// Reads an object of type `T` from the guest physical address.
@@ -585,8 +657,8 @@ impl AxVM {
return ax_err!(InvalidInput, "Unaligned guest physical address");
}
- let addr_space = self.inner_mut.address_space.lock();
- match addr_space.translated_byte_buffer(gpa_ptr, size) {
+ let g = self.inner_mut.lock();
+ match g.address_space.translated_byte_buffer(gpa_ptr, size) {
Some(buffers) => {
let mut data_bytes = Vec::with_capacity(size);
for chunk in buffers {
@@ -618,9 +690,12 @@ impl AxVM {
/// Writes an object of type `T` to the guest physical address.
pub fn write_to_guest_of(&self, gpa_ptr: GuestPhysAddr, data: &T) -> AxResult {
- let addr_space = self.inner_mut.address_space.lock();
-
- match addr_space.translated_byte_buffer(gpa_ptr, core::mem::size_of::()) {
+ match self
+ .inner_mut
+ .lock()
+ .address_space
+ .translated_byte_buffer(gpa_ptr, core::mem::size_of::())
+ {
Some(mut buffer) => {
let bytes = unsafe {
core::slice::from_raw_parts(
@@ -649,7 +724,7 @@ impl AxVM {
pub fn alloc_ivc_channel(&self, expected_size: usize) -> AxResult<(GuestPhysAddr, usize)> {
// Ensure the expected size is aligned to 4K.
let size = align_up_4k(expected_size);
- let gpa = self.inner_const.devices.alloc_ivc_channel(size)?;
+ let gpa = self.inner_const().devices.alloc_ivc_channel(size)?;
Ok((gpa, size))
}
@@ -660,6 +735,201 @@ impl AxVM {
/// ## Returns
/// * `AxResult<()>` - An empty result indicating success or failure.
pub fn release_ivc_channel(&self, gpa: GuestPhysAddr, size: usize) -> AxResult {
- self.inner_const.devices.release_ivc_channel(gpa, size)
+ self.inner_const().devices.release_ivc_channel(gpa, size)?;
+ Ok(())
+ }
+
+ /// Allocates a new memory region for the VM.
+ pub fn alloc_memory_region(
+ &self,
+ layout: Layout,
+ gpa: Option,
+ ) -> AxResult<&[u8]> {
+ assert!(
+ layout.size() > 0,
+ "Cannot allocate zero-sized memory region"
+ );
+
+ let hva = unsafe { alloc::alloc::alloc_zeroed(layout) };
+ if hva.is_null() {
+ return Err(AxError::NoMemory);
+ }
+ let s = unsafe { core::slice::from_raw_parts_mut(hva, layout.size()) };
+ let hva = HostVirtAddr::from_mut_ptr_of(hva);
+
+ let hpa = H::virt_to_phys(hva);
+
+ let gpa = gpa.unwrap_or_else(|| hpa.as_usize().into());
+
+ let mut g = self.inner_mut.lock();
+ g.address_space.map_linear(
+ gpa,
+ hpa,
+ layout.size(),
+ MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE | MappingFlags::USER,
+ )?;
+ g.memory_regions.push(VMMemoryRegion {
+ gpa,
+ hva,
+ layout,
+ needs_dealloc: true, // This region was allocated and needs to be freed
+ });
+
+ Ok(s)
+ }
+
+ /// Returns a list of all memory regions in the VM.
+ pub fn memory_regions(&self) -> Vec {
+ self.inner_mut.lock().memory_regions.clone()
+ }
+
+ /// Maps a reserved memory region for the VM.
+ pub fn map_reserved_memory_region(
+ &self,
+ layout: Layout,
+ gpa: Option,
+ ) -> AxResult<&[u8]> {
+ assert!(
+ layout.size() > 0,
+ "Cannot allocate zero-sized memory region"
+ );
+ let mut g = self.inner_mut.lock();
+ g.address_space.map_linear(
+ gpa.unwrap(),
+ gpa.unwrap().as_usize().into(),
+ layout.size(),
+ MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE | MappingFlags::USER,
+ )?;
+ let hva = gpa.unwrap().as_usize().into();
+ let tem_hva = gpa.unwrap().as_usize() as *mut u8;
+ let s = unsafe { core::slice::from_raw_parts_mut(tem_hva, layout.size()) };
+ let gpa = gpa.unwrap();
+ g.memory_regions.push(VMMemoryRegion {
+ gpa,
+ hva,
+ layout,
+ needs_dealloc: false, // This is a reserved region, not allocated
+ });
+ Ok(s)
+ }
+
+ /// Cleanup resources for the VM before drop.
+ /// This is called internally by the Drop implementation.
+ fn cleanup_resources(&self) {
+ info!("Cleaning up VM[{}] resources...", self.id());
+
+ // 1. Ensure the VM is in Stopping or Stopped state
+ let current_status = self.vm_status();
+ if !matches!(current_status, VMStatus::Stopping | VMStatus::Stopped) {
+ warn!(
+ "VM[{}] is being dropped without explicit shutdown (status: {:?}), marking as stopping",
+ self.id(),
+ current_status
+ );
+ self.set_vm_status(VMStatus::Stopping);
+ }
+
+ let mut inner_mut = self.inner_mut.lock();
+
+ // First, collect all memory regions to clean up
+ // We need to clone the regions to avoid borrowing issues
+ let regions_to_cleanup: Vec = inner_mut.memory_regions.clone();
+
+ // Unmap all memory regions from the address space
+ // This must be done BEFORE deallocating memory to avoid use-after-free
+ for region in ®ions_to_cleanup {
+ debug!(
+ "VM[{}] unmapping memory region: GPA={:#x}, size={:#x}",
+ self.id(),
+ region.gpa.as_usize(),
+ region.size()
+ );
+ // Unmap the region from guest physical address space
+ if let Err(e) = inner_mut.address_space.unmap(region.gpa, region.size()) {
+ warn!(
+ "VM[{}] failed to unmap region at GPA={:#x}: {:?}",
+ self.id(),
+ region.gpa.as_usize(),
+ e
+ );
+ }
+ }
+
+ // Now it's safe to deallocate the memory
+ for region in ®ions_to_cleanup {
+ // Only deallocate memory regions that were allocated by the allocator
+ if region.needs_dealloc {
+ debug!(
+ "VM[{}] deallocating memory region: HVA={:#x}, size={:#x}",
+ self.id(),
+ region.hva.as_usize(),
+ region.size()
+ );
+ unsafe {
+ alloc::alloc::dealloc(region.hva.as_mut_ptr(), region.layout);
+ }
+ } else {
+ debug!(
+ "VM[{}] skipping dealloc for reserved memory region: GPA={:#x}, HVA={:#x}, size={:#x}",
+ self.id(),
+ region.gpa.as_usize(),
+ region.hva.as_usize(),
+ region.size()
+ );
+ }
+ }
+ inner_mut.memory_regions.clear();
+
+ // Clear remaining address space mappings
+ // This includes:
+ // - Passthrough device MMIO mappings
+ // - Emulated device MMIO mappings
+ // - Reserved memory mappings
+ // - All other page table entries
+ debug!(
+ "VM[{}] clearing remaining address space mappings",
+ self.id()
+ );
+ inner_mut.address_space.clear();
+
+ // Release the lock before accessing inner_const
+ drop(inner_mut);
+
+ // Device cleanup
+ // Although devices will be automatically dropped when inner_const is dropped,
+ // we should perform explicit cleanup if devices hold resources like:
+ // - Hardware interrupt registrations
+ // - DMA mappings
+ // - Background threads or timers
+ if let Some(inner_const) = self.inner_const.get() {
+ debug!(
+ "VM[{}] devices cleanup: {} MMIO devices, {} SysReg devices",
+ self.id(),
+ inner_const.devices.iter_mmio_dev().count(),
+ inner_const.devices.iter_sys_reg_dev().count()
+ );
+
+ // TODO: Add device-specific cleanup if needed
+ // For example:
+ // - Stop device background tasks
+ // - Unregister interrupts
+ // - Release device-specific resources
+
+ // Note: Device Arc references will be dropped automatically when
+ // inner_const is dropped at the end of AxVM's drop
+ }
+
+ info!("VM[{}] resources cleanup completed", self.id());
+ }
+}
+
+impl Drop for AxVM {
+ fn drop(&mut self) {
+ info!("Dropping VM[{}]", self.id());
+
+ // Clean up all allocated resources
+ self.cleanup_resources();
+
+ info!("VM[{}] dropped", self.id());
}
}