From 0e92e0096ee52750bd3d86e8029c52bd4bfe17d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 14 Aug 2025 15:50:10 +0800 Subject: [PATCH 01/74] refactor: deps git to crates-io --- Cargo.toml | 3 ++- src/vm.rs | 12 +++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c441880..9803b6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,5 +36,6 @@ x86_vcpu = "0.1" riscv_vcpu = "0.1" [target.'cfg(target_arch = "aarch64")'.dependencies] -arm_vcpu = "0.1" +arm_vcpu = { git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next-dev" } arm_vgic = { version = "0.1", features = ["vgicv3"] } + diff --git a/src/vm.rs b/src/vm.rs index 9f776b4..7f37377 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -11,7 +11,7 @@ use spin::Mutex; use axaddrspace::{AddrSpace, GuestPhysAddr, HostPhysAddr, MappingFlags, device::AccessWidth}; use axdevice::{AxVmDeviceConfig, AxVmDevices}; -use axvcpu::{AxArchVCpu, AxVCpu, AxVCpuExitReason, AxVCpuHal}; +use axvcpu::{AxVCpu, AxVCpuExitReason, AxVCpuHal}; use cpumask::CpuMask; use crate::config::{AxVMConfig, VmMemMappingType}; @@ -295,7 +295,7 @@ impl AxVM { } #[cfg(not(target_arch = "aarch64"))] { - as AxArchVCpu>::SetupConfig::default() + as axvcpu::AxArchVCpu>::SetupConfig::default() } }; @@ -435,15 +435,13 @@ impl AxVM { reg_width: _, signed_ext: _, } => { - let val = self - .get_devices() - .handle_mmio_read(*addr, (*width).into())?; + let val = self.get_devices().handle_mmio_read(*addr, *width)?; vcpu.set_gpr(*reg, val); true } AxVCpuExitReason::MmioWrite { addr, width, data } => { self.get_devices() - .handle_mmio_write(*addr, (*width).into(), *data as usize)?; + .handle_mmio_write(*addr, *width, *data as usize)?; true } AxVCpuExitReason::IoRead { port, width } => { @@ -591,7 +589,7 @@ impl AxVM { ) }; let mut copied_bytes = 0; - for (_i, chunk) in buffer.iter_mut().enumerate() { + for chunk in buffer.iter_mut() { let end = copied_bytes + chunk.len(); chunk.copy_from_slice(&bytes[copied_bytes..end]); copied_bytes += chunk.len(); From 40051ba81b61818f54f19a6b298cfc214d597a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 9 Sep 2025 11:26:26 +0800 Subject: [PATCH 02/74] fix: feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9803b6d..a755a15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [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" From d419798b928236044ab9e8fa757ac3201708417c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 10 Sep 2025 16:12:32 +0800 Subject: [PATCH 03/74] refactor: support guest dyn entry --- src/config.rs | 129 +++++++++++-------- src/hal.rs | 8 -- src/lib.rs | 1 + src/vm.rs | 344 +++++++++++++++++++++++++++----------------------- 4 files changed, 263 insertions(+), 219 deletions(-) diff --git a/src/config.rs b/src/config.rs index 841c8f5..68d5125 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,6 @@ use alloc::string::String; use alloc::vec::Vec; -use core::ops::Range; use axaddrspace::GuestPhysAddr; @@ -12,9 +11,9 @@ pub use axvmconfig::{ 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, @@ -51,12 +50,9 @@ 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, + pub cpu_config: AxVCpuConfig, + pub image_config: VMImageConfig, emu_devices: Vec, pass_through_devices: Vec, // TODO: improve interrupt passthrough @@ -70,9 +66,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), @@ -83,7 +81,7 @@ 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, spi_list: Vec::new(), @@ -103,32 +101,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 @@ -146,22 +118,22 @@ impl AxVMConfig { self.cpu_config.ap_entry } - /// Returns configurations related to VM memory regions. - pub fn memory_regions(&self) -> &Vec { - &self.memory_regions - } + // /// 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); - } + // /// 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) - } + // /// 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 { @@ -193,3 +165,52 @@ impl AxVMConfig { self.interrupt_mode } } + +#[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(); + 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 + } + + pub fn cpu_num(&self) -> usize { + self.cpu_num + } + + pub fn phys_cpu_ids(&self) -> &Option> { + &self.phys_cpu_ids + } + + pub fn phys_cpu_sets(&self) -> &Option> { + &self.phys_cpu_sets + } +} diff --git a/src/hal.rs b/src/hal.rs index 94b6c11..d1ab67a 100644 --- a/src/hal.rs +++ b/src/hal.rs @@ -6,14 +6,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 5c1a622..30d70ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ pub use hal::AxVMHal; pub use vm::AxVCpuRef; pub use vm::AxVM; pub use vm::AxVMRef; +pub use vm::VMMemoryRegion; /// The architecture-independent per-CPU type. pub type AxVMPerCpu = axvcpu::AxPerCpu>; diff --git a/src/vm.rs b/src/vm.rs index 7f37377..f1b66bf 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -2,19 +2,19 @@ use alloc::boxed::Box; use alloc::format; use alloc::sync::Arc; use alloc::vec::Vec; -use axvmconfig::VMInterruptMode; +use axaddrspace::HostVirtAddr; +use axerrno::{AxError, AxResult, ax_err, ax_err_type}; +use core::alloc::Layout; use core::sync::atomic::{AtomicBool, Ordering}; 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, AxVCpuCreateConfig}; use crate::{AxVMHal, has_hardware_support}; @@ -35,8 +35,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, } @@ -44,9 +43,28 @@ struct AxVMInnerConst { unsafe impl Send for AxVMInnerConst {} unsafe impl Sync for AxVMInnerConst {} +#[derive(Debug, Clone)] +pub struct VMMemoryRegion { + pub gpa: GuestPhysAddr, + pub hva: HostVirtAddr, + pub layout: Layout, +} + +impl VMMemoryRegion { + pub fn size(&self) -> usize { + self.layout.size() + } + + 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, _marker: core::marker::PhantomData, } @@ -54,10 +72,11 @@ const TEMP_MAX_VCPU_NUM: usize = 64; /// A Virtual Machine. pub struct AxVM { + id: usize, running: AtomicBool, shutting_down: AtomicBool, - inner_const: AxVMInnerConst, - inner_mut: AxVMInnerMut, + inner_const: Once>, + inner_mut: Mutex>, } impl AxVM { @@ -65,114 +84,68 @@ 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(), + running: AtomicBool::new(false), + shutting_down: AtomicBool::new(false), + inner_const: Once::new(), + inner_mut: Mutex::new(AxVMInnerMut { + address_space, + config, + memory_regions: Vec::new(), + _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(); + + 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(target_arch = "x86_64")] let arch_config = AxVCpuCreateConfig::default(); vcpu_list.push(Arc::new(VCpu::new( - config.id(), + self.id(), vcpu_id, 0, // Currently not used. phys_cpu_set, arch_config, )?)); } - 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, @@ -209,7 +182,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, @@ -221,16 +194,16 @@ impl AxVM { } let mut devices = axdevice::AxVmDevices::new(AxVmDeviceConfig { - emu_configs: config.emu_devices().to_vec(), + emu_configs: inner_mut.config.emu_devices().to_vec(), }); - let passthrough = config.interrupt_mode() == VMInterruptMode::Passthrough; - #[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() { @@ -266,55 +239,42 @@ 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. - for vcpu in result.vcpu_list() { + for vcpu in self.vcpu_list() { + #[cfg(target_arch = "aarch64")] let setup_config = { - #[cfg(target_arch = "aarch64")] - { - crate::vcpu::AxVCpuSetupConfig { - passthrough_interrupt: passthrough, - passthrough_timer: passthrough, - } - } - #[cfg(not(target_arch = "aarch64"))] - { - as axvcpu::AxArchVCpu>::SetupConfig::default() + let passthrough = + inner_mut.config.interrupt_mode() == axvmconfig::VMInterruptMode::Passthrough; + crate::vcpu::AxVCpuSetupConfig { + passthrough_interrupt: passthrough, + passthrough_timer: passthrough, } }; + #[cfg(not(target_arch = "aarch64"))] + let setup_config = as axvcpu::AxArchVCpu>::SetupConfig::default(); 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)?; - } - info!("VM setup: id={}", result.id()); - Ok(result) - } + debug!("Setting up vCPU[{}] entry at {:#x}", vcpu.id(), entry); - /// Returns the VM id. - #[inline] - pub const fn id(&self) -> usize { - self.inner_const.id + vcpu.setup( + entry, + inner_mut.address_space.page_table_root(), + setup_config, + )?; + } + info!("VM setup: id={}", self.id()); + Ok(()) } /// Retrieves the vCPU corresponding to the given vcpu_id for the VM. @@ -326,19 +286,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]>`, @@ -354,8 +329,9 @@ 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) @@ -406,7 +382,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. @@ -475,8 +451,8 @@ impl AxVM { } AxVCpuExitReason::NestedPageFault { addr, access_flags } => self .inner_mut - .address_space .lock() + .address_space .handle_page_fault(*addr, *access_flags), _ => false, }; @@ -512,11 +488,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, @@ -526,14 +516,14 @@ impl AxVM { flags: MappingFlags, ) -> AxResult<()> { self.inner_mut - .address_space .lock() + .address_space .map_linear(gpa, hpa, size, flags) } /// 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) + self.inner_mut.lock().address_space.unmap(gpa, size) } /// Reads an object of type `T` from the guest physical address. @@ -545,8 +535,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 { @@ -578,9 +568,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( @@ -609,7 +602,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)) } @@ -620,6 +613,43 @@ 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) + } + + 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 }); + + Ok(s) + } + + pub fn memory_regions(&self) -> Vec { + self.inner_mut.lock().memory_regions.clone() } } From ecdc6b61d40f46d5f8e8252d6cb996086df9e473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 10 Sep 2025 16:27:32 +0800 Subject: [PATCH 04/74] refactor: add Clone trait to VMImageConfig --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 68d5125..7ce8c6e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,7 +31,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, From b4f561f568536078b02a58572cae5f442102782c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 12 Sep 2025 09:56:07 +0800 Subject: [PATCH 05/74] fix: update arm_vcpu branch to 'next' --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a755a15..b7d6b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,6 @@ x86_vcpu = "0.1" riscv_vcpu = "0.1" [target.'cfg(target_arch = "aarch64")'.dependencies] -arm_vcpu = { git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next-dev" } +arm_vcpu = { git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next" } arm_vgic = { version = "0.1", features = ["vgicv3"] } From c10b3c8ab30d5eedec9618725bb5974b4e298d4e Mon Sep 17 00:00:00 2001 From: szy Date: Thu, 25 Sep 2025 13:20:51 +0800 Subject: [PATCH 06/74] add fdt support and add some AxVMConfig impl --- src/config.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/config.rs b/src/config.rs index 7ce8c6e..58fdd5d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,6 +55,7 @@ pub struct AxVMConfig { pub image_config: VMImageConfig, emu_devices: Vec, pass_through_devices: Vec, + excluded_devices: Vec>, // TODO: improve interrupt passthrough spi_list: Vec, interrupt_mode: VMInterruptMode, @@ -84,6 +85,7 @@ impl From for AxVMConfig { // 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, spi_list: Vec::new(), interrupt_mode: cfg.devices.interrupt_mode, } @@ -118,6 +120,13 @@ impl AxVMConfig { self.cpu_config.ap_entry } + pub fn phys_cpu_ls_mut(&mut self) -> &mut PhysCpuList { + &mut self.phys_cpu_ls + } + + pub fn excluded_devices(&self) -> &Vec> { + &self.excluded_devices + } // /// Returns configurations related to VM memory regions. // pub fn memory_regions(&self) -> Vec { // &self.memory_regions @@ -150,6 +159,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); @@ -184,6 +203,11 @@ impl PhysCpuList { /// - 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(); + + if self.cpu_num != self.phys_cpu_ids.as_ref().unwrap().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)); } @@ -213,4 +237,8 @@ impl PhysCpuList { pub fn phys_cpu_sets(&self) -> &Option> { &self.phys_cpu_sets } + + pub fn set_guest_cpu_sets(&mut self, phys_cpu_sets: Vec) { + self.phys_cpu_sets = Some(phys_cpu_sets); + } } From d0b1e63004d6a6b3caf47abe0f3b7020ffbacb72 Mon Sep 17 00:00:00 2001 From: szy Date: Thu, 25 Sep 2025 13:31:06 +0800 Subject: [PATCH 07/74] fix fmt bug --- src/config.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 58fdd5d..8f4dfb2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -204,8 +204,11 @@ impl PhysCpuList { pub fn get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option, usize)> { let mut vcpu_pcpu_tuples = Vec::new(); - if self.cpu_num != self.phys_cpu_ids.as_ref().unwrap().len() { - error!("ERROR!!!: cpu_num: {}, phys_cpu_ids: {:?}", self.cpu_num, self.phys_cpu_ids); + if self.cpu_num != self.phys_cpu_ids.as_ref().unwrap().len() { + error!( + "ERROR!!!: cpu_num: {}, phys_cpu_ids: {:?}", + self.cpu_num, self.phys_cpu_ids + ); } for vcpu_id in 0..self.cpu_num { From 62dc1461bc8dff5d22264962da4d2f86ded915f8 Mon Sep 17 00:00:00 2001 From: szy Date: Mon, 29 Sep 2025 14:02:23 +0800 Subject: [PATCH 08/74] fix bug: when no phys_cpu_ids panic --- src/config.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8f4dfb2..2bd1289 100644 --- a/src/config.rs +++ b/src/config.rs @@ -204,11 +204,13 @@ impl PhysCpuList { pub fn get_vcpu_affinities_pcpu_ids(&self) -> Vec<(usize, Option, usize)> { let mut vcpu_pcpu_tuples = Vec::new(); - if self.cpu_num != self.phys_cpu_ids.as_ref().unwrap().len() { - error!( - "ERROR!!!: cpu_num: {}, phys_cpu_ids: {:?}", - self.cpu_num, self.phys_cpu_ids - ); + if let Some(phys_cpu_ids) = &self.phys_cpu_ids { + if 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 { From a5b6c0319673cd0eb037ed67449511d321d66aa3 Mon Sep 17 00:00:00 2001 From: bhxh <32200913+buhenxihuan@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:44:50 +0800 Subject: [PATCH 09/74] add function map_reserved_memory_region (#28) * add function map_reserved_memory_region --- src/vm.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/vm.rs b/src/vm.rs index f1b66bf..7d0661e 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -652,4 +652,28 @@ impl AxVM { pub fn memory_regions(&self) -> Vec { self.inner_mut.lock().memory_regions.clone() } + + 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 }); + Ok(s) + } } From 22437d2b8e853bfd576f77f00308380463bd2710 Mon Sep 17 00:00:00 2001 From: szy <673586548@qq.com> Date: Thu, 6 Nov 2025 14:49:15 +0800 Subject: [PATCH 10/74] add passthrough address (#29) * add pass_through address --------- Co-authored-by: szy --- Cargo.toml | 3 +++ src/config.rs | 10 ++++++++-- src/vm.rs | 10 ++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7d6b50..05acb49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,6 @@ riscv_vcpu = "0.1" arm_vcpu = { git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next" } arm_vgic = { version = "0.1", features = ["vgicv3"] } +[patch.crates-io] +axvmconfig = { git = "https://github.com/arceos-hypervisor/axvmconfig.git", branch = "next" } +axvcpu = {git = "https://github.com/arceos-hypervisor/axvcpu.git", branch = "next"} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 2bd1289..cda4117 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,8 +7,8 @@ use alloc::vec::Vec; 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`. @@ -56,6 +56,7 @@ pub struct AxVMConfig { emu_devices: Vec, pass_through_devices: Vec, excluded_devices: Vec>, + pass_through_addresses: Vec, // TODO: improve interrupt passthrough spi_list: Vec, interrupt_mode: VMInterruptMode, @@ -86,6 +87,7 @@ impl From for AxVMConfig { 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, } @@ -127,6 +129,10 @@ impl AxVMConfig { pub fn excluded_devices(&self) -> &Vec> { &self.excluded_devices } + + 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 diff --git a/src/vm.rs b/src/vm.rs index 7d0661e..4a82cbf 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -161,6 +161,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. From 2e1fb68c9cebf9a372aeaeb698bb724f1d752567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 11 Nov 2025 17:11:35 +0800 Subject: [PATCH 11/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20AArch64=20=E5=92=8C?= =?UTF-8?q?=20x86=5F64=20=E6=9E=B6=E6=9E=84=E6=94=AF=E6=8C=81=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20Vm=20=E7=BB=93=E6=9E=84=E5=8F=8A=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=93=8D=E4=BD=9C=EF=BC=8C=E6=96=B0=E5=A2=9E=20VmId?= =?UTF-8?q?=20=E5=92=8C=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 + src/arch/aarch64/mod.rs | 87 +++++++++++++++++++++++++++++++++++++++++ src/arch/x86_64/mod.rs | 0 src/fdt/mod.rs | 0 src/lib.rs | 6 +++ src/vm2.rs | 36 +++++++++++++++++ 6 files changed, 131 insertions(+) create mode 100644 src/arch/aarch64/mod.rs create mode 100644 src/arch/x86_64/mod.rs create mode 100644 src/fdt/mod.rs create mode 100644 src/vm2.rs diff --git a/Cargo.toml b/Cargo.toml index 05acb49..33c8526 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ vmx = [] log = "0.4" cfg-if = "1.0" spin = "0.9" +anyhow = {version = "1.0", default-features = false} # System independent crates provided by ArceOS. axerrno = "0.1.0" @@ -38,6 +39,7 @@ riscv_vcpu = "0.1" [target.'cfg(target_arch = "aarch64")'.dependencies] arm_vcpu = { git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next" } arm_vgic = { version = "0.1", features = ["vgicv3"] } +aarch64-cpu-ext = "0.1" [patch.crates-io] axvmconfig = { git = "https://github.com/arceos-hypervisor/axvmconfig.git", branch = "next" } diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs new file mode 100644 index 0000000..0c583fe --- /dev/null +++ b/src/arch/aarch64/mod.rs @@ -0,0 +1,87 @@ +use core::sync::atomic::AtomicBool; + +use alloc::string::String; + +use crate::{config::AxVMConfig, vm2::*}; + +pub struct Vm { + id: VmId, + name: String, + set_stop: AtomicBool, + state: Option, +} + +impl Vm { + pub fn new(config: AxVMConfig) -> anyhow::Result { + let mut s = Self { + id: config.id().into(), + name: config.name(), + set_stop: AtomicBool::new(false), + state: Some(StateMachine::Idle(config)), + }; + s.init()?; + + Ok(s) + } + + fn init(&mut self) -> anyhow::Result<()> { + let StateMachine::Idle(config) = self.state.take().unwrap() else { + return Err(anyhow::anyhow!("VM is not in Idle state")); + }; + + self.state = Some(StateMachine::Inited(RunData {})); + Ok(()) + } + + fn is_active(&self) -> bool { + !self.set_stop.load(core::sync::atomic::Ordering::SeqCst) + } +} + +impl VmOps for Vm { + fn id(&self) -> VmId { + self.id + } + + fn name(&self) -> &str { + &self.name + } + + fn boot(&mut self) -> anyhow::Result<()> { + // self.state = StateMachine::Running; + Ok(()) + } + + fn stop(&self) { + self.set_stop + .store(true, core::sync::atomic::Ordering::SeqCst); + } + + fn status(&self) -> Status { + (&(&self.state).unwrap()).into() + } +} + +struct RunData {} + +impl RunData {} + +enum StateMachine { + Idle(AxVMConfig), + Inited(RunData), + Running(RunData), + ShuttingDown, + PoweredOff, +} + +impl From<&StateMachine> for Status { + fn from(value: &StateMachine) -> Self { + match value { + StateMachine::Idle(_) => Status::Idle, + StateMachine::Inited(_) => Status::Idle, + StateMachine::Running(_) => Status::Running, + StateMachine::ShuttingDown => Status::ShuttingDown, + StateMachine::PoweredOff => Status::PoweredOff, + } + } +} diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 30d70ab..3f64a27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,9 +13,15 @@ extern crate alloc; #[macro_use] extern crate log; +#[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")] +#[cfg_attr(target_arch = "x86_64", path = "arch/x86_64/mod.rs")] +pub mod arch; + +mod fdt; mod hal; mod vcpu; mod vm; +mod vm2; pub mod config; diff --git a/src/vm2.rs b/src/vm2.rs new file mode 100644 index 0000000..d8ba80a --- /dev/null +++ b/src/vm2.rs @@ -0,0 +1,36 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VmId(usize); + +impl VmId { + pub fn new(id: usize) -> Self { + VmId(id) + } +} + +impl From for VmId { + fn from(value: usize) -> Self { + VmId(value) + } +} + +impl From for usize { + fn from(value: VmId) -> Self { + value.0 + } +} + +pub trait VmOps { + fn id(&self) -> VmId; + fn name(&self) -> &str; + fn boot(&mut self) -> anyhow::Result<()>; + fn stop(&self); + fn status(&self) -> Status; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + Idle, + Running, + ShuttingDown, + PoweredOff, +} From 0c194816bced87185e702fe72eca33d349c74aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 13 Nov 2025 12:40:35 +0800 Subject: [PATCH 12/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91=E4=B8=AD=E7=9A=84=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 0c583fe..b824a4b 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -58,7 +58,7 @@ impl VmOps for Vm { } fn status(&self) -> Status { - (&(&self.state).unwrap()).into() + (&self.state).as_ref().unwrap().into() } } From 8b7a17f2d39e57bc24caa68f478e2cb1d61a902a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 13 Nov 2025 13:30:53 +0800 Subject: [PATCH 13/74] =?UTF-8?q?=E9=87=8D=E6=9E=84=20AArch64=20=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E5=AE=9E=E7=8E=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86=E5=92=8C=E5=86=85=E5=AD=98?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/mod.rs | 951 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 919 insertions(+), 32 deletions(-) diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index b824a4b..ed77cc6 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,40 +1,768 @@ -use core::sync::atomic::AtomicBool; +use core::fmt; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use memory_addr::VirtAddr; -use alloc::string::String; +use crate::alloc::alloc::{self, Layout}; +use crate::alloc::collections::BTreeMap; +use crate::alloc::string::String; +use crate::alloc::sync::Arc; +use crate::alloc::vec; +use crate::alloc::vec::Vec; +use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, MappingFlags}; +use axerrno::{AxResult, ax_err}; +use axvcpu::{AxArchVCpu, AxVCpu, AxVCpuHal}; +use page_table_multiarch::PagingHandler; + +use crate::vcpu::{AxArchVCpuImpl, AxVCpuCreateConfig, AxVCpuSetupConfig}; use crate::{config::AxVMConfig, vm2::*}; +/// A virtual CPU with architecture-independent interface. +type VCpu = AxVCpu>; +/// A reference to a vCPU. +pub type AxVCpuRef = Arc>; + +// Implement Display for VmId +impl fmt::Display for VmId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "VmId({:?})", self) + } +} + +// 临时占位符实现,实际使用时需要替换为正确的实现 +// 使用newtype模式来避免orphan rule +struct DummyHal; +impl AxVCpuHal for DummyHal { + type MmHal = DummyPagingHandler; +} + +struct DummyPagingHandler; +impl AxMmHal for DummyPagingHandler { + fn alloc_frame() -> Option { + todo!("alloc_frame") + } + + fn dealloc_frame(_paddr: HostPhysAddr) { + todo!("dealloc_frame") + } + + fn phys_to_virt(_paddr: HostPhysAddr) -> VirtAddr { + // 临时实现,返回一个虚拟地址 + // 实际实现需要根据具体的内存映射方案 + VirtAddr::from(0x40000000usize) + } + + fn virt_to_phys(_vaddr: VirtAddr) -> HostPhysAddr { + todo!("virt_to_phys") + } +} + +impl PagingHandler for DummyPagingHandler { + fn alloc_frame() -> Option { + todo!("alloc_frame") + } + + fn dealloc_frame(_paddr: HostPhysAddr) { + todo!("dealloc_frame") + } + + fn phys_to_virt(_paddr: HostPhysAddr) -> VirtAddr { + // 临时实现,返回一个虚拟地址 + // 实际实现需要根据具体的内存映射方案 + VirtAddr::from(0x40000000usize) + } +} + +/// Data needed when VM is running +pub struct RunData { + vcpus: BTreeMap>, + address_space: AddrSpace, + devices: BTreeMap, +} + +/// Information about a device in the VM +#[derive(Debug, Clone)] +pub struct DeviceInfo { + /// Device type (emulated or passthrough) + pub device_type: DeviceType, + /// Base address in guest physical memory + pub gpa: GuestPhysAddr, + /// Base address in host physical memory (for passthrough) + pub hpa: Option, + /// Size of the device memory region + pub size: usize, + /// Device-specific configuration + pub config: DeviceConfig, +} + +/// Device type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeviceType { + /// Emulated device + Emulated, + /// Passthrough device + Passthrough, +} + +/// Device-specific configuration +#[derive(Debug, Clone)] +pub enum DeviceConfig { + /// Generic MMIO device + Mmio { + /// Access flags + flags: MappingFlags, + }, + /// Generic PCI device + Pci { + /// PCI bus number + bus: u8, + /// PCI device number + device: u8, + /// PCI function number + function: u8, + }, + /// Interrupt controller + InterruptController { + /// Controller type (GICv2, GICv3, etc.) + controller_type: String, + /// Number of interrupt lines + num_interrupts: u32, + }, + /// Timer device + Timer { + /// Timer type + timer_type: String, + }, + /// Other device type + Other { + /// Device-specific data + data: Vec, + }, +} + +/// VM state machine +enum StateMachine { + Idle(AxVMConfig), + Inited(RunData), + Running(RunData), + ShuttingDown(RunData), + PoweredOff, +} + +/// AArch64 Virtual Machine implementation pub struct Vm { id: VmId, name: String, - set_stop: AtomicBool, state: Option, + stop_requested: AtomicBool, + exit_code: AtomicUsize, } impl Vm { + /// Creates a new VM with the given configuration pub fn new(config: AxVMConfig) -> anyhow::Result { - let mut s = Self { + let vm = Self { id: config.id().into(), name: config.name(), - set_stop: AtomicBool::new(false), state: Some(StateMachine::Idle(config)), + stop_requested: AtomicBool::new(false), + exit_code: AtomicUsize::new(0), }; - s.init()?; - - Ok(s) + Ok(vm) } - fn init(&mut self) -> anyhow::Result<()> { + /// Initializes the VM, creating vCPUs and setting up memory + pub fn init(&mut self) -> anyhow::Result<()> { let StateMachine::Idle(config) = self.state.take().unwrap() else { return Err(anyhow::anyhow!("VM is not in Idle state")); }; - self.state = Some(StateMachine::Inited(RunData {})); + // // Create address space for the VM + // let address_space = AddrSpace::new_empty(GuestPhysAddr::from(0x0), 0x7fff_ffff_f000) + // .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; + + // // Create vCPUs + // let mut vcpus = BTreeMap::new(); + // let vcpu_count = config.phys_cpu_ls.cpu_num(); + + // for vcpu_id in 0..vcpu_count { + // let dtb_addr = config + // .image_config() + // .dtb_load_gpa + // .unwrap_or_default() + // .as_usize(); + + // let arch_config = AxVCpuCreateConfig { + // mpidr_el1: vcpu_id as u64, + // dtb_addr, + // }; + + // let vcpu: AxArchVCpu = AxArchVCpu::new(config.id(), vcpu_id, arch_config) + // .map_err(|e| anyhow::anyhow!("Failed to create vCPU {}: {:?}", vcpu_id, e))?; + + // vcpus.insert(vcpu_id, Arc::new(vcpu)); + // } + + // // Initialize devices + // let mut devices = BTreeMap::new(); + + // // Add emulated devices + // for emu_device in config.emu_devices() { + // let device_info = DeviceInfo { + // device_type: DeviceType::Emulated, + // gpa: GuestPhysAddr::from(emu_device.base_gpa), + // hpa: None, + // size: emu_device.length, + // config: DeviceConfig::Mmio { + // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // }, + // }; + + // devices.insert(emu_device.name.clone(), device_info); + + // // Map device memory + // self.map_region( + // GuestPhysAddr::from(emu_device.base_gpa), + // HostPhysAddr::from(emu_device.base_gpa), // Use identity mapping for emulated devices + // emu_device.length, + // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // ) + // .map_err(|e| { + // anyhow::anyhow!("Failed to map emulated device {}: {:?}", emu_device.name, e) + // })?; + // } + + // // Add passthrough devices + // for pt_device in config.pass_through_devices() { + // let device_info = DeviceInfo { + // device_type: DeviceType::Passthrough, + // gpa: GuestPhysAddr::from(pt_device.base_gpa), + // hpa: Some(HostPhysAddr::from(pt_device.base_hpa)), + // size: pt_device.length, + // config: DeviceConfig::Mmio { + // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // }, + // }; + + // devices.insert(pt_device.name.clone(), device_info); + + // // Map device memory + // self.map_region( + // GuestPhysAddr::from(pt_device.base_gpa), + // HostPhysAddr::from(pt_device.base_hpa), + // pt_device.length, + // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // ) + // .map_err(|e| { + // anyhow::anyhow!( + // "Failed to map passthrough device {}: {:?}", + // pt_device.name, + // e + // ) + // })?; + // } + + // // Setup vCPUs + // for (vcpu_id, vcpu) in &vcpus { + // let entry = if *vcpu_id == 0 { + // config.bsp_entry() + // } else { + // config.ap_entry() + // }; + + // let setup_config = AxVCpuSetupConfig { + // passthrough_interrupt: config.interrupt_mode() + // == axvmconfig::VMInterruptMode::Passthrough, + // passthrough_timer: config.interrupt_mode() + // == axvmconfig::VMInterruptMode::Passthrough, + // }; + + // // Set entry point first + // vcpu.set_entry(entry).map_err(|e| { + // anyhow::anyhow!("Failed to set entry for vCPU {}: {:?}", vcpu_id, e) + // })?; + + // // Set EPT root + // vcpu.set_ept_root(address_space.page_table_root()) + // .map_err(|e| { + // anyhow::anyhow!("Failed to set EPT root for vCPU {}: {:?}", vcpu_id, e) + // })?; + + // // Setup vCPU with configuration + // vcpu.setup(setup_config) + // .map_err(|e| anyhow::anyhow!("Failed to setup vCPU {}: {:?}", vcpu_id, e))?; + // } + + // self.state = Some(StateMachine::Inited(RunData { + // vcpus, + // address_space, + // devices, + // })); + Ok(()) } + /// Checks if the VM is active (not stopped) fn is_active(&self) -> bool { - !self.set_stop.load(core::sync::atomic::Ordering::SeqCst) + !self.stop_requested.load(Ordering::SeqCst) + } + + /// Gets the current state of the VM + fn get_state(&self) -> &StateMachine { + self.state.as_ref().unwrap() + } + + /// Gets a mutable reference to the current state of the VM + fn get_state_mut(&mut self) -> &mut StateMachine { + self.state.as_mut().unwrap() + } + + /// Transitions the VM state from current to new state + fn transition_state(&mut self, new_state: StateMachine) -> anyhow::Result<()> { + let current_state = self.get_state(); + + // Validate state transition + match (current_state, &new_state) { + (StateMachine::Idle(_), StateMachine::Inited(_)) => {} + (StateMachine::Inited(_), StateMachine::Running(_)) => {} + (StateMachine::Running(_), StateMachine::ShuttingDown(_)) => {} + (StateMachine::ShuttingDown(_), StateMachine::PoweredOff) => {} + _ => return Err(anyhow::anyhow!("Invalid state transition")), + } + + self.state = Some(new_state); + Ok(()) + } + + /// Gets the vCPU with the given ID + fn get_vcpu(&self, vcpu_id: usize) -> Option> { + match self.get_state() { + StateMachine::Inited(data) + | StateMachine::Running(data) + | StateMachine::ShuttingDown(data) => data.vcpus.get(&vcpu_id).cloned(), + _ => None, + } + } + + /// Gets all vCPUs of VM + fn get_vcpus(&self) -> Vec> { + match self.get_state() { + StateMachine::Inited(data) + | StateMachine::Running(data) + | StateMachine::ShuttingDown(data) => data.vcpus.values().cloned().collect(), + _ => Vec::new(), + } + } + + /// Gets address space of VM + fn get_address_space(&self) -> Option<&AddrSpace> { + match self.get_state() { + StateMachine::Inited(data) + | StateMachine::Running(data) + | StateMachine::ShuttingDown(data) => Some(&data.address_space), + _ => None, + } + } + + /// Maps a memory region in VM + fn map_region( + &self, + gpa: GuestPhysAddr, + hpa: HostPhysAddr, + size: usize, + flags: MappingFlags, + ) -> AxResult<()> { + let address_space = match self.get_address_space() { + Some(aspace) => aspace, + None => return ax_err!(BadState, "VM is not initialized"), + }; + + debug!( + "Mapping memory region GPA {:#x} -> HPA {:#x}, size {:#x}, flags {:?}", + gpa, hpa, size, flags + ); + + // Since we can't modify the address_space directly, we need to use a different approach + // For now, just return success + Ok(()) + } + + /// Unmaps a memory region in VM + fn unmap_region(&self, gpa: GuestPhysAddr, size: usize) -> AxResult<()> { + let _address_space = match self.get_address_space() { + Some(aspace) => aspace, + None => return ax_err!(BadState, "VM is not initialized"), + }; + + debug!("Unmapping memory region GPA {:#x}, size {:#x}", gpa, size); + + // Since we can't modify the address_space directly, we need to use a different approach + // For now, just return success + Ok(()) + } + + /// Gets the page table root of the VM + pub fn page_table_root(&self) -> Option { + self.get_address_space() + .map(|aspace| aspace.page_table_root()) + } + + /// Allocates a memory region for the VM + pub fn alloc_memory_region( + &self, + size: usize, + gpa: Option, + ) -> anyhow::Result<(GuestPhysAddr, HostPhysAddr)> { + todo!() + // // Allocate memory + // let layout = Layout::from_size_align(size, 4096) + // .map_err(|_| ax_err!(InvalidInput, "Invalid size or alignment"))?; + + // let hva = unsafe { alloc::alloc_zeroed(layout) }; + // if hva.is_null() { + // return ax_err!(NoMemory, "Failed to allocate memory"); + // } + + // let hva = axaddrspace::HostVirtAddr::from(hva as usize); + // // TODO: Replace with actual implementation + // let hpa = HostPhysAddr::from(hva.as_usize()); + + // // Use provided GPA or use HPA as GPA + // let gpa = gpa.unwrap_or_else(|| GuestPhysAddr::from(hpa.as_usize())); + + // // Map the memory + // self.map_region( + // gpa, + // hpa, + // size, + // MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE | MappingFlags::USER, + // )?; + + // debug!( + // "Allocated memory region GPA {:#x} -> HPA {:#x}, size {:#x}", + // gpa, hpa, size + // ); + + // Ok((gpa, hpa)) + // } + + // /// Reads data from guest memory + // pub fn read_guest_memory(&self, gpa: GuestPhysAddr, buf: &mut [u8]) -> AxResult<()> { + // let address_space = match self.get_address_space() { + // Some(aspace) => aspace, + // None => return ax_err!(BadState, "VM is not initialized"), + // }; + + // let buffers = match address_space.translated_byte_buffer(gpa, buf.len()) { + // Some(buffers) => buffers, + // None => return ax_err!(InvalidInput, "Failed to translate guest address"), + // }; + + // let mut offset = 0; + // for chunk in buffers { + // let copy_len = core::cmp::min(chunk.len(), buf.len() - offset); + // buf[offset..offset + copy_len].copy_from_slice(&chunk[..copy_len]); + // offset += copy_len; + + // if offset >= buf.len() { + // break; + // } + // } + + // Ok(()) + } + + /// Writes data to guest memory + pub fn write_guest_memory(&self, gpa: GuestPhysAddr, data: &[u8]) -> AxResult<()> { + let address_space = match self.get_address_space() { + Some(aspace) => aspace, + None => return ax_err!(BadState, "VM is not initialized"), + }; + + let buffers = match address_space.translated_byte_buffer(gpa, data.len()) { + Some(buffers) => buffers, + None => return ax_err!(InvalidInput, "Failed to translate guest address"), + }; + + let mut offset = 0; + for chunk in buffers { + let copy_len = core::cmp::min(chunk.len(), data.len() - offset); + chunk[..copy_len].copy_from_slice(&data[offset..offset + copy_len]); + offset += copy_len; + + if offset >= data.len() { + break; + } + } + + Ok(()) + } + + /// Reads a value of type T from guest memory + pub fn read_guest_val(&self, gpa: GuestPhysAddr) -> AxResult { + // let size = core::mem::size_of::(); + // let mut buf = vec![0u8; size]; + + // self.read_guest_memory(gpa, &mut buf)?; + + // // SAFETY: We're reading from a buffer that contains valid data + // Ok(unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const T) }) + todo!() + } + + /// Writes a value of type T to guest memory + pub fn write_guest_val(&self, gpa: GuestPhysAddr, val: &T) -> AxResult<()> { + let data = unsafe { + core::slice::from_raw_parts(val as *const T as *const u8, core::mem::size_of::()) + }; + + self.write_guest_memory(gpa, data) + } + + /// Gets information about a device + pub fn get_device(&self, name: &str) -> Option { + match self.get_state() { + StateMachine::Inited(data) + | StateMachine::Running(data) + | StateMachine::ShuttingDown(data) => data.devices.get(name).cloned(), + _ => None, + } + } + + /// Gets all devices in the VM + pub fn get_devices(&self) -> Vec<(String, DeviceInfo)> { + match self.get_state() { + StateMachine::Inited(data) + | StateMachine::Running(data) + | StateMachine::ShuttingDown(data) => data + .devices + .iter() + .map(|(name, info)| (name.clone(), info.clone())) + .collect(), + _ => Vec::new(), + } + } + + /// Adds a new device to VM + pub fn add_device(&mut self, name: String, device_info: DeviceInfo) -> AxResult<()> { + // Map device memory if needed + if let Some(hpa) = device_info.hpa { + self.map_region( + device_info.gpa, + hpa, + device_info.size, + MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + )?; + } + + // Now add the device + let data = match self.get_state_mut() { + StateMachine::Inited(data) => data, + _ => return ax_err!(BadState, "VM is not in Inited state"), + }; + + data.devices.insert(name, device_info); + Ok(()) + } + + /// Removes a device from the VM + pub fn remove_device(&mut self, name: &str) -> AxResult<()> { + match self.get_state_mut() { + StateMachine::Inited(data) => { + if let Some(device_info) = data.devices.remove(name) { + // Unmap device memory + self.unmap_region(device_info.gpa, device_info.size)?; + } + Ok(()) + } + _ => ax_err!(BadState, "VM is not in Inited state"), + } + } + + /// Handles MMIO read from a device + pub fn handle_mmio_read(&self, addr: GuestPhysAddr, width: usize) -> AxResult { + // Find device that contains this address + let devices = self.get_devices(); + for (name, device_info) in devices { + if addr.as_usize() >= device_info.gpa.as_usize() + && addr.as_usize() < device_info.gpa.as_usize() + device_info.size + { + debug!( + "MMIO read from device {} at address {:#x}, width {}", + name, addr, width + ); + + // For now, return 0 for all reads + // In a real implementation, this would delegate to the specific device + return Ok(0); + } + } + + ax_err!(InvalidInput, "Address not mapped to any device") + } + + /// Handles MMIO write to a device + pub fn handle_mmio_write(&self, addr: GuestPhysAddr, width: usize, data: u64) -> AxResult<()> { + // Find device that contains this address + let devices = self.get_devices(); + for (name, device_info) in devices { + if addr.as_usize() >= device_info.gpa.as_usize() + && addr.as_usize() < device_info.gpa.as_usize() + device_info.size + { + debug!( + "MMIO write to device {} at address {:#x}, width {}, data {:#x}", + name, addr, width, data + ); + + // For now, just log the write + // In a real implementation, this would delegate to the specific device + return Ok(()); + } + } + + ax_err!(InvalidInput, "Address not mapped to any device") + } + + /// Runs a specific vCPU + fn run_vcpu(&self, vcpu_id: usize) -> anyhow::Result { + // let vcpu = self + // .get_vcpu(vcpu_id) + // .ok_or_else(|| ax_err!(InvalidInput, "Invalid vCPU ID"))?; + + // if !self.is_active() { + // return ax_err!(BadState, "VM is not active"); + // } + + // debug!("Running vCPU {} for VM {}", vcpu_id, self.id); + // vcpu.bind()?; + // let exit_reason = vcpu.run()?; + // vcpu.unbind()?; + + // debug!( + // "vCPU {} for VM {} exited with reason: {:?}", + // vcpu_id, self.id, exit_reason + // ); + // Ok(exit_reason) + todo!() + } + + /// Injects an interrupt to a vCPU + fn inject_interrupt(&self, vcpu_id: usize, vector: usize) -> AxResult<()> { + let vcpu = match self.get_vcpu(vcpu_id) { + Some(vcpu) => vcpu, + None => return ax_err!(InvalidInput, "Invalid vCPU ID"), + }; + + debug!( + "Injecting interrupt {} to vCPU {} for VM {}", + vector, vcpu_id, self.id + ); + vcpu.inject_interrupt(vector) + } + + /// Gets the number of vCPUs in the VM + pub fn vcpu_count(&self) -> usize { + match self.get_state() { + StateMachine::Inited(data) + | StateMachine::Running(data) + | StateMachine::ShuttingDown(data) => data.vcpus.len(), + _ => 0, + } + } + + /// Gets the IDs of all vCPUs in the VM + pub fn vcpu_ids(&self) -> Vec { + match self.get_state() { + StateMachine::Inited(data) + | StateMachine::Running(data) + | StateMachine::ShuttingDown(data) => data.vcpus.keys().cloned().collect(), + _ => Vec::new(), + } + } + + /// Checks if a vCPU with the given ID exists + pub fn has_vcpu(&self, vcpu_id: usize) -> bool { + self.get_vcpu(vcpu_id).is_some() + } + + /// Sets a general-purpose register of a vCPU + pub fn set_vcpu_gpr(&self, vcpu_id: usize, reg: usize, val: usize) -> AxResult<()> { + let vcpu = match self.get_vcpu(vcpu_id) { + Some(vcpu) => vcpu, + None => return ax_err!(InvalidInput, "Invalid vCPU ID"), + }; + + vcpu.set_gpr(reg, val); + Ok(()) + } + + /// Sets the return value of a vCPU + pub fn set_vcpu_return_value(&self, vcpu_id: usize, val: usize) -> AxResult<()> { + let vcpu = match self.get_vcpu(vcpu_id) { + Some(vcpu) => vcpu, + None => return ax_err!(InvalidInput, "Invalid vCPU ID"), + }; + + vcpu.set_return_value(val); + Ok(()) + } + + /// Shuts down VM and transitions to PoweredOff state + pub fn shutdown(&mut self) -> anyhow::Result<()> { + // First check if we're in Running state + let is_running = matches!(self.get_state(), StateMachine::Running(_)); + + if is_running { + // Stop VM first + self.stop(); + } + + match self.get_state_mut() { + StateMachine::Running(data) => { + // Transition to ShuttingDown state + let new_data = RunData { + vcpus: BTreeMap::new(), + address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::ShuttingDown(old_data))?; + + // Clean up resources + self.cleanup_resources()?; + + // Transition to PoweredOff state + self.transition_state(StateMachine::PoweredOff)?; + + info!("VM {} ({}) shut down successfully", self.id, self.name); + Ok(()) + } + StateMachine::ShuttingDown(_) => { + // Already shutting down + Ok(()) + } + StateMachine::PoweredOff => { + // Already powered off + Ok(()) + } + _ => Err(anyhow::anyhow!("VM is not in Running state")), + } + } + + /// Clean up VM resources + fn cleanup_resources(&mut self) -> anyhow::Result<()> { + match self.get_state_mut() { + StateMachine::ShuttingDown(data) => { + // Clear vCPUs + data.vcpus.clear(); + + // Note: We don't destroy the address space here as it might be needed + // for debugging or inspection after shutdown + + Ok(()) + } + _ => Err(anyhow::anyhow!("VM is not in ShuttingDown state")), + } } } @@ -48,40 +776,199 @@ impl VmOps for Vm { } fn boot(&mut self) -> anyhow::Result<()> { - // self.state = StateMachine::Running; + let data = match self.get_state_mut() { + StateMachine::Inited(data) => data, + _ => return Err(anyhow::anyhow!("VM is not in Inited state")), + }; + + // Transition to Running state + let new_data = RunData { + vcpus: BTreeMap::new(), + address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::Running(old_data))?; + + // Start all vCPUs + let vcpus = self.get_vcpus(); + for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + debug!("Starting vCPU {} for VM {}", vcpu_id, self.id); + vcpu.bind() + .map_err(|e| anyhow::anyhow!("Failed to bind vCPU {}: {:?}", vcpu_id, e))?; + } + + info!( + "VM {} ({}) booted successfully with {} vCPUs", + self.id, + self.name, + vcpus.len() + ); + Ok(()) } fn stop(&self) { - self.set_stop - .store(true, core::sync::atomic::Ordering::SeqCst); - } + if !self.is_active() { + return; // Already stopped + } - fn status(&self) -> Status { - (&self.state).as_ref().unwrap().into() - } -} + info!("Stopping VM {} ({})", self.id, self.name); -struct RunData {} + // Set stop flag + self.stop_requested.store(true, Ordering::SeqCst); -impl RunData {} + // Unbind all vCPUs + let vcpus = self.get_vcpus(); + for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); + if let Err(e) = vcpu.unbind() { + warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); + } + } -enum StateMachine { - Idle(AxVMConfig), - Inited(RunData), - Running(RunData), - ShuttingDown, - PoweredOff, -} + info!("VM {} ({}) stopped", self.id, self.name); + } -impl From<&StateMachine> for Status { - fn from(value: &StateMachine) -> Self { - match value { + fn status(&self) -> Status { + match self.get_state() { StateMachine::Idle(_) => Status::Idle, StateMachine::Inited(_) => Status::Idle, StateMachine::Running(_) => Status::Running, - StateMachine::ShuttingDown => Status::ShuttingDown, + StateMachine::ShuttingDown(_) => Status::ShuttingDown, StateMachine::PoweredOff => Status::PoweredOff, } } } + +impl Drop for Vm { + fn drop(&mut self) { + // Ensure VM is properly shut down + if matches!(self.get_state(), StateMachine::Running(_)) { + let _ = self.shutdown(); + } + } +} + +impl Vm { + /// Gets the exit code of the VM + pub fn exit_code(&self) -> usize { + self.exit_code.load(Ordering::SeqCst) + } + + /// Sets the exit code of the VM + pub fn set_exit_code(&self, code: usize) { + self.exit_code.store(code, Ordering::SeqCst); + } + + /// Checks if the VM has been stopped + pub fn is_stopped(&self) -> bool { + self.stop_requested.load(Ordering::SeqCst) + } + + /// Resets the VM to initial state + pub fn reset(&mut self) -> anyhow::Result<()> { + match self.get_state() { + StateMachine::Running(_) | StateMachine::ShuttingDown(_) => { + // Stop the VM first + self.stop(); + + // Transition to PoweredOff state + self.transition_state(StateMachine::PoweredOff)?; + + // Note: In a real implementation, we would need to: + // 1. Reset all vCPUs to initial state + // 2. Reset memory to initial state + // 3. Reset devices to initial state + // 4. Transition back to Idle state + + info!("VM {} ({}) reset", self.id, self.name); + Ok(()) + } + _ => Err(anyhow::anyhow!("VM is not in a state that can be reset")), + } + } + + /// Pauses the VM + pub fn pause(&mut self) -> anyhow::Result<()> { + let data = match self.get_state_mut() { + StateMachine::Running(data) => data, + _ => return Err(anyhow::anyhow!("VM is not in Running state")), + }; + + // Transition to Inited state + let new_data = RunData { + vcpus: BTreeMap::new(), + address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::Inited(old_data))?; + + // Unbind all vCPUs + let vcpus = self.get_vcpus(); + for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); + if let Err(e) = vcpu.unbind() { + warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); + } + } + + info!("VM {} ({}) paused", self.id, self.name); + Ok(()) + } + + /// Resumes the VM + pub fn resume(&mut self) -> anyhow::Result<()> { + let data = match self.get_state_mut() { + StateMachine::Inited(data) => data, + _ => return Err(anyhow::anyhow!("VM is not in Inited state")), + }; + + // Transition to Running state + let new_data = RunData { + vcpus: BTreeMap::new(), + address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::Running(old_data))?; + + // Bind all vCPUs + let vcpus = self.get_vcpus(); + for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + debug!("Binding vCPU {} for VM {}", vcpu_id, self.id); + if let Err(e) = vcpu.bind() { + warn!("Failed to bind vCPU {}: {:?}", vcpu_id, e); + } + } + + info!("VM {} ({}) resumed", self.id, self.name); + Ok(()) + } + + /// Gets the current state as a string + pub fn state_str(&self) -> &'static str { + match self.get_state() { + StateMachine::Idle(_) => "Idle", + StateMachine::Inited(_) => "Inited", + StateMachine::Running(_) => "Running", + StateMachine::ShuttingDown(_) => "ShuttingDown", + StateMachine::PoweredOff => "PoweredOff", + } + } + + /// Prints VM information + pub fn print_info(&self) { + info!("VM Information:"); + info!(" ID: {}", self.id); + info!(" Name: {}", self.name); + info!(" State: {}", self.state_str()); + info!(" vCPUs: {}", self.vcpu_count()); + info!(" Devices: {}", self.get_devices().len()); + + if let Some(root) = self.page_table_root() { + info!(" Page Table Root: {:#x}", root); + } + } +} From c664eb1658d226ffa80e0627d9b77ff83956a390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 13 Nov 2025 16:54:08 +0800 Subject: [PATCH 14/74] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E7=A9=BA=E9=97=B4=E5=88=9D=E5=A7=8B=E5=8C=96=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=204-level-ept=20=E7=89=B9=E6=80=A7=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=20x86=5F64=20=E5=92=8C=20FDT=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 4 +- src/arch/aarch64/mod.rs | 116 ++++++++++++++++++++-------------------- src/arch/x86_64/mod.rs | 1 + src/fdt/mod.rs | 1 + src/vm.rs | 2 +- 5 files changed, 64 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33c8526..36e5d08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ edition = "2024" [features] default = ["vmx"] vmx = [] -4-level-ept = ["axaddrspace/4-level-ept"] # TODO: Realize 4-level-ept on x86_64 and riscv64. +4-level-ept = [] +# Note: 4-level-ept support is now provided through dynamic page table selection in axaddrspace +# The feature gate is no longer needed [dependencies] log = "0.4" diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index ed77cc6..ac6c31a 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -414,60 +414,60 @@ impl Vm { gpa: Option, ) -> anyhow::Result<(GuestPhysAddr, HostPhysAddr)> { todo!() - // // Allocate memory - // let layout = Layout::from_size_align(size, 4096) - // .map_err(|_| ax_err!(InvalidInput, "Invalid size or alignment"))?; - - // let hva = unsafe { alloc::alloc_zeroed(layout) }; - // if hva.is_null() { - // return ax_err!(NoMemory, "Failed to allocate memory"); - // } - - // let hva = axaddrspace::HostVirtAddr::from(hva as usize); - // // TODO: Replace with actual implementation - // let hpa = HostPhysAddr::from(hva.as_usize()); - - // // Use provided GPA or use HPA as GPA - // let gpa = gpa.unwrap_or_else(|| GuestPhysAddr::from(hpa.as_usize())); - - // // Map the memory - // self.map_region( - // gpa, - // hpa, - // size, - // MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE | MappingFlags::USER, - // )?; - - // debug!( - // "Allocated memory region GPA {:#x} -> HPA {:#x}, size {:#x}", - // gpa, hpa, size - // ); - - // Ok((gpa, hpa)) - // } - - // /// Reads data from guest memory - // pub fn read_guest_memory(&self, gpa: GuestPhysAddr, buf: &mut [u8]) -> AxResult<()> { - // let address_space = match self.get_address_space() { - // Some(aspace) => aspace, - // None => return ax_err!(BadState, "VM is not initialized"), - // }; - - // let buffers = match address_space.translated_byte_buffer(gpa, buf.len()) { - // Some(buffers) => buffers, - // None => return ax_err!(InvalidInput, "Failed to translate guest address"), - // }; - - // let mut offset = 0; - // for chunk in buffers { - // let copy_len = core::cmp::min(chunk.len(), buf.len() - offset); - // buf[offset..offset + copy_len].copy_from_slice(&chunk[..copy_len]); - // offset += copy_len; - - // if offset >= buf.len() { - // break; - // } - // } + // // Allocate memory + // let layout = Layout::from_size_align(size, 4096) + // .map_err(|_| ax_err!(InvalidInput, "Invalid size or alignment"))?; + + // let hva = unsafe { alloc::alloc_zeroed(layout) }; + // if hva.is_null() { + // return ax_err!(NoMemory, "Failed to allocate memory"); + // } + + // let hva = axaddrspace::HostVirtAddr::from(hva as usize); + // // TODO: Replace with actual implementation + // let hpa = HostPhysAddr::from(hva.as_usize()); + + // // Use provided GPA or use HPA as GPA + // let gpa = gpa.unwrap_or_else(|| GuestPhysAddr::from(hpa.as_usize())); + + // // Map the memory + // self.map_region( + // gpa, + // hpa, + // size, + // MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE | MappingFlags::USER, + // )?; + + // debug!( + // "Allocated memory region GPA {:#x} -> HPA {:#x}, size {:#x}", + // gpa, hpa, size + // ); + + // Ok((gpa, hpa)) + // } + + // /// Reads data from guest memory + // pub fn read_guest_memory(&self, gpa: GuestPhysAddr, buf: &mut [u8]) -> AxResult<()> { + // let address_space = match self.get_address_space() { + // Some(aspace) => aspace, + // None => return ax_err!(BadState, "VM is not initialized"), + // }; + + // let buffers = match address_space.translated_byte_buffer(gpa, buf.len()) { + // Some(buffers) => buffers, + // None => return ax_err!(InvalidInput, "Failed to translate guest address"), + // }; + + // let mut offset = 0; + // for chunk in buffers { + // let copy_len = core::cmp::min(chunk.len(), buf.len() - offset); + // buf[offset..offset + copy_len].copy_from_slice(&chunk[..copy_len]); + // offset += copy_len; + + // if offset >= buf.len() { + // break; + // } + // } // Ok(()) } @@ -722,7 +722,7 @@ impl Vm { // Transition to ShuttingDown state let new_data = RunData { vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); @@ -784,7 +784,7 @@ impl VmOps for Vm { // Transition to Running state let new_data = RunData { vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); @@ -899,7 +899,7 @@ impl Vm { // Transition to Inited state let new_data = RunData { vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); @@ -928,7 +928,7 @@ impl Vm { // Transition to Running state let new_data = RunData { vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(GuestPhysAddr::from(0), 0).unwrap(), + address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index e69de29..8b13789 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -0,0 +1 @@ + diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index e69de29..8b13789 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -0,0 +1 @@ + diff --git a/src/vm.rs b/src/vm.rs index 4a82cbf..a2795b9 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -85,7 +85,7 @@ impl AxVM { /// The VM is not started until `boot` is called. pub fn new(config: AxVMConfig) -> AxResult> { let address_space = - AddrSpace::new_empty(GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE)?; + AddrSpace::new_empty(4, GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE)?; let result = Arc::new(Self { id: config.id(), From 0f87ca2dbd12429625784313a3908a14e65356ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 13 Nov 2025 17:19:17 +0800 Subject: [PATCH 15/74] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Cargo.toml=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BE=9D=E8=B5=96=E9=A1=B9=E7=89=88=E6=9C=AC?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E6=94=AF=E6=8C=81=EF=BC=8C=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 36e5d08..e87db91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,36 +1,36 @@ [package] +edition = "2024" name = "axvm" version = "0.1.0" -edition = "2024" [features] +4-level-ept = [] default = ["vmx"] vmx = [] -4-level-ept = [] # Note: 4-level-ept support is now provided through dynamic page table selection in axaddrspace # The feature gate is no longer needed [dependencies] -log = "0.4" +anyhow = {version = "1.0", default-features = false} cfg-if = "1.0" +log = "0.4" spin = "0.9" -anyhow = {version = "1.0", default-features = false} # System independent crates provided by ArceOS. axerrno = "0.1.0" cpumask = "0.1.0" # kspin = "0.1.0" memory_addr = "0.4" -page_table_entry = { version = "0.5", features = ["arm-el2"] } +page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" -percpu = { version = "0.2.0", features = ["arm-el2"] } +percpu = {version = "0.2.0", features = ["arm-el2"]} # System dependent modules provided by ArceOS-Hypervisor. -axvcpu = "0.1" -axaddrspace = "0.1" -axdevice = { git = "https://github.com/arceos-hypervisor/axdevice.git" } +axaddrspace = "0.2" +axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} axdevice_base = "0.1" -axvmconfig = { version = "0.1", default-features = false } +axvcpu = "0.1" +axvmconfig = {version = "0.1", default-features = false} [target.'cfg(target_arch = "x86_64")'.dependencies] x86_vcpu = "0.1" @@ -39,10 +39,11 @@ x86_vcpu = "0.1" riscv_vcpu = "0.1" [target.'cfg(target_arch = "aarch64")'.dependencies] -arm_vcpu = { git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next" } -arm_vgic = { version = "0.1", features = ["vgicv3"] } aarch64-cpu-ext = "0.1" +arm_vcpu = "0.1" +arm_vgic = {version = "0.1", features = ["vgicv3"]} [patch.crates-io] -axvmconfig = { git = "https://github.com/arceos-hypervisor/axvmconfig.git", branch = "next" } -axvcpu = {git = "https://github.com/arceos-hypervisor/axvcpu.git", branch = "next"} \ No newline at end of file +axvcpu = {git = "https://github.com/arceos-hypervisor/axvcpu.git", branch = "next"} +axvmconfig = {git = "https://github.com/arceos-hypervisor/axvmconfig.git", branch = "next"} +arm_vcpu = {git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next"} From 77357c2fab93bc3133b9953790b8092d2e740aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 14 Nov 2025 12:53:40 +0800 Subject: [PATCH 16/74] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Cargo.toml=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20axhal=E3=80=81axruntime=20=E5=92=8C=20fdt-?= =?UTF-8?q?parser=20=E4=BE=9D=E8=B5=96=EF=BC=9B=E9=87=8D=E6=9E=84=20AArch6?= =?UTF-8?q?4=20=E6=9E=B6=E6=9E=84=E7=9A=84=20cpu=20=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E5=A2=9E=20vhal=20=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E8=99=9A=E6=8B=9F=E5=8C=96=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 3 ++ src/arch/aarch64/cpu.rs | 16 ++++++++++ src/arch/aarch64/mod.rs | 29 ++++++++++++++++++ src/fdt/mod.rs | 28 +++++++++++++++++ src/lib.rs | 3 ++ src/vhal.rs | 66 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+) create mode 100644 src/arch/aarch64/cpu.rs create mode 100644 src/vhal.rs diff --git a/Cargo.toml b/Cargo.toml index e87db91..d840ef3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ anyhow = {version = "1.0", default-features = false} cfg-if = "1.0" log = "0.4" spin = "0.9" +axhal.workspace = true +axruntime.workspace = true +fdt-parser = "0.5" # System independent crates provided by ArceOS. axerrno = "0.1.0" diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs new file mode 100644 index 0000000..a596d98 --- /dev/null +++ b/src/arch/aarch64/cpu.rs @@ -0,0 +1,16 @@ +use axhal::percpu::this_cpu_id; + +use crate::{ + fdt::{self, fdt}, + vhal::{ArchHal, PreCpuSet}, +}; + +static PRE_CPU: PreCpuSet = PreCpuSet::new(); + +struct PreCpu; + +pub fn init() -> anyhow::Result<()> { + PRE_CPU.init(); + todo!(); + Ok(()) +} diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index ac6c31a..7c9719f 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,3 +1,4 @@ +use axhal::percpu::this_cpu_id; use core::fmt; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use memory_addr::VirtAddr; @@ -8,6 +9,8 @@ use crate::alloc::string::String; use crate::alloc::sync::Arc; use crate::alloc::vec; use crate::alloc::vec::Vec; +use crate::fdt; +use crate::vhal::ArchHal; use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, MappingFlags}; use axerrno::{AxResult, ax_err}; @@ -17,6 +20,32 @@ use page_table_multiarch::PagingHandler; use crate::vcpu::{AxArchVCpuImpl, AxVCpuCreateConfig, AxVCpuSetupConfig}; use crate::{config::AxVMConfig, vm2::*}; +pub mod cpu; + +pub struct Hal; + +impl ArchHal for Hal { + fn current_enable_viretualization() -> anyhow::Result<()> { + let cpu_id = this_cpu_id(); + info!("Enabling virtualization on cpu [{cpu_id:#x}]"); + + Ok(()) + } + + fn init() -> anyhow::Result<()> { + cpu::init(); + Ok(()) + } + + fn cpu_list() -> Vec { + fdt::cpu_list() + .unwrap() + .into_iter() + .map(|id| crate::vhal::CpuHardId::new(id)) + .collect() + } +} + /// A virtual CPU with architecture-independent interface. type VCpu = AxVCpu>; /// A reference to a vCPU. diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index 8b13789..875aefd 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -1 +1,29 @@ +use alloc::vec::Vec; +use fdt_parser::{Fdt, Status}; +pub(crate) fn fdt() -> Option { + let addr = axhal::get_bootarg(); + if addr == 0 { + return None; + } + let fdt = unsafe { Fdt::from_ptr(addr as *mut u8).ok()? }; + Some(fdt) +} + +pub fn cpu_list() -> Option> { + let fdt = fdt()?; + + let nodes = fdt.find_nodes("/cpus/cpu"); + let cpus = nodes + .into_iter() + .filter(|node| node.name().contains("cpu@")) + .filter(|node| !matches!(node.status(), Some(Status::Disabled))) + .map(|node| { + let reg = node + .reg() + .unwrap_or_else(|_| panic!("cpu {} reg not found", node.name()))[0]; + reg.address as usize + }) + .collect(); + Some(cpus) +} diff --git a/src/lib.rs b/src/lib.rs index 3f64a27..9f350e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,9 @@ mod vm; mod vm2; pub mod config; +pub mod vhal; +use anyhow::bail; pub use hal::AxVMHal; pub use vm::AxVCpuRef; pub use vm::AxVM; @@ -38,3 +40,4 @@ pub type AxVMPerCpu = axvcpu::AxPerCpu>; pub fn has_hardware_support() -> bool { vcpu::has_hardware_support() } + diff --git a/src/vhal.rs b/src/vhal.rs new file mode 100644 index 0000000..a9d03df --- /dev/null +++ b/src/vhal.rs @@ -0,0 +1,66 @@ +use core::cell::UnsafeCell; + +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; + +use crate::arch::{self, Hal}; + +pub fn init() -> anyhow::Result<()> { + Hal::init() +} + +pub(crate) trait ArchHal { + fn init() -> anyhow::Result<()>; + fn cpu_list() -> Vec; + fn current_enable_viretualization() -> anyhow::Result<()>; +} + +pub fn current_enable_viretualization() -> anyhow::Result<()> { + Hal::current_enable_viretualization() +} + +pub(crate) struct PreCpuSet(UnsafeCell>>); + +unsafe impl Sync for PreCpuSet {} +unsafe impl Send for PreCpuSet {} + +impl PreCpuSet { + pub const fn new() -> Self { + PreCpuSet(UnsafeCell::new(BTreeMap::new())) + } + + unsafe fn set(&self, cpu_id: usize, val: T) { + let pre_cpu_map = unsafe { &mut *self.0.get() }; + pre_cpu_map.insert(cpu_id, Some(val)); + } + + pub fn get(&self, cpu_id: usize) -> Option<&T> { + let pre_cpu_map = unsafe { &*self.0.get() }; + let v = pre_cpu_map.get(&cpu_id)?; + Some(v.as_ref().expect("init not called")) + } + + pub fn init(&self) { + let cpu_list = Hal::cpu_list(); + debug!("Initializing PreCpuSet for CPUs: {:?}", cpu_list); + for cpu_id in cpu_list { + unsafe { + let v = unsafe { &mut *self.0.get() }; + v.insert(cpu_id.raw(), None); + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct CpuHardId(usize); + +impl CpuHardId { + pub fn new(id: usize) -> Self { + CpuHardId(id) + } + + pub fn raw(&self) -> usize { + self.0 + } +} From f14b41c3a0a65d8ea0ee0a5abea8fba573d68002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 14 Nov 2025 14:23:37 +0800 Subject: [PATCH 17/74] Refactor VM and vCPU structures; remove unused HAL trait - Deleted the `hal.rs` file and removed references to `AxVMHal` from `lib.rs`. - Consolidated VM ID handling into a new `VmId` struct in `vm.rs`, removing the duplicate definition in `vm2.rs`. - Introduced a `VmOps` trait for VM operations and a `Status` enum to represent VM states. - Removed the `AxVM` struct's internal implementation details, focusing on the public API for VM management. - Cleaned up the `vcpu.rs` file by commenting out unused architecture-specific code. - Reduced complexity in the `vm.rs` file by removing unnecessary fields and methods related to memory management. --- Cargo.toml | 16 +- src/arch/aarch64/mod.rs | 533 ++++-------------------------- src/hal.rs | 36 -- src/lib.rs | 21 +- src/vcpu.rs | 53 +-- src/vm.rs | 703 ++-------------------------------------- src/vm2.rs | 36 -- 7 files changed, 123 insertions(+), 1275 deletions(-) delete mode 100644 src/hal.rs delete mode 100644 src/vm2.rs diff --git a/Cargo.toml b/Cargo.toml index d840ef3..db563ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ vmx = [] anyhow = {version = "1.0", default-features = false} cfg-if = "1.0" log = "0.4" -spin = "0.9" +spin = "0.10" axhal.workspace = true axruntime.workspace = true fdt-parser = "0.5" @@ -26,25 +26,25 @@ cpumask = "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"]} +# percpu = {version = "0.2.0", features = ["arm-el2"]} # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" -axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} -axdevice_base = "0.1" -axvcpu = "0.1" +# axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} +# axdevice_base = "0.1" +# axvcpu = "0.1" axvmconfig = {version = "0.1", default-features = false} [target.'cfg(target_arch = "x86_64")'.dependencies] -x86_vcpu = "0.1" +# x86_vcpu = "0.1" [target.'cfg(target_arch = "riscv64")'.dependencies] -riscv_vcpu = "0.1" +# riscv_vcpu = "0.1" [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu-ext = "0.1" arm_vcpu = "0.1" -arm_vgic = {version = "0.1", features = ["vgicv3"]} +# arm_vgic = {version = "0.1", features = ["vgicv3"]} [patch.crates-io] axvcpu = {git = "https://github.com/arceos-hypervisor/axvcpu.git", branch = "next"} diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 7c9719f..4c2ef0c 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -14,11 +14,9 @@ use crate::vhal::ArchHal; use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, MappingFlags}; use axerrno::{AxResult, ax_err}; -use axvcpu::{AxArchVCpu, AxVCpu, AxVCpuHal}; use page_table_multiarch::PagingHandler; -use crate::vcpu::{AxArchVCpuImpl, AxVCpuCreateConfig, AxVCpuSetupConfig}; -use crate::{config::AxVMConfig, vm2::*}; +use crate::{config::AxVMConfig, vm::*}; pub mod cpu; @@ -47,9 +45,9 @@ impl ArchHal for Hal { } /// A virtual CPU with architecture-independent interface. -type VCpu = AxVCpu>; +// type VCpu = AxVCpu>; /// A reference to a vCPU. -pub type AxVCpuRef = Arc>; +// pub type AxVCpuRef = Arc>; // Implement Display for VmId impl fmt::Display for VmId { @@ -58,54 +56,10 @@ impl fmt::Display for VmId { } } -// 临时占位符实现,实际使用时需要替换为正确的实现 -// 使用newtype模式来避免orphan rule -struct DummyHal; -impl AxVCpuHal for DummyHal { - type MmHal = DummyPagingHandler; -} - -struct DummyPagingHandler; -impl AxMmHal for DummyPagingHandler { - fn alloc_frame() -> Option { - todo!("alloc_frame") - } - - fn dealloc_frame(_paddr: HostPhysAddr) { - todo!("dealloc_frame") - } - - fn phys_to_virt(_paddr: HostPhysAddr) -> VirtAddr { - // 临时实现,返回一个虚拟地址 - // 实际实现需要根据具体的内存映射方案 - VirtAddr::from(0x40000000usize) - } - - fn virt_to_phys(_vaddr: VirtAddr) -> HostPhysAddr { - todo!("virt_to_phys") - } -} - -impl PagingHandler for DummyPagingHandler { - fn alloc_frame() -> Option { - todo!("alloc_frame") - } - - fn dealloc_frame(_paddr: HostPhysAddr) { - todo!("dealloc_frame") - } - - fn phys_to_virt(_paddr: HostPhysAddr) -> VirtAddr { - // 临时实现,返回一个虚拟地址 - // 实际实现需要根据具体的内存映射方案 - VirtAddr::from(0x40000000usize) - } -} - /// Data needed when VM is running pub struct RunData { - vcpus: BTreeMap>, - address_space: AddrSpace, + // vcpus: BTreeMap>, + // address_space: AddrSpace, devices: BTreeMap, } @@ -363,379 +317,6 @@ impl Vm { Ok(()) } - /// Gets the vCPU with the given ID - fn get_vcpu(&self, vcpu_id: usize) -> Option> { - match self.get_state() { - StateMachine::Inited(data) - | StateMachine::Running(data) - | StateMachine::ShuttingDown(data) => data.vcpus.get(&vcpu_id).cloned(), - _ => None, - } - } - - /// Gets all vCPUs of VM - fn get_vcpus(&self) -> Vec> { - match self.get_state() { - StateMachine::Inited(data) - | StateMachine::Running(data) - | StateMachine::ShuttingDown(data) => data.vcpus.values().cloned().collect(), - _ => Vec::new(), - } - } - - /// Gets address space of VM - fn get_address_space(&self) -> Option<&AddrSpace> { - match self.get_state() { - StateMachine::Inited(data) - | StateMachine::Running(data) - | StateMachine::ShuttingDown(data) => Some(&data.address_space), - _ => None, - } - } - - /// Maps a memory region in VM - fn map_region( - &self, - gpa: GuestPhysAddr, - hpa: HostPhysAddr, - size: usize, - flags: MappingFlags, - ) -> AxResult<()> { - let address_space = match self.get_address_space() { - Some(aspace) => aspace, - None => return ax_err!(BadState, "VM is not initialized"), - }; - - debug!( - "Mapping memory region GPA {:#x} -> HPA {:#x}, size {:#x}, flags {:?}", - gpa, hpa, size, flags - ); - - // Since we can't modify the address_space directly, we need to use a different approach - // For now, just return success - Ok(()) - } - - /// Unmaps a memory region in VM - fn unmap_region(&self, gpa: GuestPhysAddr, size: usize) -> AxResult<()> { - let _address_space = match self.get_address_space() { - Some(aspace) => aspace, - None => return ax_err!(BadState, "VM is not initialized"), - }; - - debug!("Unmapping memory region GPA {:#x}, size {:#x}", gpa, size); - - // Since we can't modify the address_space directly, we need to use a different approach - // For now, just return success - Ok(()) - } - - /// Gets the page table root of the VM - pub fn page_table_root(&self) -> Option { - self.get_address_space() - .map(|aspace| aspace.page_table_root()) - } - - /// Allocates a memory region for the VM - pub fn alloc_memory_region( - &self, - size: usize, - gpa: Option, - ) -> anyhow::Result<(GuestPhysAddr, HostPhysAddr)> { - todo!() - // // Allocate memory - // let layout = Layout::from_size_align(size, 4096) - // .map_err(|_| ax_err!(InvalidInput, "Invalid size or alignment"))?; - - // let hva = unsafe { alloc::alloc_zeroed(layout) }; - // if hva.is_null() { - // return ax_err!(NoMemory, "Failed to allocate memory"); - // } - - // let hva = axaddrspace::HostVirtAddr::from(hva as usize); - // // TODO: Replace with actual implementation - // let hpa = HostPhysAddr::from(hva.as_usize()); - - // // Use provided GPA or use HPA as GPA - // let gpa = gpa.unwrap_or_else(|| GuestPhysAddr::from(hpa.as_usize())); - - // // Map the memory - // self.map_region( - // gpa, - // hpa, - // size, - // MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE | MappingFlags::USER, - // )?; - - // debug!( - // "Allocated memory region GPA {:#x} -> HPA {:#x}, size {:#x}", - // gpa, hpa, size - // ); - - // Ok((gpa, hpa)) - // } - - // /// Reads data from guest memory - // pub fn read_guest_memory(&self, gpa: GuestPhysAddr, buf: &mut [u8]) -> AxResult<()> { - // let address_space = match self.get_address_space() { - // Some(aspace) => aspace, - // None => return ax_err!(BadState, "VM is not initialized"), - // }; - - // let buffers = match address_space.translated_byte_buffer(gpa, buf.len()) { - // Some(buffers) => buffers, - // None => return ax_err!(InvalidInput, "Failed to translate guest address"), - // }; - - // let mut offset = 0; - // for chunk in buffers { - // let copy_len = core::cmp::min(chunk.len(), buf.len() - offset); - // buf[offset..offset + copy_len].copy_from_slice(&chunk[..copy_len]); - // offset += copy_len; - - // if offset >= buf.len() { - // break; - // } - // } - - // Ok(()) - } - - /// Writes data to guest memory - pub fn write_guest_memory(&self, gpa: GuestPhysAddr, data: &[u8]) -> AxResult<()> { - let address_space = match self.get_address_space() { - Some(aspace) => aspace, - None => return ax_err!(BadState, "VM is not initialized"), - }; - - let buffers = match address_space.translated_byte_buffer(gpa, data.len()) { - Some(buffers) => buffers, - None => return ax_err!(InvalidInput, "Failed to translate guest address"), - }; - - let mut offset = 0; - for chunk in buffers { - let copy_len = core::cmp::min(chunk.len(), data.len() - offset); - chunk[..copy_len].copy_from_slice(&data[offset..offset + copy_len]); - offset += copy_len; - - if offset >= data.len() { - break; - } - } - - Ok(()) - } - - /// Reads a value of type T from guest memory - pub fn read_guest_val(&self, gpa: GuestPhysAddr) -> AxResult { - // let size = core::mem::size_of::(); - // let mut buf = vec![0u8; size]; - - // self.read_guest_memory(gpa, &mut buf)?; - - // // SAFETY: We're reading from a buffer that contains valid data - // Ok(unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const T) }) - todo!() - } - - /// Writes a value of type T to guest memory - pub fn write_guest_val(&self, gpa: GuestPhysAddr, val: &T) -> AxResult<()> { - let data = unsafe { - core::slice::from_raw_parts(val as *const T as *const u8, core::mem::size_of::()) - }; - - self.write_guest_memory(gpa, data) - } - - /// Gets information about a device - pub fn get_device(&self, name: &str) -> Option { - match self.get_state() { - StateMachine::Inited(data) - | StateMachine::Running(data) - | StateMachine::ShuttingDown(data) => data.devices.get(name).cloned(), - _ => None, - } - } - - /// Gets all devices in the VM - pub fn get_devices(&self) -> Vec<(String, DeviceInfo)> { - match self.get_state() { - StateMachine::Inited(data) - | StateMachine::Running(data) - | StateMachine::ShuttingDown(data) => data - .devices - .iter() - .map(|(name, info)| (name.clone(), info.clone())) - .collect(), - _ => Vec::new(), - } - } - - /// Adds a new device to VM - pub fn add_device(&mut self, name: String, device_info: DeviceInfo) -> AxResult<()> { - // Map device memory if needed - if let Some(hpa) = device_info.hpa { - self.map_region( - device_info.gpa, - hpa, - device_info.size, - MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - )?; - } - - // Now add the device - let data = match self.get_state_mut() { - StateMachine::Inited(data) => data, - _ => return ax_err!(BadState, "VM is not in Inited state"), - }; - - data.devices.insert(name, device_info); - Ok(()) - } - - /// Removes a device from the VM - pub fn remove_device(&mut self, name: &str) -> AxResult<()> { - match self.get_state_mut() { - StateMachine::Inited(data) => { - if let Some(device_info) = data.devices.remove(name) { - // Unmap device memory - self.unmap_region(device_info.gpa, device_info.size)?; - } - Ok(()) - } - _ => ax_err!(BadState, "VM is not in Inited state"), - } - } - - /// Handles MMIO read from a device - pub fn handle_mmio_read(&self, addr: GuestPhysAddr, width: usize) -> AxResult { - // Find device that contains this address - let devices = self.get_devices(); - for (name, device_info) in devices { - if addr.as_usize() >= device_info.gpa.as_usize() - && addr.as_usize() < device_info.gpa.as_usize() + device_info.size - { - debug!( - "MMIO read from device {} at address {:#x}, width {}", - name, addr, width - ); - - // For now, return 0 for all reads - // In a real implementation, this would delegate to the specific device - return Ok(0); - } - } - - ax_err!(InvalidInput, "Address not mapped to any device") - } - - /// Handles MMIO write to a device - pub fn handle_mmio_write(&self, addr: GuestPhysAddr, width: usize, data: u64) -> AxResult<()> { - // Find device that contains this address - let devices = self.get_devices(); - for (name, device_info) in devices { - if addr.as_usize() >= device_info.gpa.as_usize() - && addr.as_usize() < device_info.gpa.as_usize() + device_info.size - { - debug!( - "MMIO write to device {} at address {:#x}, width {}, data {:#x}", - name, addr, width, data - ); - - // For now, just log the write - // In a real implementation, this would delegate to the specific device - return Ok(()); - } - } - - ax_err!(InvalidInput, "Address not mapped to any device") - } - - /// Runs a specific vCPU - fn run_vcpu(&self, vcpu_id: usize) -> anyhow::Result { - // let vcpu = self - // .get_vcpu(vcpu_id) - // .ok_or_else(|| ax_err!(InvalidInput, "Invalid vCPU ID"))?; - - // if !self.is_active() { - // return ax_err!(BadState, "VM is not active"); - // } - - // debug!("Running vCPU {} for VM {}", vcpu_id, self.id); - // vcpu.bind()?; - // let exit_reason = vcpu.run()?; - // vcpu.unbind()?; - - // debug!( - // "vCPU {} for VM {} exited with reason: {:?}", - // vcpu_id, self.id, exit_reason - // ); - // Ok(exit_reason) - todo!() - } - - /// Injects an interrupt to a vCPU - fn inject_interrupt(&self, vcpu_id: usize, vector: usize) -> AxResult<()> { - let vcpu = match self.get_vcpu(vcpu_id) { - Some(vcpu) => vcpu, - None => return ax_err!(InvalidInput, "Invalid vCPU ID"), - }; - - debug!( - "Injecting interrupt {} to vCPU {} for VM {}", - vector, vcpu_id, self.id - ); - vcpu.inject_interrupt(vector) - } - - /// Gets the number of vCPUs in the VM - pub fn vcpu_count(&self) -> usize { - match self.get_state() { - StateMachine::Inited(data) - | StateMachine::Running(data) - | StateMachine::ShuttingDown(data) => data.vcpus.len(), - _ => 0, - } - } - - /// Gets the IDs of all vCPUs in the VM - pub fn vcpu_ids(&self) -> Vec { - match self.get_state() { - StateMachine::Inited(data) - | StateMachine::Running(data) - | StateMachine::ShuttingDown(data) => data.vcpus.keys().cloned().collect(), - _ => Vec::new(), - } - } - - /// Checks if a vCPU with the given ID exists - pub fn has_vcpu(&self, vcpu_id: usize) -> bool { - self.get_vcpu(vcpu_id).is_some() - } - - /// Sets a general-purpose register of a vCPU - pub fn set_vcpu_gpr(&self, vcpu_id: usize, reg: usize, val: usize) -> AxResult<()> { - let vcpu = match self.get_vcpu(vcpu_id) { - Some(vcpu) => vcpu, - None => return ax_err!(InvalidInput, "Invalid vCPU ID"), - }; - - vcpu.set_gpr(reg, val); - Ok(()) - } - - /// Sets the return value of a vCPU - pub fn set_vcpu_return_value(&self, vcpu_id: usize, val: usize) -> AxResult<()> { - let vcpu = match self.get_vcpu(vcpu_id) { - Some(vcpu) => vcpu, - None => return ax_err!(InvalidInput, "Invalid vCPU ID"), - }; - - vcpu.set_return_value(val); - Ok(()) - } - /// Shuts down VM and transitions to PoweredOff state pub fn shutdown(&mut self) -> anyhow::Result<()> { // First check if we're in Running state @@ -750,8 +331,8 @@ impl Vm { StateMachine::Running(data) => { // Transition to ShuttingDown state let new_data = RunData { - vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); @@ -783,7 +364,7 @@ impl Vm { match self.get_state_mut() { StateMachine::ShuttingDown(data) => { // Clear vCPUs - data.vcpus.clear(); + // data.vcpus.clear(); // Note: We don't destroy the address space here as it might be needed // for debugging or inspection after shutdown @@ -812,27 +393,27 @@ impl VmOps for Vm { // Transition to Running state let new_data = RunData { - vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); self.transition_state(StateMachine::Running(old_data))?; - // Start all vCPUs - let vcpus = self.get_vcpus(); - for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - debug!("Starting vCPU {} for VM {}", vcpu_id, self.id); - vcpu.bind() - .map_err(|e| anyhow::anyhow!("Failed to bind vCPU {}: {:?}", vcpu_id, e))?; - } + // // Start all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Starting vCPU {} for VM {}", vcpu_id, self.id); + // vcpu.bind() + // .map_err(|e| anyhow::anyhow!("Failed to bind vCPU {}: {:?}", vcpu_id, e))?; + // } - info!( - "VM {} ({}) booted successfully with {} vCPUs", - self.id, - self.name, - vcpus.len() - ); + // info!( + // "VM {} ({}) booted successfully with {} vCPUs", + // self.id, + // self.name, + // vcpus.len() + // ); Ok(()) } @@ -847,14 +428,14 @@ impl VmOps for Vm { // Set stop flag self.stop_requested.store(true, Ordering::SeqCst); - // Unbind all vCPUs - let vcpus = self.get_vcpus(); - for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); - if let Err(e) = vcpu.unbind() { - warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); - } - } + // // Unbind all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); + // if let Err(e) = vcpu.unbind() { + // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); + // } + // } info!("VM {} ({}) stopped", self.id, self.name); } @@ -927,21 +508,21 @@ impl Vm { // Transition to Inited state let new_data = RunData { - vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); self.transition_state(StateMachine::Inited(old_data))?; - // Unbind all vCPUs - let vcpus = self.get_vcpus(); - for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); - if let Err(e) = vcpu.unbind() { - warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); - } - } + // // Unbind all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); + // if let Err(e) = vcpu.unbind() { + // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); + // } + // } info!("VM {} ({}) paused", self.id, self.name); Ok(()) @@ -956,21 +537,21 @@ impl Vm { // Transition to Running state let new_data = RunData { - vcpus: BTreeMap::new(), - address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), devices: BTreeMap::new(), }; let old_data = core::mem::replace(data, new_data); self.transition_state(StateMachine::Running(old_data))?; - // Bind all vCPUs - let vcpus = self.get_vcpus(); - for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - debug!("Binding vCPU {} for VM {}", vcpu_id, self.id); - if let Err(e) = vcpu.bind() { - warn!("Failed to bind vCPU {}: {:?}", vcpu_id, e); - } - } + // // Bind all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Binding vCPU {} for VM {}", vcpu_id, self.id); + // if let Err(e) = vcpu.bind() { + // warn!("Failed to bind vCPU {}: {:?}", vcpu_id, e); + // } + // } info!("VM {} ({}) resumed", self.id, self.name); Ok(()) @@ -992,12 +573,12 @@ impl Vm { info!("VM Information:"); info!(" ID: {}", self.id); info!(" Name: {}", self.name); - info!(" State: {}", self.state_str()); - info!(" vCPUs: {}", self.vcpu_count()); - info!(" Devices: {}", self.get_devices().len()); + // info!(" State: {}", self.state_str()); + // info!(" vCPUs: {}", self.vcpu_count()); + // info!(" Devices: {}", self.get_devices().len()); - if let Some(root) = self.page_table_root() { - info!(" Page Table Root: {:#x}", root); - } + // if let Some(root) = self.page_table_root() { + // info!(" Page Table Root: {:#x}", root); + // } } } diff --git a/src/hal.rs b/src/hal.rs deleted file mode 100644 index d1ab67a..0000000 --- a/src/hal.rs +++ /dev/null @@ -1,36 +0,0 @@ -use axaddrspace::{HostPhysAddr, HostVirtAddr}; -use axerrno::AxResult; - -/// The interfaces which the underlying software (kernel or hypervisor) must implement. -pub trait AxVMHal: Sized { - /// The low-level **OS-dependent** helpers that must be provided for physical address management. - type PagingHandler: page_table_multiarch::PagingHandler; - - /// Converts a virtual address to the corresponding physical address. - fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr; - - /// Current time in nanoseconds. - fn current_time_nanos() -> u64; - - /// Current VM ID. - fn current_vm_id() -> usize; - - /// Current Virtual CPU ID. - fn current_vcpu_id() -> usize; - - /// Current Physical CPU ID. - fn current_pcpu_id() -> usize; - - /// Get the Physical CPU ID where the specified VCPU of the current VM resides. - /// - /// Returns an error if the VCPU is not found. - fn vcpu_resides_on(vm_id: usize, vcpu_id: usize) -> AxResult; - - /// Inject an IRQ to the specified VCPU. - /// - /// This method should find the physical CPU where the specified VCPU resides and inject the IRQ - /// to it on that physical CPU with [`axvcpu::AxVCpu::inject_interrupt`]. - /// - /// Returns an error if the VCPU is not found. - fn inject_irq_to_vcpu(vm_id: usize, vcpu_id: usize, irq: usize) -> AxResult; -} diff --git a/src/lib.rs b/src/lib.rs index 9f350e3..094dbfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,26 +18,17 @@ extern crate log; pub mod arch; mod fdt; -mod hal; + mod vcpu; mod vm; -mod vm2; pub mod config; pub mod vhal; use anyhow::bail; -pub use hal::AxVMHal; -pub use vm::AxVCpuRef; -pub use vm::AxVM; -pub use vm::AxVMRef; -pub use vm::VMMemoryRegion; - -/// The architecture-independent per-CPU type. -pub type AxVMPerCpu = axvcpu::AxPerCpu>; - -/// Whether the hardware has virtualization support. -pub fn has_hardware_support() -> bool { - vcpu::has_hardware_support() -} + +pub fn enable_viretualization() -> anyhow::Result<()> { + vhal::init()?; + Ok(()) +} diff --git a/src/vcpu.rs b/src/vcpu.rs index 3e5aec9..24543aa 100644 --- a/src/vcpu.rs +++ b/src/vcpu.rs @@ -1,30 +1,31 @@ //! Architecture dependent vcpu implementations. -cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - pub use x86_vcpu::VmxArchVCpu as AxArchVCpuImpl; - pub use x86_vcpu::VmxArchPerCpuState as AxVMArchPerCpuImpl; - pub use x86_vcpu::has_hardware_support; - pub type AxVCpuCreateConfig = (); +// cfg_if::cfg_if! { +// if #[cfg(target_arch = "x86_64")] { +// pub use x86_vcpu::VmxArchVCpu as AxArchVCpuImpl; +// pub use x86_vcpu::VmxArchPerCpuState as AxVMArchPerCpuImpl; +// pub use x86_vcpu::has_hardware_support; +// pub type AxVCpuCreateConfig = (); - // Note: - // According to the requirements of `x86_vcpu`, - // users of the `x86_vcpu` crate need to implement the `PhysFrameIf` trait for it with the help of `crate_interface`. - // - // Since in our hypervisor architecture, `axvm` is not responsible for OS-related resource management, - // we leave the `PhysFrameIf` implementation to `vmm_app`. - } else if #[cfg(target_arch = "riscv64")] { - pub use riscv_vcpu::RISCVVCpu as AxArchVCpuImpl; - pub use riscv_vcpu::RISCVPerCpu as AxVMArchPerCpuImpl; - pub use riscv_vcpu::RISCVVCpuCreateConfig as AxVCpuCreateConfig; - pub use riscv_vcpu::has_hardware_support; - } else if #[cfg(target_arch = "aarch64")] { - pub use arm_vcpu::Aarch64VCpu as AxArchVCpuImpl; - pub use arm_vcpu::Aarch64PerCpu as AxVMArchPerCpuImpl; - pub use arm_vcpu::Aarch64VCpuCreateConfig as AxVCpuCreateConfig; - pub use arm_vcpu::Aarch64VCpuSetupConfig as AxVCpuSetupConfig; - pub use arm_vcpu::has_hardware_support; +// // Note: +// // According to the requirements of `x86_vcpu`, +// // users of the `x86_vcpu` crate need to implement the `PhysFrameIf` trait for it with the help of `crate_interface`. +// // +// // Since in our hypervisor architecture, `axvm` is not responsible for OS-related resource management, +// // we leave the `PhysFrameIf` implementation to `vmm_app`. +// } else if #[cfg(target_arch = "riscv64")] { +// pub use riscv_vcpu::RISCVVCpu as AxArchVCpuImpl; +// pub use riscv_vcpu::RISCVPerCpu as AxVMArchPerCpuImpl; +// pub use riscv_vcpu::RISCVVCpuCreateConfig as AxVCpuCreateConfig; +// pub use riscv_vcpu::has_hardware_support; +// } else if #[cfg(target_arch = "aarch64")] { +// pub use arm_vcpu::Aarch64VCpu as AxArchVCpuImpl; +// pub use arm_vcpu::Aarch64PerCpu as AxVMArchPerCpuImpl; +// + // pub use arm_vcpu::Aarch64VCpuCreateConfig as AxVCpuCreateConfig; +// pub use arm_vcpu::Aarch64VCpuSetupConfig as AxVCpuSetupConfig; +// pub use arm_vcpu::has_hardware_support; - pub use arm_vgic::vtimer::get_sysreg_device; - } -} +// pub use arm_vgic::vtimer::get_sysreg_device; +// } +// } diff --git a/src/vm.rs b/src/vm.rs index a2795b9..d8ba80a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,689 +1,36 @@ -use alloc::boxed::Box; -use alloc::format; -use alloc::sync::Arc; -use alloc::vec::Vec; -use axaddrspace::HostVirtAddr; -use axerrno::{AxError, AxResult, ax_err, ax_err_type}; -use core::alloc::Layout; -use core::sync::atomic::{AtomicBool, Ordering}; -use memory_addr::{align_down_4k, align_up_4k}; -use spin::{Mutex, Once}; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VmId(usize); -use axaddrspace::{AddrSpace, GuestPhysAddr, HostPhysAddr, MappingFlags, device::AccessWidth}; -use axdevice::{AxVmDeviceConfig, AxVmDevices}; -use axvcpu::{AxVCpu, AxVCpuExitReason, AxVCpuHal}; -use cpumask::CpuMask; - -use crate::config::{AxVMConfig, PhysCpuList}; -use crate::vcpu::{AxArchVCpuImpl, AxVCpuCreateConfig}; -use crate::{AxVMHal, has_hardware_support}; - -#[cfg(target_arch = "aarch64")] -use crate::vcpu::get_sysreg_device; - -const VM_ASPACE_BASE: usize = 0x0; -const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; - -/// A vCPU with architecture-independent interface. -#[allow(type_alias_bounds)] -type VCpu = AxVCpu>; -/// A reference to a vCPU. -#[allow(type_alias_bounds)] -pub type AxVCpuRef = Arc>; -/// A reference to a VM. -#[allow(type_alias_bounds)] -pub type AxVMRef = Arc>; // we know the bound is not enforced here, we keep it for clarity - -struct AxVMInnerConst { - phys_cpu_ls: PhysCpuList, - vcpu_list: Box<[AxVCpuRef]>, - devices: AxVmDevices, -} - -unsafe impl Send for AxVMInnerConst {} -unsafe impl Sync for AxVMInnerConst {} - -#[derive(Debug, Clone)] -pub struct VMMemoryRegion { - pub gpa: GuestPhysAddr, - pub hva: HostVirtAddr, - pub layout: Layout, -} - -impl VMMemoryRegion { - pub fn size(&self) -> usize { - self.layout.size() +impl VmId { + pub fn new(id: usize) -> Self { + VmId(id) } +} - pub fn is_identical(&self) -> bool { - self.gpa.as_usize() == self.hva.as_usize() +impl From for VmId { + fn from(value: usize) -> Self { + VmId(value) } } -struct AxVMInnerMut { - // Todo: use more efficient lock. - address_space: AddrSpace, - memory_regions: Vec, - config: AxVMConfig, - _marker: core::marker::PhantomData, +impl From for usize { + fn from(value: VmId) -> Self { + value.0 + } } -const TEMP_MAX_VCPU_NUM: usize = 64; - -/// A Virtual Machine. -pub struct AxVM { - id: usize, - running: AtomicBool, - shutting_down: AtomicBool, - inner_const: Once>, - inner_mut: Mutex>, +pub trait VmOps { + fn id(&self) -> VmId; + fn name(&self) -> &str; + fn boot(&mut self) -> anyhow::Result<()>; + fn stop(&self); + fn status(&self) -> Status; } -impl AxVM { - /// Creates a new VM with the given configuration. - /// Returns an error if the configuration is invalid. - /// The VM is not started until `boot` is called. - pub fn new(config: AxVMConfig) -> AxResult> { - let address_space = - AddrSpace::new_empty(4, GuestPhysAddr::from(VM_ASPACE_BASE), VM_ASPACE_SIZE)?; - - let result = Arc::new(Self { - id: config.id(), - running: AtomicBool::new(false), - shutting_down: AtomicBool::new(false), - inner_const: Once::new(), - inner_mut: Mutex::new(AxVMInnerMut { - address_space, - config, - memory_regions: Vec::new(), - _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(); - - 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: dtb_addr.unwrap_or_default().as_usize(), - }; - #[cfg(target_arch = "riscv64")] - let arch_config = AxVCpuCreateConfig { - hart_id: vcpu_id as _, - dtb_addr: dtb_addr.unwrap_or_default().as_usize(), - }; - #[cfg(target_arch = "x86_64")] - let arch_config = AxVCpuCreateConfig::default(); - - vcpu_list.push(Arc::new(VCpu::new( - self.id(), - vcpu_id, - 0, // Currently not used. - phys_cpu_set, - arch_config, - )?)); - } - - let mut pt_dev_region = Vec::new(); - for pt_device in inner_mut.config.pass_through_devices() { - trace!( - "PT dev {:?} region: [{:#x}~{:#x}] -> [{:#x}~{:#x}]", - pt_device.name, - pt_device.base_gpa, - pt_device.base_gpa + pt_device.length, - pt_device.base_hpa, - pt_device.base_hpa + pt_device.length - ); - // Align the base address and length to 4K boundaries. - pt_dev_region.push(( - align_down_4k(pt_device.base_gpa), - align_up_4k(pt_device.length), - )); - } - - 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. - let pt_dev_region = - pt_dev_region - .into_iter() - .fold(Vec::<(usize, usize)>::new(), |mut acc, (gpa, len)| { - if let Some(last) = acc.last_mut() { - if last.0 + last.1 >= gpa { - // Merge with the last region. - last.1 = (last.0 + last.1).max(gpa + len) - last.0; - } else { - acc.push((gpa, len)); - } - } else { - acc.push((gpa, len)); - } - acc - }); - - for (gpa, len) in &pt_dev_region { - inner_mut.address_space.map_linear( - GuestPhysAddr::from(*gpa), - HostPhysAddr::from(*gpa), - *len, - MappingFlags::DEVICE - | MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::USER, - )?; - } - - let mut devices = axdevice::AxVmDevices::new(AxVmDeviceConfig { - 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 = 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() { - if let Some(result) = axdevice_base::map_device_of_type( - device, - |gicd: &arm_vgic::v3::vgicd::VGicD| { - debug!("VGicD found, assigning SPIs..."); - - for spi in spis { - gicd.assign_irq(*spi + 32, cpu_id, (0, 0, 0, cpu_id as _)) - } - - Ok(()) - }, - ) { - result?; - gicd_found = true; - break; - } - } - - if !gicd_found { - warn!("Failed to assign SPIs: No VGicD found in device list"); - } - } else { - // non-passthrough mode, we need to set up the virtual timer. - // - // FIXME: maybe let `axdevice` handle this automatically? - // how to let `axdevice` know whether the VM is in passthrough mode or not? - for dev in get_sysreg_device() { - devices.add_sys_reg_dev(dev); - } - } - } - - self.inner_const.call_once(|| AxVMInnerConst { - phys_cpu_ls: inner_mut.config.phys_cpu_ls.clone(), - vcpu_list: vcpu_list.into_boxed_slice(), - devices, - }); - - // Setup VCpus. - 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, - } - }; - #[cfg(not(target_arch = "aarch64"))] - let setup_config = as axvcpu::AxArchVCpu>::SetupConfig::default(); - - let entry = if vcpu.id() == 0 { - inner_mut.config.bsp_entry() - } else { - inner_mut.config.ap_entry() - }; - - debug!("Setting up vCPU[{}] entry at {:#x}", vcpu.id(), entry); - - vcpu.setup( - entry, - inner_mut.address_space.page_table_root(), - setup_config, - )?; - } - info!("VM setup: id={}", self.id()); - Ok(()) - } - - /// Retrieves the vCPU corresponding to the given vcpu_id for the VM. - /// Returns None if the vCPU does not exist. - #[inline] - pub fn vcpu(&self, vcpu_id: usize) -> Option> { - self.vcpu_list().get(vcpu_id).cloned() - } - - /// Returns the number of vCPUs corresponding to the VM. - #[inline] - 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 - } - - /// Returns the base address of the two-stage address translation page table for the VM. - pub fn ept_root(&self) -> HostPhysAddr { - 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]>`, - /// according to the given `image_load_gpa` and `image_size. - /// `Vec<&'static mut [u8]>` is a series of (HVA) address segments, - /// which may correspond to non-contiguous physical addresses, - /// - /// FIXME: - /// Find a more elegant way to manage potentially non-contiguous physical memory - /// instead of `Vec<&'static mut [u8]>`. - pub fn get_image_load_region( - &self, - image_load_gpa: GuestPhysAddr, - image_size: usize, - ) -> AxResult> { - 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. - pub fn boot(&self) -> AxResult { - if !has_hardware_support() { - ax_err!(Unsupported, "Hardware does not support virtualization") - } else if self.running() { - ax_err!(BadState, format!("VM[{}] is already running", self.id())) - } else { - info!("Booting VM[{}]", self.id()); - self.running.store(true, Ordering::Relaxed); - Ok(()) - } - } - - /// Returns if the VM is shutting down. - pub fn shutting_down(&self) -> bool { - self.shutting_down.load(Ordering::Relaxed) - } - - /// Shuts down the VM by setting the shutting_down flag as true. - /// - /// 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()) - ) - } else { - info!("Shutting down VM[{}]", self.id()); - self.shutting_down.store(true, Ordering::Relaxed); - Ok(()) - } - } - - // TODO: implement suspend/resume. - // TODO: implement re-init. - - /// Returns this VM's emulated devices. - pub fn get_devices(&self) -> &AxVmDevices { - &self.inner_const().devices - } - - /// Run a vCPU according to the given vcpu_id. - /// - /// ## Arguments - /// * `vcpu_id` - the id of the vCPU to run. - /// - /// ## Returns - /// * `AxVCpuExitReason` - the exit reason of the vCPU, wrapped in an `AxResult`. - /// - pub fn run_vcpu(&self, vcpu_id: usize) -> AxResult { - let vcpu = self - .vcpu(vcpu_id) - .ok_or_else(|| ax_err_type!(InvalidInput, "Invalid vcpu_id"))?; - - vcpu.bind()?; - - let exit_reason = loop { - let exit_reason = vcpu.run()?; - trace!("{exit_reason:#x?}"); - let handled = match &exit_reason { - AxVCpuExitReason::MmioRead { - addr, - width, - reg, - reg_width: _, - signed_ext: _, - } => { - let val = self.get_devices().handle_mmio_read(*addr, *width)?; - vcpu.set_gpr(*reg, val); - true - } - AxVCpuExitReason::MmioWrite { addr, width, data } => { - self.get_devices() - .handle_mmio_write(*addr, *width, *data as usize)?; - true - } - AxVCpuExitReason::IoRead { port, width } => { - let val = self.get_devices().handle_port_read(*port, *width)?; - vcpu.set_gpr(0, val); // The target is always eax/ax/al, todo: handle access_width correctly - - true - } - AxVCpuExitReason::IoWrite { port, width, data } => { - self.get_devices() - .handle_port_write(*port, *width, *data as usize)?; - true - } - AxVCpuExitReason::SysRegRead { addr, reg } => { - let val = self.get_devices().handle_sys_reg_read( - *addr, - // Generally speaking, the width of system register is fixed and needless to be specified. - // AccessWidth::Qword here is just a placeholder, may be changed in the future. - AccessWidth::Qword, - )?; - vcpu.set_gpr(*reg, val); - true - } - AxVCpuExitReason::SysRegWrite { addr, value } => { - self.get_devices().handle_sys_reg_write( - *addr, - AccessWidth::Qword, - *value as usize, - )?; - true - } - AxVCpuExitReason::NestedPageFault { addr, access_flags } => self - .inner_mut - .lock() - .address_space - .handle_page_fault(*addr, *access_flags), - _ => false, - }; - if !handled { - break exit_reason; - } - }; - - vcpu.unbind()?; - Ok(exit_reason) - } - - /// Injects an interrupt to the vCPU. - pub fn inject_interrupt_to_vcpu( - &self, - targets: CpuMask, - irq: usize, - ) -> AxResult { - let vm_id = self.id(); - // Check if the current running vm is self. - // - // It is not supported to inject interrupt to a vcpu in another VM yet. - // - // It may be supported in the future, as a essential feature for cross-VM communication. - if H::current_vm_id() != self.id() { - panic!("Injecting interrupt to a vcpu in another VM is not supported"); - } - - for target_vcpu in &targets { - H::inject_irq_to_vcpu(vm_id, target_vcpu, irq)?; - } - - Ok(()) - } - - /// 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, - gpa: GuestPhysAddr, - hpa: HostPhysAddr, - size: usize, - flags: MappingFlags, - ) -> AxResult<()> { - self.inner_mut - .lock() - .address_space - .map_linear(gpa, hpa, size, flags) - } - - /// Unmaps a region of guest physical memory. - pub fn unmap_region(&self, gpa: GuestPhysAddr, size: usize) -> AxResult<()> { - self.inner_mut.lock().address_space.unmap(gpa, size) - } - - /// Reads an object of type `T` from the guest physical address. - pub fn read_from_guest_of(&self, gpa_ptr: GuestPhysAddr) -> AxResult { - let size = core::mem::size_of::(); - - // Ensure the address is properly aligned for the type. - if gpa_ptr.as_usize() % core::mem::align_of::() != 0 { - return ax_err!(InvalidInput, "Unaligned guest physical address"); - } - - 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 { - let remaining = size - data_bytes.len(); - let chunk_size = remaining.min(chunk.len()); - data_bytes.extend_from_slice(&chunk[..chunk_size]); - if data_bytes.len() >= size { - break; - } - } - if data_bytes.len() < size { - return ax_err!( - InvalidInput, - "Insufficient data in guest memory to read the requested object" - ); - } - let data: T = unsafe { - // Use `ptr::read_unaligned` for safety in case of unaligned memory. - core::ptr::read_unaligned(data_bytes.as_ptr() as *const T) - }; - Ok(data) - } - None => ax_err!( - InvalidInput, - "Failed to translate guest physical address or insufficient buffer size" - ), - } - } - - /// Writes an object of type `T` to the guest physical address. - pub fn write_to_guest_of(&self, gpa_ptr: GuestPhysAddr, data: &T) -> AxResult { - 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( - data as *const T as *const u8, - core::mem::size_of::(), - ) - }; - let mut copied_bytes = 0; - for chunk in buffer.iter_mut() { - let end = copied_bytes + chunk.len(); - chunk.copy_from_slice(&bytes[copied_bytes..end]); - copied_bytes += chunk.len(); - } - Ok(()) - } - None => ax_err!(InvalidInput, "Failed to translate guest physical address"), - } - } - - /// Allocates an IVC channel for inter-VM communication region. - /// - /// ## Arguments - /// * `expected_size` - The expected size of the IVC channel in bytes. - /// ## Returns - /// * `AxResult<(GuestPhysAddr, usize)>` - A tuple containing the guest physical address of the allocated IVC channel and its actual size. - 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)?; - Ok((gpa, size)) - } - - /// Releases an IVC channel for inter-VM communication region. - /// ## Arguments - /// * `gpa` - The guest physical address of the IVC channel to release. - /// * `size` - The size of the IVC channel in bytes. - /// ## 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) - } - - 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 }); - - Ok(s) - } - - pub fn memory_regions(&self) -> Vec { - self.inner_mut.lock().memory_regions.clone() - } - - 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 }); - Ok(s) - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + Idle, + Running, + ShuttingDown, + PoweredOff, } diff --git a/src/vm2.rs b/src/vm2.rs deleted file mode 100644 index d8ba80a..0000000 --- a/src/vm2.rs +++ /dev/null @@ -1,36 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct VmId(usize); - -impl VmId { - pub fn new(id: usize) -> Self { - VmId(id) - } -} - -impl From for VmId { - fn from(value: usize) -> Self { - VmId(value) - } -} - -impl From for usize { - fn from(value: VmId) -> Self { - value.0 - } -} - -pub trait VmOps { - fn id(&self) -> VmId; - fn name(&self) -> &str; - fn boot(&mut self) -> anyhow::Result<()>; - fn stop(&self); - fn status(&self) -> Status; -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Status { - Idle, - Running, - ShuttingDown, - PoweredOff, -} From e8a7feafdc017e9c4abf64a9497004149ed61c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 14 Nov 2025 15:09:40 +0800 Subject: [PATCH 18/74] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Cargo.toml=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84=E4=BE=9D=E8=B5=96=E9=A1=B9?= =?UTF-8?q?=E5=B9=B6=E8=B0=83=E6=95=B4=E7=8E=B0=E6=9C=89=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=A1=B9=EF=BC=9B=E9=87=8D=E6=9E=84=20vhal=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=AE=9A=E6=97=B6=E5=99=A8?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=B9=B6=E7=A7=BB=E9=99=A4=E6=97=A7=E7=9A=84?= =?UTF-8?q?=20vhal=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 14 +++-- src/arch/aarch64/cpu.rs | 1 - src/lib.rs | 10 ++-- src/vhal.rs | 66 ----------------------- src/vhal/mod.rs | 112 +++++++++++++++++++++++++++++++++++++++ src/vhal/timer.rs | 113 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 74 deletions(-) delete mode 100644 src/vhal.rs create mode 100644 src/vhal/mod.rs create mode 100644 src/vhal/timer.rs diff --git a/Cargo.toml b/Cargo.toml index db563ba..a431822 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,18 +15,18 @@ anyhow = {version = "1.0", default-features = false} cfg-if = "1.0" log = "0.4" spin = "0.10" -axhal.workspace = true -axruntime.workspace = true +timer_list = "0.1" fdt-parser = "0.5" +lazyinit = "0.2" # System independent crates provided by ArceOS. axerrno = "0.1.0" cpumask = "0.1.0" -# kspin = "0.1.0" +kspin = "0.1" 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"]} +percpu = {version = "0.2", features = ["arm-el2"]} # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" @@ -35,6 +35,12 @@ axaddrspace = "0.2" # axvcpu = "0.1" axvmconfig = {version = "0.1", default-features = false} +axhal.workspace = true +axruntime.workspace = true +axtask.workspace = true + + + [target.'cfg(target_arch = "x86_64")'.dependencies] # x86_vcpu = "0.1" diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index a596d98..09762d0 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -11,6 +11,5 @@ struct PreCpu; pub fn init() -> anyhow::Result<()> { PRE_CPU.init(); - todo!(); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 094dbfc..17745a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,26 +9,30 @@ //! This crate contains: //! - [`AxVM`]: The main structure representing a VM. +#[macro_use] extern crate alloc; #[macro_use] extern crate log; +#[macro_use] +extern crate anyhow; + +const TASK_STACK_SIZE: usize = 0x4000; // 16KB #[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")] #[cfg_attr(target_arch = "x86_64", path = "arch/x86_64/mod.rs")] pub mod arch; mod fdt; - mod vcpu; mod vm; pub mod config; pub mod vhal; -use anyhow::bail; - pub fn enable_viretualization() -> anyhow::Result<()> { vhal::init()?; + + panic!(); Ok(()) } diff --git a/src/vhal.rs b/src/vhal.rs deleted file mode 100644 index a9d03df..0000000 --- a/src/vhal.rs +++ /dev/null @@ -1,66 +0,0 @@ -use core::cell::UnsafeCell; - -use alloc::{collections::btree_map::BTreeMap, vec::Vec}; - -use crate::arch::{self, Hal}; - -pub fn init() -> anyhow::Result<()> { - Hal::init() -} - -pub(crate) trait ArchHal { - fn init() -> anyhow::Result<()>; - fn cpu_list() -> Vec; - fn current_enable_viretualization() -> anyhow::Result<()>; -} - -pub fn current_enable_viretualization() -> anyhow::Result<()> { - Hal::current_enable_viretualization() -} - -pub(crate) struct PreCpuSet(UnsafeCell>>); - -unsafe impl Sync for PreCpuSet {} -unsafe impl Send for PreCpuSet {} - -impl PreCpuSet { - pub const fn new() -> Self { - PreCpuSet(UnsafeCell::new(BTreeMap::new())) - } - - unsafe fn set(&self, cpu_id: usize, val: T) { - let pre_cpu_map = unsafe { &mut *self.0.get() }; - pre_cpu_map.insert(cpu_id, Some(val)); - } - - pub fn get(&self, cpu_id: usize) -> Option<&T> { - let pre_cpu_map = unsafe { &*self.0.get() }; - let v = pre_cpu_map.get(&cpu_id)?; - Some(v.as_ref().expect("init not called")) - } - - pub fn init(&self) { - let cpu_list = Hal::cpu_list(); - debug!("Initializing PreCpuSet for CPUs: {:?}", cpu_list); - for cpu_id in cpu_list { - unsafe { - let v = unsafe { &mut *self.0.get() }; - v.insert(cpu_id.raw(), None); - } - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct CpuHardId(usize); - -impl CpuHardId { - pub fn new(id: usize) -> Self { - CpuHardId(id) - } - - pub fn raw(&self) -> usize { - self.0 - } -} diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs new file mode 100644 index 0000000..c4e52ef --- /dev/null +++ b/src/vhal/mod.rs @@ -0,0 +1,112 @@ +use core::{ + cell::UnsafeCell, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; +use axtask::AxCpuMask; + +use crate::{ + TASK_STACK_SIZE, + arch::{self, Hal}, +}; + +mod timer; + +pub fn init() -> anyhow::Result<()> { + Hal::init(); + + static CORES: AtomicUsize = AtomicUsize::new(0); + + let cpu_count = cpu_count(); + + info!("Initializing VHal for {cpu_count} CPUs..."); + + for cpu_id in 0..cpu_count { + let _handle = axtask::spawn_raw( + move || { + info!("Core {cpu_id} is initializing hardware virtualization support..."); + // Initialize cpu affinity here. + assert!( + axtask::set_current_affinity(AxCpuMask::one_shot(cpu_id)), + "Initialize CPU affinity failed!" + ); + info!("Enabling hardware virtualization support on core {cpu_id}"); + timer::init_percpu(); + + let _ = CORES.fetch_add(1, Ordering::Release); + }, + format!("init-cpu-{}", cpu_id), + TASK_STACK_SIZE, + ); + } + info!("Waiting for all cores to enable hardware virtualization..."); + + // Wait for all cores to enable virtualization. + while CORES.load(Ordering::Acquire) != cpu_count { + // Use `yield_now` instead of `core::hint::spin_loop` to avoid deadlock. + axtask::yield_now(); + } + + info!("All cores have enabled hardware virtualization support."); + Ok(()) +} + +pub fn cpu_count() -> usize { + axruntime::cpu_count() +} + +pub(crate) trait ArchHal { + fn init() -> anyhow::Result<()>; + fn cpu_list() -> Vec; + fn current_enable_viretualization() -> anyhow::Result<()>; +} + +pub fn current_enable_viretualization() -> anyhow::Result<()> { + Hal::current_enable_viretualization() +} + +pub(crate) struct PreCpuSet(UnsafeCell>>); + +unsafe impl Sync for PreCpuSet {} +unsafe impl Send for PreCpuSet {} + +impl PreCpuSet { + pub const fn new() -> Self { + PreCpuSet(UnsafeCell::new(BTreeMap::new())) + } + + unsafe fn set(&self, cpu_id: usize, val: T) { + let pre_cpu_map = unsafe { &mut *self.0.get() }; + pre_cpu_map.insert(cpu_id, Some(val)); + } + + pub fn get(&self, cpu_id: usize) -> Option<&T> { + let pre_cpu_map = unsafe { &*self.0.get() }; + let v = pre_cpu_map.get(&cpu_id)?; + Some(v.as_ref().expect("init not called")) + } + + pub fn init(&self) { + let cpu_list = Hal::cpu_list(); + debug!("Initializing PreCpuSet for CPUs: {:?}", cpu_list); + for cpu_id in cpu_list { + let v = unsafe { &mut *self.0.get() }; + v.insert(cpu_id.raw(), None); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct CpuHardId(usize); + +impl CpuHardId { + pub fn new(id: usize) -> Self { + CpuHardId(id) + } + + pub fn raw(&self) -> usize { + self.0 + } +} diff --git a/src/vhal/timer.rs b/src/vhal/timer.rs new file mode 100644 index 0000000..d8a6bfe --- /dev/null +++ b/src/vhal/timer.rs @@ -0,0 +1,113 @@ +use core::sync::atomic::AtomicUsize; +use core::sync::atomic::Ordering; + +use axhal; + +use alloc::boxed::Box; +use kspin::SpinNoIrq; +use lazyinit::LazyInit; +use timer_list::{TimeValue, TimerEvent, TimerList}; + +static TOKEN: AtomicUsize = AtomicUsize::new(0); +// const PERIODIC_INTERVAL_NANOS: u64 = axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64; + +/// Represents a timer event in the virtual machine monitor (VMM). +/// +/// This struct holds a unique token for the timer and a callback function +/// that will be executed when the timer expires. +pub struct VmmTimerEvent { + // Unique identifier for the timer event + token: usize, + // Callback function to be executed when the timer expires + timer_callback: Box, +} + +impl VmmTimerEvent { + fn new(token: usize, f: F) -> Self + where + F: FnOnce(TimeValue) + Send + 'static, + { + Self { + token, + timer_callback: Box::new(f), + } + } +} + +impl TimerEvent for VmmTimerEvent { + fn callback(self, now: TimeValue) { + (self.timer_callback)(now) + } +} + +#[percpu::def_percpu] +static TIMER_LIST: LazyInit>> = LazyInit::new(); + +/// Registers a new timer that will execute at the specified deadline +/// +/// # Arguments +/// - `deadline`: The absolute time in nanoseconds when the timer should trigger +/// - `handler`: The callback function to execute when the timer expires +/// +/// # Returns +/// A unique token that can be used to cancel this timer later +pub fn register_timer(deadline: u64, handler: F) -> usize +where + F: FnOnce(TimeValue) + Send + 'static, +{ + trace!("Registering timer..."); + trace!( + "deadline is {:#?} = {:#?}", + deadline, + TimeValue::from_nanos(deadline) + ); + let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; + let mut timers = timer_list.lock(); + let token = TOKEN.fetch_add(1, Ordering::Release); + let event = VmmTimerEvent::new(token, handler); + timers.set(TimeValue::from_nanos(deadline), event); + token +} + +/// Cancels a timer with the specified token. +/// +/// # Parameters +/// - `token`: The unique token of the timer to cancel. +pub fn cancel_timer(token: usize) { + let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; + let mut timers = timer_list.lock(); + timers.cancel(|event| event.token == token); +} + +/// Check and process any pending timer events +pub fn check_events() { + // info!("Checking timer events..."); + // info!("now is {:#?}", axhal::time::wall_time()); + let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; + loop { + let now = axhal::time::wall_time(); + let event = timer_list.lock().expire_one(now); + if let Some((_deadline, event)) = event { + trace!("pick one {_deadline:#?} to handle!!!"); + event.callback(now); + } else { + break; + } + } +} + +// /// Schedule the next timer event based on the periodic interval +// pub fn scheduler_next_event() { +// trace!("Scheduling next event..."); +// let now_ns = axhal::time::monotonic_time_nanos(); +// let deadline = now_ns + PERIODIC_INTERVAL_NANOS; +// debug!("PHY deadline {} !!!", deadline); +// axhal::time::set_oneshot_timer(deadline); +// } + +/// Initialize the hypervisor timer system +pub fn init_percpu() { + info!("Initing HV Timer..."); + let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; + timer_list.init_once(SpinNoIrq::new(TimerList::new())); +} From bf4ab3ae8d0ba6b06d3a5c1033c4aadc2901c66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 14 Nov 2025 16:01:56 +0800 Subject: [PATCH 19/74] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Cargo.toml=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20aarch64-cpu=20=E4=BE=9D=E8=B5=96=EF=BC=9B?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20AArch64=20=E6=9E=B6=E6=9E=84=E7=9A=84=20CP?= =?UTF-8?q?U=20=E6=A8=A1=E5=9D=97=EF=BC=8C=E6=96=B0=E5=A2=9E=20CpuData=20?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BD=93=E5=B9=B6=E5=AE=9E=E7=8E=B0=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/arch/aarch64/cpu.rs | 29 +++++++++++++---- src/arch/aarch64/mod.rs | 28 ++++++++-------- src/vhal/mod.rs | 71 ++++++++++++++++++++++------------------- src/vhal/precpu.rs | 46 ++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 53 deletions(-) create mode 100644 src/vhal/precpu.rs diff --git a/Cargo.toml b/Cargo.toml index a431822..ba405c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ axtask.workspace = true # riscv_vcpu = "0.1" [target.'cfg(target_arch = "aarch64")'.dependencies] +aarch64-cpu = "11.0" aarch64-cpu-ext = "0.1" arm_vcpu = "0.1" # arm_vgic = {version = "0.1", features = ["vgicv3"]} diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 09762d0..364c0c9 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -1,15 +1,30 @@ +use aarch64_cpu::registers::*; use axhal::percpu::this_cpu_id; use crate::{ - fdt::{self, fdt}, - vhal::{ArchHal, PreCpuSet}, + fdt, + vhal::{ArchCpuData, ArchHal, CpuHardId, CpuId, precpu::PreCpuSet}, }; -static PRE_CPU: PreCpuSet = PreCpuSet::new(); +pub struct CpuData { + pub id: CpuId, + pub hard_id: CpuHardId, +} + +impl CpuData { + pub fn new(id: CpuId) -> Self { + let mpidr = MPIDR_EL1.get() as usize; + let hard_id = mpidr & 0xff_ff_ff; -struct PreCpu; + CpuData { + id, + hard_id: CpuHardId::new(hard_id), + } + } +} -pub fn init() -> anyhow::Result<()> { - PRE_CPU.init(); - Ok(()) +impl ArchCpuData for CpuData { + fn hard_id(&self) -> crate::vhal::CpuHardId { + self.hard_id + } } diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 4c2ef0c..4afeb2d 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,3 +1,4 @@ +use aarch64_cpu::registers::MPIDR_EL1; use axhal::percpu::this_cpu_id; use core::fmt; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -10,8 +11,9 @@ use crate::alloc::sync::Arc; use crate::alloc::vec; use crate::alloc::vec::Vec; use crate::fdt; -use crate::vhal::ArchHal; +use crate::vhal::{ArchHal, CpuId}; +use aarch64_cpu::registers::{ReadWriteable, Writeable, Readable}; use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, MappingFlags}; use axerrno::{AxResult, ax_err}; use page_table_multiarch::PagingHandler; @@ -20,18 +22,18 @@ use crate::{config::AxVMConfig, vm::*}; pub mod cpu; +pub use cpu::CpuData; + pub struct Hal; impl ArchHal for Hal { - fn current_enable_viretualization() -> anyhow::Result<()> { - let cpu_id = this_cpu_id(); - info!("Enabling virtualization on cpu [{cpu_id:#x}]"); + fn current_cpu_init(id: CpuId) -> anyhow::Result { + info!("Enabling virtualization on cpu {id}"); - Ok(()) + Ok(CpuData::new(id)) } fn init() -> anyhow::Result<()> { - cpu::init(); Ok(()) } @@ -39,15 +41,15 @@ impl ArchHal for Hal { fdt::cpu_list() .unwrap() .into_iter() - .map(|id| crate::vhal::CpuHardId::new(id)) + .map(crate::vhal::CpuHardId::new) .collect() } -} -/// A virtual CPU with architecture-independent interface. -// type VCpu = AxVCpu>; -/// A reference to a vCPU. -// pub type AxVCpuRef = Arc>; + fn cpu_hard_id() -> crate::vhal::CpuHardId { + let mpidr = MPIDR_EL1.get() as usize; + crate::vhal::CpuHardId::new(mpidr) + } +} // Implement Display for VmId impl fmt::Display for VmId { @@ -578,7 +580,7 @@ impl Vm { // info!(" Devices: {}", self.get_devices().len()); // if let Some(root) = self.page_table_root() { - // info!(" Page Table Root: {:#x}", root); + // info!(" Page Table Root: {:#x}", root); // } } } diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index c4e52ef..587e151 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -1,28 +1,35 @@ +use alloc::{collections::BTreeMap, vec::Vec}; use core::{ cell::UnsafeCell, + fmt::Display, sync::atomic::{AtomicUsize, Ordering}, }; -use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use axtask::AxCpuMask; use crate::{ TASK_STACK_SIZE, - arch::{self, Hal}, + arch::{CpuData, Hal}, + vhal::precpu::PreCpuSet, }; +pub(crate) mod precpu; mod timer; +static PRE_CPU: PreCpuSet = PreCpuSet::new(); + pub fn init() -> anyhow::Result<()> { - Hal::init(); + Hal::init()?; static CORES: AtomicUsize = AtomicUsize::new(0); let cpu_count = cpu_count(); info!("Initializing VHal for {cpu_count} CPUs..."); + PRE_CPU.init(); for cpu_id in 0..cpu_count { + let id = CpuId::new(cpu_id); let _handle = axtask::spawn_raw( move || { info!("Core {cpu_id} is initializing hardware virtualization support..."); @@ -31,9 +38,11 @@ pub fn init() -> anyhow::Result<()> { axtask::set_current_affinity(AxCpuMask::one_shot(cpu_id)), "Initialize CPU affinity failed!" ); - info!("Enabling hardware virtualization support on core {cpu_id}"); + info!("Enabling hardware virtualization support on core {id}"); timer::init_percpu(); + let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); + unsafe { PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; let _ = CORES.fetch_add(1, Ordering::Release); }, format!("init-cpu-{}", cpu_id), @@ -58,55 +67,51 @@ pub fn cpu_count() -> usize { pub(crate) trait ArchHal { fn init() -> anyhow::Result<()>; + fn cpu_hard_id() -> CpuHardId; fn cpu_list() -> Vec; - fn current_enable_viretualization() -> anyhow::Result<()>; + fn current_cpu_init(id: CpuId) -> anyhow::Result; } -pub fn current_enable_viretualization() -> anyhow::Result<()> { - Hal::current_enable_viretualization() +pub(crate) trait ArchCpuData { + fn hard_id(&self) -> CpuHardId; } -pub(crate) struct PreCpuSet(UnsafeCell>>); - -unsafe impl Sync for PreCpuSet {} -unsafe impl Send for PreCpuSet {} - -impl PreCpuSet { - pub const fn new() -> Self { - PreCpuSet(UnsafeCell::new(BTreeMap::new())) - } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct CpuHardId(usize); - unsafe fn set(&self, cpu_id: usize, val: T) { - let pre_cpu_map = unsafe { &mut *self.0.get() }; - pre_cpu_map.insert(cpu_id, Some(val)); +impl CpuHardId { + pub fn new(id: usize) -> Self { + CpuHardId(id) } - pub fn get(&self, cpu_id: usize) -> Option<&T> { - let pre_cpu_map = unsafe { &*self.0.get() }; - let v = pre_cpu_map.get(&cpu_id)?; - Some(v.as_ref().expect("init not called")) + pub fn raw(&self) -> usize { + self.0 } +} - pub fn init(&self) { - let cpu_list = Hal::cpu_list(); - debug!("Initializing PreCpuSet for CPUs: {:?}", cpu_list); - for cpu_id in cpu_list { - let v = unsafe { &mut *self.0.get() }; - v.insert(cpu_id.raw(), None); - } +impl Display for CpuHardId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CPU Hard({:#x})", self.0) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct CpuHardId(usize); +pub struct CpuId(usize); -impl CpuHardId { +impl CpuId { pub fn new(id: usize) -> Self { - CpuHardId(id) + CpuId(id) } pub fn raw(&self) -> usize { self.0 } } + +impl Display for CpuId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CPU({})", self.0) + } +} diff --git a/src/vhal/precpu.rs b/src/vhal/precpu.rs new file mode 100644 index 0000000..c6c02ed --- /dev/null +++ b/src/vhal/precpu.rs @@ -0,0 +1,46 @@ +use alloc::collections::BTreeMap; +use core::{cell::UnsafeCell, ops::Deref}; + +use crate::{ + arch::Hal, + vhal::{ArchHal, CpuHardId}, +}; + +pub(crate) struct PreCpuSet(UnsafeCell>>); + +unsafe impl Sync for PreCpuSet {} +unsafe impl Send for PreCpuSet {} + +impl PreCpuSet { + pub const fn new() -> Self { + PreCpuSet(UnsafeCell::new(BTreeMap::new())) + } + + pub unsafe fn set(&self, cpu_id: CpuHardId, val: T) { + let pre_cpu_map = unsafe { &mut *self.0.get() }; + pre_cpu_map.insert(cpu_id, Some(val)); + } + + pub fn init(&self) { + let cpu_list = Hal::cpu_list(); + debug!("Initializing PreCpuSet for CPUs: {:?}", cpu_list); + for cpu_id in cpu_list { + let v = unsafe { &mut *self.0.get() }; + v.insert(cpu_id, None); + } + } +} + +impl Deref for PreCpuSet { + type Target = T; + + fn deref(&self) -> &Self::Target { + let set = unsafe { &*self.0.get() }; + let cpu_id = Hal::cpu_hard_id(); + let cpu_data = set + .get(&cpu_id) + .and_then(|data| data.as_ref()) + .expect("CPU data not initialized!"); + cpu_data + } +} From c784a55ddd1c9a054a85034b8c4283d63265933a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 17 Nov 2025 10:59:17 +0800 Subject: [PATCH 20/74] =?UTF-8?q?=E9=87=8D=E6=9E=84=20AArch64=20CPU=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=EF=BC=8C=E6=96=B0=E5=A2=9E=20vpercpu=20?= =?UTF-8?q?=E5=92=8C=20max=5Fguest=5Fpage=5Ftable=5Flevels=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=9B=E5=AE=9E=E7=8E=B0=20CpuData=20=E7=9A=84?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=92=8C=E6=98=BE=E7=A4=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=9B=E6=9B=B4=E6=96=B0=20Hal=20=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E8=99=9A=E6=8B=9F=E5=8C=96=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 44 +++++++++++++++++++++++++++++++++++++++++ src/arch/aarch64/mod.rs | 9 ++++++--- src/vhal/mod.rs | 2 +- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 364c0c9..a69c0e9 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -1,4 +1,7 @@ +use core::fmt::Display; + use aarch64_cpu::registers::*; +use arm_vcpu::Aarch64PerCpu; use axhal::percpu::this_cpu_id; use crate::{ @@ -9,6 +12,8 @@ use crate::{ pub struct CpuData { pub id: CpuId, pub hard_id: CpuHardId, + vpercpu: Aarch64PerCpu, + max_guest_page_table_levels: usize, } impl CpuData { @@ -16,11 +21,25 @@ impl CpuData { let mpidr = MPIDR_EL1.get() as usize; let hard_id = mpidr & 0xff_ff_ff; + let vpercpu = Aarch64PerCpu::new(); + CpuData { id, hard_id: CpuHardId::new(hard_id), + vpercpu, + max_guest_page_table_levels: 0, } } + + pub fn init(&mut self) -> anyhow::Result<()> { + self.vpercpu.hardware_enable(); + self.max_guest_page_table_levels = self.vpercpu.max_guest_page_table_levels(); + Ok(()) + } + + pub fn max_guest_page_table_levels(&self) -> usize { + self.max_guest_page_table_levels + } } impl ArchCpuData for CpuData { @@ -28,3 +47,28 @@ impl ArchCpuData for CpuData { self.hard_id } } + +impl Display for CpuData { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + " +CPU {}: + Hard ID: {} + PT Levels: {}", + self.id, self.hard_id, self.max_guest_page_table_levels + ) + } +} + +pub(super) struct VCpuHal; + +impl arm_vcpu::CpuHal for VCpuHal { + fn irq_hanlder(&self) { + axhal::irq::irq_handler(0); + } + + fn inject_interrupt(&self, irq: usize) { + todo!() + } +} \ No newline at end of file diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 4afeb2d..6f2d8ea 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -13,7 +13,7 @@ use crate::alloc::vec::Vec; use crate::fdt; use crate::vhal::{ArchHal, CpuId}; -use aarch64_cpu::registers::{ReadWriteable, Writeable, Readable}; +use aarch64_cpu::registers::{ReadWriteable, Readable, Writeable}; use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, MappingFlags}; use axerrno::{AxResult, ax_err}; use page_table_multiarch::PagingHandler; @@ -29,11 +29,14 @@ pub struct Hal; impl ArchHal for Hal { fn current_cpu_init(id: CpuId) -> anyhow::Result { info!("Enabling virtualization on cpu {id}"); - - Ok(CpuData::new(id)) + let mut cpu = CpuData::new(id); + cpu.init()?; + info!("{cpu}"); + Ok(cpu) } fn init() -> anyhow::Result<()> { + arm_vcpu::init_hal(&cpu::VCpuHal); Ok(()) } diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index 587e151..14dba28 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -42,7 +42,7 @@ pub fn init() -> anyhow::Result<()> { timer::init_percpu(); let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); - unsafe { PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; + // unsafe { PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; let _ = CORES.fetch_add(1, Ordering::Release); }, format!("init-cpu-{}", cpu_id), From f280d08be18d0793ecd0babd254117575f3019a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 18 Nov 2025 10:11:49 +0800 Subject: [PATCH 21/74] update --- src/vhal/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index 14dba28..e5e1da7 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -42,12 +42,13 @@ pub fn init() -> anyhow::Result<()> { timer::init_percpu(); let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); - // unsafe { PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; + unsafe { PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; let _ = CORES.fetch_add(1, Ordering::Release); }, format!("init-cpu-{}", cpu_id), TASK_STACK_SIZE, ); + // _handle.join(); } info!("Waiting for all cores to enable hardware virtualization..."); From 5b74a371378eae70550dfc0534b185a5bcf76175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 19 Nov 2025 14:11:54 +0800 Subject: [PATCH 22/74] =?UTF-8?q?=E7=A7=BB=E9=99=A4=20enable=5Fviretualiza?= =?UTF-8?q?tion=20=E5=87=BD=E6=95=B0=E4=B8=AD=E7=9A=84=20panic!=20?= =?UTF-8?q?=E8=B0=83=E7=94=A8=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=B3=A8=E9=87=8A=E4=BB=A5=E8=AF=B4=E6=98=8E=E7=A1=AC?= =?UTF-8?q?=E4=BB=B6=E8=99=9A=E6=8B=9F=E5=8C=96=E6=94=AF=E6=8C=81=E7=9A=84?= =?UTF-8?q?=E5=90=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 17745a9..6919ddb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,10 +29,8 @@ mod vm; pub mod config; pub mod vhal; - +/// Enable hardware virtualization support. pub fn enable_viretualization() -> anyhow::Result<()> { vhal::init()?; - - panic!(); Ok(()) } From 61497b0ae83e02dc2790f56ebcbfaace1f97b6ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 19 Nov 2025 15:16:56 +0800 Subject: [PATCH 23/74] fix task_stack_size --- Cargo.toml | 10 ++++------ src/arch/aarch64/cpu.rs | 2 +- src/lib.rs | 2 +- src/vcpu.rs | 4 ++-- src/vhal/mod.rs | 11 ++++++----- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ba405c4..a072ffa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,11 @@ vmx = [] [dependencies] anyhow = {version = "1.0", default-features = false} cfg-if = "1.0" +fdt-parser = "0.5" +lazyinit = "0.2" log = "0.4" spin = "0.10" timer_list = "0.1" -fdt-parser = "0.5" -lazyinit = "0.2" # System independent crates provided by ArceOS. axerrno = "0.1.0" @@ -34,13 +34,11 @@ axaddrspace = "0.2" # axdevice_base = "0.1" # axvcpu = "0.1" axvmconfig = {version = "0.1", default-features = false} - +axconfig = {workspace = true} axhal.workspace = true axruntime.workspace = true axtask.workspace = true - - [target.'cfg(target_arch = "x86_64")'.dependencies] # x86_vcpu = "0.1" @@ -54,6 +52,6 @@ arm_vcpu = "0.1" # arm_vgic = {version = "0.1", features = ["vgicv3"]} [patch.crates-io] +arm_vcpu = {git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next"} axvcpu = {git = "https://github.com/arceos-hypervisor/axvcpu.git", branch = "next"} axvmconfig = {git = "https://github.com/arceos-hypervisor/axvmconfig.git", branch = "next"} -arm_vcpu = {git = "https://github.com/arceos-hypervisor/arm_vcpu", branch = "next"} diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index a69c0e9..462d6db 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -71,4 +71,4 @@ impl arm_vcpu::CpuHal for VCpuHal { fn inject_interrupt(&self, irq: usize) { todo!() } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 6919ddb..f6b151b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ extern crate log; #[macro_use] extern crate anyhow; -const TASK_STACK_SIZE: usize = 0x4000; // 16KB +const TASK_STACK_SIZE: usize = 0x40000; // 16KB #[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")] #[cfg_attr(target_arch = "x86_64", path = "arch/x86_64/mod.rs")] diff --git a/src/vcpu.rs b/src/vcpu.rs index 24543aa..95d970a 100644 --- a/src/vcpu.rs +++ b/src/vcpu.rs @@ -21,8 +21,8 @@ // } else if #[cfg(target_arch = "aarch64")] { // pub use arm_vcpu::Aarch64VCpu as AxArchVCpuImpl; // pub use arm_vcpu::Aarch64PerCpu as AxVMArchPerCpuImpl; -// - // pub use arm_vcpu::Aarch64VCpuCreateConfig as AxVCpuCreateConfig; +// +// pub use arm_vcpu::Aarch64VCpuCreateConfig as AxVCpuCreateConfig; // pub use arm_vcpu::Aarch64VCpuSetupConfig as AxVCpuSetupConfig; // pub use arm_vcpu::has_hardware_support; diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index e5e1da7..ae2f635 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -6,9 +6,9 @@ use core::{ }; use axtask::AxCpuMask; - +use axconfig::TASK_STACK_SIZE; use crate::{ - TASK_STACK_SIZE, + arch::{CpuData, Hal}, vhal::precpu::PreCpuSet, }; @@ -27,7 +27,6 @@ pub fn init() -> anyhow::Result<()> { info!("Initializing VHal for {cpu_count} CPUs..."); PRE_CPU.init(); - for cpu_id in 0..cpu_count { let id = CpuId::new(cpu_id); let _handle = axtask::spawn_raw( @@ -48,7 +47,7 @@ pub fn init() -> anyhow::Result<()> { format!("init-cpu-{}", cpu_id), TASK_STACK_SIZE, ); - // _handle.join(); + // handles.push(_handle); } info!("Waiting for all cores to enable hardware virtualization..."); @@ -57,7 +56,9 @@ pub fn init() -> anyhow::Result<()> { // Use `yield_now` instead of `core::hint::spin_loop` to avoid deadlock. axtask::yield_now(); } - + // for handle in handles { + // handle.join(); + // } info!("All cores have enabled hardware virtualization support."); Ok(()) } From 9adb387f2671d0f397e23ba97e097276abb2457f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 24 Nov 2025 17:35:08 +0800 Subject: [PATCH 24/74] Refactor AArch64 architecture: Introduce HCpu struct, update VM handling, and enhance Cargo.toml dependencies - Replaced CpuData with HCpu struct in the AArch64 CPU module to encapsulate CPU-related data. - Updated the current_cpu_init function to return HCpu instead of CpuData. - Added a new ArchVm struct for AArch64 virtual machine implementation, encapsulating VM state and management. - Introduced a Mutex-protected inner field in the Vm struct to manage the ArchVm instance safely. - Enhanced Cargo.toml by adding vm-allocator.workspace dependency for improved memory management. - Refactored vhal module to utilize HCpu and updated related functions for consistency. --- Cargo.toml | 1 + src/arch/aarch64/cpu.rs | 29 ++- src/arch/aarch64/mod.rs | 468 +--------------------------------------- src/arch/aarch64/vm.rs | 459 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 +- src/vhal/mod.rs | 44 +++- src/vm.rs | 37 ++++ 7 files changed, 568 insertions(+), 475 deletions(-) create mode 100644 src/arch/aarch64/vm.rs diff --git a/Cargo.toml b/Cargo.toml index a072ffa..c4ac6c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ memory_addr = "0.4" page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" percpu = {version = "0.2", features = ["arm-el2"]} +vm-allocator.workspace = true # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 462d6db..687a89d 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -1,6 +1,7 @@ use core::fmt::Display; use aarch64_cpu::registers::*; +use alloc::sync::Weak; use arm_vcpu::Aarch64PerCpu; use axhal::percpu::this_cpu_id; @@ -9,21 +10,21 @@ use crate::{ vhal::{ArchCpuData, ArchHal, CpuHardId, CpuId, precpu::PreCpuSet}, }; -pub struct CpuData { +pub struct HCpu { pub id: CpuId, pub hard_id: CpuHardId, vpercpu: Aarch64PerCpu, max_guest_page_table_levels: usize, } -impl CpuData { +impl HCpu { pub fn new(id: CpuId) -> Self { let mpidr = MPIDR_EL1.get() as usize; let hard_id = mpidr & 0xff_ff_ff; let vpercpu = Aarch64PerCpu::new(); - CpuData { + HCpu { id, hard_id: CpuHardId::new(hard_id), vpercpu, @@ -42,13 +43,13 @@ impl CpuData { } } -impl ArchCpuData for CpuData { +impl ArchCpuData for HCpu { fn hard_id(&self) -> crate::vhal::CpuHardId { self.hard_id } } -impl Display for CpuData { +impl Display for HCpu { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, @@ -72,3 +73,21 @@ impl arm_vcpu::CpuHal for VCpuHal { todo!() } } + +pub struct VCpu { + pub v_hard_id: CpuHardId, + pub vcpu: arm_vcpu::Aarch64VCpu, + hcpu: CpuHardId, +} + +impl VCpu { + // pub fn new(config: &) -> Self { + // let vcpu = arm_vcpu::Aarch64VCpu::new(VCpuHal); + + // VCpu { + // v_hard_id, + // vcpu, + // hcpu: hcpu_id, + // } + // } +} \ No newline at end of file diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 6f2d8ea..5dcf013 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -21,15 +21,17 @@ use page_table_multiarch::PagingHandler; use crate::{config::AxVMConfig, vm::*}; pub mod cpu; +mod vm; -pub use cpu::CpuData; +pub use cpu::HCpu; +pub use vm::*; pub struct Hal; impl ArchHal for Hal { - fn current_cpu_init(id: CpuId) -> anyhow::Result { + fn current_cpu_init(id: CpuId) -> anyhow::Result { info!("Enabling virtualization on cpu {id}"); - let mut cpu = CpuData::new(id); + let mut cpu = HCpu::new(id); cpu.init()?; info!("{cpu}"); Ok(cpu) @@ -127,463 +129,3 @@ pub enum DeviceConfig { data: Vec, }, } - -/// VM state machine -enum StateMachine { - Idle(AxVMConfig), - Inited(RunData), - Running(RunData), - ShuttingDown(RunData), - PoweredOff, -} - -/// AArch64 Virtual Machine implementation -pub struct Vm { - id: VmId, - name: String, - state: Option, - stop_requested: AtomicBool, - exit_code: AtomicUsize, -} - -impl Vm { - /// Creates a new VM with the given configuration - pub fn new(config: AxVMConfig) -> anyhow::Result { - let vm = Self { - id: config.id().into(), - name: config.name(), - state: Some(StateMachine::Idle(config)), - stop_requested: AtomicBool::new(false), - exit_code: AtomicUsize::new(0), - }; - Ok(vm) - } - - /// Initializes the VM, creating vCPUs and setting up memory - pub fn init(&mut self) -> anyhow::Result<()> { - let StateMachine::Idle(config) = self.state.take().unwrap() else { - return Err(anyhow::anyhow!("VM is not in Idle state")); - }; - - // // Create address space for the VM - // let address_space = AddrSpace::new_empty(GuestPhysAddr::from(0x0), 0x7fff_ffff_f000) - // .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; - - // // Create vCPUs - // let mut vcpus = BTreeMap::new(); - // let vcpu_count = config.phys_cpu_ls.cpu_num(); - - // for vcpu_id in 0..vcpu_count { - // let dtb_addr = config - // .image_config() - // .dtb_load_gpa - // .unwrap_or_default() - // .as_usize(); - - // let arch_config = AxVCpuCreateConfig { - // mpidr_el1: vcpu_id as u64, - // dtb_addr, - // }; - - // let vcpu: AxArchVCpu = AxArchVCpu::new(config.id(), vcpu_id, arch_config) - // .map_err(|e| anyhow::anyhow!("Failed to create vCPU {}: {:?}", vcpu_id, e))?; - - // vcpus.insert(vcpu_id, Arc::new(vcpu)); - // } - - // // Initialize devices - // let mut devices = BTreeMap::new(); - - // // Add emulated devices - // for emu_device in config.emu_devices() { - // let device_info = DeviceInfo { - // device_type: DeviceType::Emulated, - // gpa: GuestPhysAddr::from(emu_device.base_gpa), - // hpa: None, - // size: emu_device.length, - // config: DeviceConfig::Mmio { - // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // }, - // }; - - // devices.insert(emu_device.name.clone(), device_info); - - // // Map device memory - // self.map_region( - // GuestPhysAddr::from(emu_device.base_gpa), - // HostPhysAddr::from(emu_device.base_gpa), // Use identity mapping for emulated devices - // emu_device.length, - // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // ) - // .map_err(|e| { - // anyhow::anyhow!("Failed to map emulated device {}: {:?}", emu_device.name, e) - // })?; - // } - - // // Add passthrough devices - // for pt_device in config.pass_through_devices() { - // let device_info = DeviceInfo { - // device_type: DeviceType::Passthrough, - // gpa: GuestPhysAddr::from(pt_device.base_gpa), - // hpa: Some(HostPhysAddr::from(pt_device.base_hpa)), - // size: pt_device.length, - // config: DeviceConfig::Mmio { - // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // }, - // }; - - // devices.insert(pt_device.name.clone(), device_info); - - // // Map device memory - // self.map_region( - // GuestPhysAddr::from(pt_device.base_gpa), - // HostPhysAddr::from(pt_device.base_hpa), - // pt_device.length, - // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // ) - // .map_err(|e| { - // anyhow::anyhow!( - // "Failed to map passthrough device {}: {:?}", - // pt_device.name, - // e - // ) - // })?; - // } - - // // Setup vCPUs - // for (vcpu_id, vcpu) in &vcpus { - // let entry = if *vcpu_id == 0 { - // config.bsp_entry() - // } else { - // config.ap_entry() - // }; - - // let setup_config = AxVCpuSetupConfig { - // passthrough_interrupt: config.interrupt_mode() - // == axvmconfig::VMInterruptMode::Passthrough, - // passthrough_timer: config.interrupt_mode() - // == axvmconfig::VMInterruptMode::Passthrough, - // }; - - // // Set entry point first - // vcpu.set_entry(entry).map_err(|e| { - // anyhow::anyhow!("Failed to set entry for vCPU {}: {:?}", vcpu_id, e) - // })?; - - // // Set EPT root - // vcpu.set_ept_root(address_space.page_table_root()) - // .map_err(|e| { - // anyhow::anyhow!("Failed to set EPT root for vCPU {}: {:?}", vcpu_id, e) - // })?; - - // // Setup vCPU with configuration - // vcpu.setup(setup_config) - // .map_err(|e| anyhow::anyhow!("Failed to setup vCPU {}: {:?}", vcpu_id, e))?; - // } - - // self.state = Some(StateMachine::Inited(RunData { - // vcpus, - // address_space, - // devices, - // })); - - Ok(()) - } - - /// Checks if the VM is active (not stopped) - fn is_active(&self) -> bool { - !self.stop_requested.load(Ordering::SeqCst) - } - - /// Gets the current state of the VM - fn get_state(&self) -> &StateMachine { - self.state.as_ref().unwrap() - } - - /// Gets a mutable reference to the current state of the VM - fn get_state_mut(&mut self) -> &mut StateMachine { - self.state.as_mut().unwrap() - } - - /// Transitions the VM state from current to new state - fn transition_state(&mut self, new_state: StateMachine) -> anyhow::Result<()> { - let current_state = self.get_state(); - - // Validate state transition - match (current_state, &new_state) { - (StateMachine::Idle(_), StateMachine::Inited(_)) => {} - (StateMachine::Inited(_), StateMachine::Running(_)) => {} - (StateMachine::Running(_), StateMachine::ShuttingDown(_)) => {} - (StateMachine::ShuttingDown(_), StateMachine::PoweredOff) => {} - _ => return Err(anyhow::anyhow!("Invalid state transition")), - } - - self.state = Some(new_state); - Ok(()) - } - - /// Shuts down VM and transitions to PoweredOff state - pub fn shutdown(&mut self) -> anyhow::Result<()> { - // First check if we're in Running state - let is_running = matches!(self.get_state(), StateMachine::Running(_)); - - if is_running { - // Stop VM first - self.stop(); - } - - match self.get_state_mut() { - StateMachine::Running(data) => { - // Transition to ShuttingDown state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::ShuttingDown(old_data))?; - - // Clean up resources - self.cleanup_resources()?; - - // Transition to PoweredOff state - self.transition_state(StateMachine::PoweredOff)?; - - info!("VM {} ({}) shut down successfully", self.id, self.name); - Ok(()) - } - StateMachine::ShuttingDown(_) => { - // Already shutting down - Ok(()) - } - StateMachine::PoweredOff => { - // Already powered off - Ok(()) - } - _ => Err(anyhow::anyhow!("VM is not in Running state")), - } - } - - /// Clean up VM resources - fn cleanup_resources(&mut self) -> anyhow::Result<()> { - match self.get_state_mut() { - StateMachine::ShuttingDown(data) => { - // Clear vCPUs - // data.vcpus.clear(); - - // Note: We don't destroy the address space here as it might be needed - // for debugging or inspection after shutdown - - Ok(()) - } - _ => Err(anyhow::anyhow!("VM is not in ShuttingDown state")), - } - } -} - -impl VmOps for Vm { - fn id(&self) -> VmId { - self.id - } - - fn name(&self) -> &str { - &self.name - } - - fn boot(&mut self) -> anyhow::Result<()> { - let data = match self.get_state_mut() { - StateMachine::Inited(data) => data, - _ => return Err(anyhow::anyhow!("VM is not in Inited state")), - }; - - // Transition to Running state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::Running(old_data))?; - - // // Start all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Starting vCPU {} for VM {}", vcpu_id, self.id); - // vcpu.bind() - // .map_err(|e| anyhow::anyhow!("Failed to bind vCPU {}: {:?}", vcpu_id, e))?; - // } - - // info!( - // "VM {} ({}) booted successfully with {} vCPUs", - // self.id, - // self.name, - // vcpus.len() - // ); - - Ok(()) - } - - fn stop(&self) { - if !self.is_active() { - return; // Already stopped - } - - info!("Stopping VM {} ({})", self.id, self.name); - - // Set stop flag - self.stop_requested.store(true, Ordering::SeqCst); - - // // Unbind all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); - // if let Err(e) = vcpu.unbind() { - // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); - // } - // } - - info!("VM {} ({}) stopped", self.id, self.name); - } - - fn status(&self) -> Status { - match self.get_state() { - StateMachine::Idle(_) => Status::Idle, - StateMachine::Inited(_) => Status::Idle, - StateMachine::Running(_) => Status::Running, - StateMachine::ShuttingDown(_) => Status::ShuttingDown, - StateMachine::PoweredOff => Status::PoweredOff, - } - } -} - -impl Drop for Vm { - fn drop(&mut self) { - // Ensure VM is properly shut down - if matches!(self.get_state(), StateMachine::Running(_)) { - let _ = self.shutdown(); - } - } -} - -impl Vm { - /// Gets the exit code of the VM - pub fn exit_code(&self) -> usize { - self.exit_code.load(Ordering::SeqCst) - } - - /// Sets the exit code of the VM - pub fn set_exit_code(&self, code: usize) { - self.exit_code.store(code, Ordering::SeqCst); - } - - /// Checks if the VM has been stopped - pub fn is_stopped(&self) -> bool { - self.stop_requested.load(Ordering::SeqCst) - } - - /// Resets the VM to initial state - pub fn reset(&mut self) -> anyhow::Result<()> { - match self.get_state() { - StateMachine::Running(_) | StateMachine::ShuttingDown(_) => { - // Stop the VM first - self.stop(); - - // Transition to PoweredOff state - self.transition_state(StateMachine::PoweredOff)?; - - // Note: In a real implementation, we would need to: - // 1. Reset all vCPUs to initial state - // 2. Reset memory to initial state - // 3. Reset devices to initial state - // 4. Transition back to Idle state - - info!("VM {} ({}) reset", self.id, self.name); - Ok(()) - } - _ => Err(anyhow::anyhow!("VM is not in a state that can be reset")), - } - } - - /// Pauses the VM - pub fn pause(&mut self) -> anyhow::Result<()> { - let data = match self.get_state_mut() { - StateMachine::Running(data) => data, - _ => return Err(anyhow::anyhow!("VM is not in Running state")), - }; - - // Transition to Inited state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::Inited(old_data))?; - - // // Unbind all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); - // if let Err(e) = vcpu.unbind() { - // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); - // } - // } - - info!("VM {} ({}) paused", self.id, self.name); - Ok(()) - } - - /// Resumes the VM - pub fn resume(&mut self) -> anyhow::Result<()> { - let data = match self.get_state_mut() { - StateMachine::Inited(data) => data, - _ => return Err(anyhow::anyhow!("VM is not in Inited state")), - }; - - // Transition to Running state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::Running(old_data))?; - - // // Bind all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Binding vCPU {} for VM {}", vcpu_id, self.id); - // if let Err(e) = vcpu.bind() { - // warn!("Failed to bind vCPU {}: {:?}", vcpu_id, e); - // } - // } - - info!("VM {} ({}) resumed", self.id, self.name); - Ok(()) - } - - /// Gets the current state as a string - pub fn state_str(&self) -> &'static str { - match self.get_state() { - StateMachine::Idle(_) => "Idle", - StateMachine::Inited(_) => "Inited", - StateMachine::Running(_) => "Running", - StateMachine::ShuttingDown(_) => "ShuttingDown", - StateMachine::PoweredOff => "PoweredOff", - } - } - - /// Prints VM information - pub fn print_info(&self) { - info!("VM Information:"); - info!(" ID: {}", self.id); - info!(" Name: {}", self.name); - // info!(" State: {}", self.state_str()); - // info!(" vCPUs: {}", self.vcpu_count()); - // info!(" Devices: {}", self.get_devices().len()); - - // if let Some(root) = self.page_table_root() { - // info!(" Page Table Root: {:#x}", root); - // } - } -} diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs new file mode 100644 index 0000000..29d3a1b --- /dev/null +++ b/src/arch/aarch64/vm.rs @@ -0,0 +1,459 @@ +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +use alloc::{collections::BTreeMap, string::String}; + +use crate::{ + arch::RunData, + config::AxVMConfig, + vm::{Status, VmId, VmOps}, +}; + +/// AArch64 Virtual Machine implementation +pub struct ArchVm { + pub id: VmId, + pub name: String, + state: Option, + stop_requested: AtomicBool, + exit_code: AtomicUsize, +} + +impl ArchVm { + /// Creates a new VM with the given configuration + pub fn new(config: AxVMConfig) -> anyhow::Result { + let vm = Self { + id: config.id().into(), + name: config.name(), + state: Some(StateMachine::Idle(config)), + stop_requested: AtomicBool::new(false), + exit_code: AtomicUsize::new(0), + }; + Ok(vm) + } + + /// Initializes the VM, creating vCPUs and setting up memory + pub fn init(&mut self) -> anyhow::Result<()> { + let StateMachine::Idle(config) = self.state.take().unwrap() else { + return Err(anyhow::anyhow!("VM is not in Idle state")); + }; + + // Create vCPUs + let mut vcpus = BTreeMap::new(); + + for (hard_id, cpu_config) in config.phys_cpu_ls.iter() { + + } + + + let vcpu_count = config.phys_cpu_ls.cpu_num(); + + + + // // Create address space for the VM + // let address_space = AddrSpace::new_empty(GuestPhysAddr::from(0x0), 0x7fff_ffff_f000) + // .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; + + // // Initialize devices + // let mut devices = BTreeMap::new(); + + // // Add emulated devices + // for emu_device in config.emu_devices() { + // let device_info = DeviceInfo { + // device_type: DeviceType::Emulated, + // gpa: GuestPhysAddr::from(emu_device.base_gpa), + // hpa: None, + // size: emu_device.length, + // config: DeviceConfig::Mmio { + // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // }, + // }; + + // devices.insert(emu_device.name.clone(), device_info); + + // // Map device memory + // self.map_region( + // GuestPhysAddr::from(emu_device.base_gpa), + // HostPhysAddr::from(emu_device.base_gpa), // Use identity mapping for emulated devices + // emu_device.length, + // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // ) + // .map_err(|e| { + // anyhow::anyhow!("Failed to map emulated device {}: {:?}", emu_device.name, e) + // })?; + // } + + // // Add passthrough devices + // for pt_device in config.pass_through_devices() { + // let device_info = DeviceInfo { + // device_type: DeviceType::Passthrough, + // gpa: GuestPhysAddr::from(pt_device.base_gpa), + // hpa: Some(HostPhysAddr::from(pt_device.base_hpa)), + // size: pt_device.length, + // config: DeviceConfig::Mmio { + // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // }, + // }; + + // devices.insert(pt_device.name.clone(), device_info); + + // // Map device memory + // self.map_region( + // GuestPhysAddr::from(pt_device.base_gpa), + // HostPhysAddr::from(pt_device.base_hpa), + // pt_device.length, + // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + // ) + // .map_err(|e| { + // anyhow::anyhow!( + // "Failed to map passthrough device {}: {:?}", + // pt_device.name, + // e + // ) + // })?; + // } + + // // Setup vCPUs + // for (vcpu_id, vcpu) in &vcpus { + // let entry = if *vcpu_id == 0 { + // config.bsp_entry() + // } else { + // config.ap_entry() + // }; + + // let setup_config = AxVCpuSetupConfig { + // passthrough_interrupt: config.interrupt_mode() + // == axvmconfig::VMInterruptMode::Passthrough, + // passthrough_timer: config.interrupt_mode() + // == axvmconfig::VMInterruptMode::Passthrough, + // }; + + // // Set entry point first + // vcpu.set_entry(entry).map_err(|e| { + // anyhow::anyhow!("Failed to set entry for vCPU {}: {:?}", vcpu_id, e) + // })?; + + // // Set EPT root + // vcpu.set_ept_root(address_space.page_table_root()) + // .map_err(|e| { + // anyhow::anyhow!("Failed to set EPT root for vCPU {}: {:?}", vcpu_id, e) + // })?; + + // // Setup vCPU with configuration + // vcpu.setup(setup_config) + // .map_err(|e| anyhow::anyhow!("Failed to setup vCPU {}: {:?}", vcpu_id, e))?; + // } + + // self.state = Some(StateMachine::Inited(RunData { + // vcpus, + // address_space, + // devices, + // })); + + Ok(()) + } + + /// Checks if the VM is active (not stopped) + fn is_active(&self) -> bool { + !self.stop_requested.load(Ordering::SeqCst) + } + + /// Gets the current state of the VM + fn get_state(&self) -> &StateMachine { + self.state.as_ref().unwrap() + } + + /// Gets a mutable reference to the current state of the VM + fn get_state_mut(&mut self) -> &mut StateMachine { + self.state.as_mut().unwrap() + } + + /// Transitions the VM state from current to new state + fn transition_state(&mut self, new_state: StateMachine) -> anyhow::Result<()> { + let current_state = self.get_state(); + + // Validate state transition + match (current_state, &new_state) { + (StateMachine::Idle(_), StateMachine::Inited(_)) => {} + (StateMachine::Inited(_), StateMachine::Running(_)) => {} + (StateMachine::Running(_), StateMachine::ShuttingDown(_)) => {} + (StateMachine::ShuttingDown(_), StateMachine::PoweredOff) => {} + _ => return Err(anyhow::anyhow!("Invalid state transition")), + } + + self.state = Some(new_state); + Ok(()) + } + + /// Shuts down VM and transitions to PoweredOff state + pub fn shutdown(&mut self) -> anyhow::Result<()> { + // First check if we're in Running state + let is_running = matches!(self.get_state(), StateMachine::Running(_)); + + if is_running { + // Stop VM first + self.stop(); + } + + match self.get_state_mut() { + StateMachine::Running(data) => { + // Transition to ShuttingDown state + let new_data = RunData { + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::ShuttingDown(old_data))?; + + // Clean up resources + self.cleanup_resources()?; + + // Transition to PoweredOff state + self.transition_state(StateMachine::PoweredOff)?; + + info!("VM {} ({}) shut down successfully", self.id, self.name); + Ok(()) + } + StateMachine::ShuttingDown(_) => { + // Already shutting down + Ok(()) + } + StateMachine::PoweredOff => { + // Already powered off + Ok(()) + } + _ => Err(anyhow::anyhow!("VM is not in Running state")), + } + } + + /// Clean up VM resources + fn cleanup_resources(&mut self) -> anyhow::Result<()> { + match self.get_state_mut() { + StateMachine::ShuttingDown(data) => { + // Clear vCPUs + // data.vcpus.clear(); + + // Note: We don't destroy the address space here as it might be needed + // for debugging or inspection after shutdown + + Ok(()) + } + _ => Err(anyhow::anyhow!("VM is not in ShuttingDown state")), + } + } +} + +impl VmOps for ArchVm { + fn id(&self) -> VmId { + self.id + } + + fn name(&self) -> &str { + &self.name + } + + fn boot(&mut self) -> anyhow::Result<()> { + let data = match self.get_state_mut() { + StateMachine::Inited(data) => data, + _ => return Err(anyhow::anyhow!("VM is not in Inited state")), + }; + + // Transition to Running state + let new_data = RunData { + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::Running(old_data))?; + + // // Start all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Starting vCPU {} for VM {}", vcpu_id, self.id); + // vcpu.bind() + // .map_err(|e| anyhow::anyhow!("Failed to bind vCPU {}: {:?}", vcpu_id, e))?; + // } + + // info!( + // "VM {} ({}) booted successfully with {} vCPUs", + // self.id, + // self.name, + // vcpus.len() + // ); + + Ok(()) + } + + fn stop(&self) { + if !self.is_active() { + return; // Already stopped + } + + info!("Stopping VM {} ({})", self.id, self.name); + + // Set stop flag + self.stop_requested.store(true, Ordering::SeqCst); + + // // Unbind all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); + // if let Err(e) = vcpu.unbind() { + // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); + // } + // } + + info!("VM {} ({}) stopped", self.id, self.name); + } + + fn status(&self) -> Status { + match self.get_state() { + StateMachine::Idle(_) => Status::Idle, + StateMachine::Inited(_) => Status::Idle, + StateMachine::Running(_) => Status::Running, + StateMachine::ShuttingDown(_) => Status::ShuttingDown, + StateMachine::PoweredOff => Status::PoweredOff, + } + } +} + +impl Drop for ArchVm { + fn drop(&mut self) { + // Ensure VM is properly shut down + if matches!(self.get_state(), StateMachine::Running(_)) { + let _ = self.shutdown(); + } + } +} + +impl ArchVm { + /// Gets the exit code of the VM + pub fn exit_code(&self) -> usize { + self.exit_code.load(Ordering::SeqCst) + } + + /// Sets the exit code of the VM + pub fn set_exit_code(&self, code: usize) { + self.exit_code.store(code, Ordering::SeqCst); + } + + /// Checks if the VM has been stopped + pub fn is_stopped(&self) -> bool { + self.stop_requested.load(Ordering::SeqCst) + } + + /// Resets the VM to initial state + pub fn reset(&mut self) -> anyhow::Result<()> { + match self.get_state() { + StateMachine::Running(_) | StateMachine::ShuttingDown(_) => { + // Stop the VM first + self.stop(); + + // Transition to PoweredOff state + self.transition_state(StateMachine::PoweredOff)?; + + // Note: In a real implementation, we would need to: + // 1. Reset all vCPUs to initial state + // 2. Reset memory to initial state + // 3. Reset devices to initial state + // 4. Transition back to Idle state + + info!("VM {} ({}) reset", self.id, self.name); + Ok(()) + } + _ => Err(anyhow::anyhow!("VM is not in a state that can be reset")), + } + } + + /// Pauses the VM + pub fn pause(&mut self) -> anyhow::Result<()> { + let data = match self.get_state_mut() { + StateMachine::Running(data) => data, + _ => return Err(anyhow::anyhow!("VM is not in Running state")), + }; + + // Transition to Inited state + let new_data = RunData { + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::Inited(old_data))?; + + // // Unbind all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); + // if let Err(e) = vcpu.unbind() { + // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); + // } + // } + + info!("VM {} ({}) paused", self.id, self.name); + Ok(()) + } + + /// Resumes the VM + pub fn resume(&mut self) -> anyhow::Result<()> { + let data = match self.get_state_mut() { + StateMachine::Inited(data) => data, + _ => return Err(anyhow::anyhow!("VM is not in Inited state")), + }; + + // Transition to Running state + let new_data = RunData { + // vcpus: BTreeMap::new(), + // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + devices: BTreeMap::new(), + }; + let old_data = core::mem::replace(data, new_data); + self.transition_state(StateMachine::Running(old_data))?; + + // // Bind all vCPUs + // let vcpus = self.get_vcpus(); + // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { + // debug!("Binding vCPU {} for VM {}", vcpu_id, self.id); + // if let Err(e) = vcpu.bind() { + // warn!("Failed to bind vCPU {}: {:?}", vcpu_id, e); + // } + // } + + info!("VM {} ({}) resumed", self.id, self.name); + Ok(()) + } + + /// Gets the current state as a string + pub fn state_str(&self) -> &'static str { + match self.get_state() { + StateMachine::Idle(_) => "Idle", + StateMachine::Inited(_) => "Inited", + StateMachine::Running(_) => "Running", + StateMachine::ShuttingDown(_) => "ShuttingDown", + StateMachine::PoweredOff => "PoweredOff", + } + } + + /// Prints VM information + pub fn print_info(&self) { + info!("VM Information:"); + info!(" ID: {}", self.id); + info!(" Name: {}", self.name); + // info!(" State: {}", self.state_str()); + // info!(" vCPUs: {}", self.vcpu_count()); + // info!(" Devices: {}", self.get_devices().len()); + + // if let Some(root) = self.page_table_root() { + // info!(" Page Table Root: {:#x}", root); + // } + } +} + +/// VM state machine +enum StateMachine { + Idle(AxVMConfig), + Inited(RunData), + Running(RunData), + ShuttingDown(RunData), + PoweredOff, +} diff --git a/src/lib.rs b/src/lib.rs index f6b151b..8b9ec21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ const TASK_STACK_SIZE: usize = 0x40000; // 16KB #[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")] #[cfg_attr(target_arch = "x86_64", path = "arch/x86_64/mod.rs")] -pub mod arch; +pub(crate) mod arch; mod fdt; mod vcpu; @@ -29,6 +29,9 @@ mod vm; pub mod config; pub mod vhal; +pub use config::AxVMConfig; +pub use vm::*; + /// Enable hardware virtualization support. pub fn enable_viretualization() -> anyhow::Result<()> { vhal::init()?; diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index ae2f635..d3f8d3a 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -4,19 +4,21 @@ use core::{ fmt::Display, sync::atomic::{AtomicUsize, Ordering}, }; +use spin::Mutex; +use vm_allocator::IdAllocator; -use axtask::AxCpuMask; -use axconfig::TASK_STACK_SIZE; use crate::{ - - arch::{CpuData, Hal}, + arch::{HCpu, Hal}, vhal::precpu::PreCpuSet, }; +use axconfig::TASK_STACK_SIZE; +use axtask::AxCpuMask; pub(crate) mod precpu; mod timer; -static PRE_CPU: PreCpuSet = PreCpuSet::new(); +static PRE_CPU: PreCpuSet = PreCpuSet::new(); +static HCPU_ALLOC: Mutex> = Mutex::new(None); pub fn init() -> anyhow::Result<()> { Hal::init()?; @@ -59,6 +61,9 @@ pub fn init() -> anyhow::Result<()> { // for handle in handles { // handle.join(); // } + + HCPU_ALLOC.lock().replace(IdAllocator::new(0, cpu_count)); + info!("All cores have enabled hardware virtualization support."); Ok(()) } @@ -67,11 +72,38 @@ pub fn cpu_count() -> usize { axruntime::cpu_count() } +pub struct HCpuExclusive(CpuId); + +impl HCpuExclusive { + pub fn try_new(id: Option) -> Option { + // let id = id.unwrap_or_else(|| { + // let hard_id = Hal::cpu_hard_id(); + // let cpu_list = Hal::cpu_list(); + // let index = cpu_list + // .iter() + // .position(|&h_id| h_id == hard_id) + // .expect("Current CPU hard ID not found in CPU list"); + // CpuId::new(index) + // }); + // let cpu_data = PRE_CPU.get(id).ok()?; + // Some(HCpuExclusive(id)) + } +} + +impl Drop for HCpuExclusive { + fn drop(&mut self) { + let mut allocator = HCPU_ALLOC.lock(); + if let Some(ref mut alloc) = *allocator { + let _ = alloc.free_id(self.0.raw() as u32); + } + } +} + pub(crate) trait ArchHal { fn init() -> anyhow::Result<()>; fn cpu_hard_id() -> CpuHardId; fn cpu_list() -> Vec; - fn current_cpu_init(id: CpuId) -> anyhow::Result; + fn current_cpu_init(id: CpuId) -> anyhow::Result; } pub(crate) trait ArchCpuData { diff --git a/src/vm.rs b/src/vm.rs index d8ba80a..ff8235c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,3 +1,8 @@ +use alloc::string::String; +use spin::Mutex; + +use crate::AxVMConfig; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct VmId(usize); @@ -34,3 +39,35 @@ pub enum Status { ShuttingDown, PoweredOff, } + +pub struct Vm { + id: VmId, + name: String, + inner: Mutex, +} + +impl Vm { + pub fn new(config: AxVMConfig) -> anyhow::Result { + let mut arch_vm = crate::arch::ArchVm::new(config)?; + arch_vm.init()?; + + Ok(Vm { + id: arch_vm.id(), + name: arch_vm.name().into(), + inner: Mutex::new(arch_vm), + }) + } + + pub fn id(&self) -> VmId { + self.id + } + + pub fn name(&self) -> &str { + self.name.as_str() + } + + pub fn boot(&self) -> anyhow::Result<()> { + let mut arch_vm = self.inner.lock(); + arch_vm.boot() + } +} From 4550ca5368ac9d615f7dd4f72718e892cedd1467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 25 Nov 2025 10:24:13 +0800 Subject: [PATCH 25/74] =?UTF-8?q?=E9=87=8D=E6=9E=84=20AArch64=20CPU=20?= =?UTF-8?q?=E5=92=8C=E8=99=9A=E6=8B=9F=E5=8C=96=E6=A8=A1=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20HCpuExclusive=20=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E4=BD=93=EF=BC=8C=E6=9B=B4=E6=96=B0=20CPU=20ID=20=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E4=BC=98=E5=8C=96=20Cargo.toml=20=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 + src/arch/aarch64/cpu.rs | 42 ++++++++++------- src/arch/aarch64/mod.rs | 11 +++-- src/arch/aarch64/vm.rs | 8 ++-- src/vhal/cpu.rs | 101 ++++++++++++++++++++++++++++++++++++++++ src/vhal/mod.rs | 85 ++++----------------------------- src/vhal/precpu.rs | 8 +++- 7 files changed, 154 insertions(+), 103 deletions(-) create mode 100644 src/vhal/cpu.rs diff --git a/Cargo.toml b/Cargo.toml index c4ac6c2..1c536e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true +bitmap-allocator = "0.2.1" # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" @@ -39,6 +40,7 @@ axconfig = {workspace = true} axhal.workspace = true axruntime.workspace = true axtask.workspace = true +axvm-types.workspace = true [target.'cfg(target_arch = "x86_64")'.dependencies] # x86_vcpu = "0.1" diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 687a89d..d736aae 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -2,12 +2,13 @@ use core::fmt::Display; use aarch64_cpu::registers::*; use alloc::sync::Weak; -use arm_vcpu::Aarch64PerCpu; +use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; use axhal::percpu::this_cpu_id; +use axvm_types::addr::*; -use crate::{ - fdt, - vhal::{ArchCpuData, ArchHal, CpuHardId, CpuId, precpu::PreCpuSet}, +use crate::vhal::{ + ArchCpuData, + cpu::{CpuHardId, CpuId, HCpuExclusive}, }; pub struct HCpu { @@ -44,7 +45,7 @@ impl HCpu { } impl ArchCpuData for HCpu { - fn hard_id(&self) -> crate::vhal::CpuHardId { + fn hard_id(&self) -> CpuHardId { self.hard_id } } @@ -75,19 +76,26 @@ impl arm_vcpu::CpuHal for VCpuHal { } pub struct VCpu { - pub v_hard_id: CpuHardId, + pub id: CpuHardId, pub vcpu: arm_vcpu::Aarch64VCpu, - hcpu: CpuHardId, + hcpu: HCpuExclusive, } impl VCpu { - // pub fn new(config: &) -> Self { - // let vcpu = arm_vcpu::Aarch64VCpu::new(VCpuHal); - - // VCpu { - // v_hard_id, - // vcpu, - // hcpu: hcpu_id, - // } - // } -} \ No newline at end of file + pub fn new(host_cpuid: Option, dtb_addr: HostVirtAddr) -> anyhow::Result { + let hcpu_exclusive = HCpuExclusive::try_new(host_cpuid) + .ok_or_else(|| anyhow!("Failed to allocate cpu with id `{host_cpuid:?}`"))?; + + let vcpu = arm_vcpu::Aarch64VCpu::new(Aarch64VCpuCreateConfig { + mpidr_el1: hcpu_exclusive.hard_id().raw() as u64, + dtb_addr: dtb_addr.as_usize(), + }) + .unwrap(); + todo!() + // Ok(VCpu { + // v_hard_id, + // vcpu, + // hcpu: hcpu_id, + // }) + } +} diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 5dcf013..b3b9873 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -11,7 +11,8 @@ use crate::alloc::sync::Arc; use crate::alloc::vec; use crate::alloc::vec::Vec; use crate::fdt; -use crate::vhal::{ArchHal, CpuId}; +use crate::vhal::cpu::CpuHardId; +use crate::vhal::{ArchHal, cpu::CpuId}; use aarch64_cpu::registers::{ReadWriteable, Readable, Writeable}; use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, MappingFlags}; @@ -42,17 +43,17 @@ impl ArchHal for Hal { Ok(()) } - fn cpu_list() -> Vec { + fn cpu_list() -> Vec { fdt::cpu_list() .unwrap() .into_iter() - .map(crate::vhal::CpuHardId::new) + .map(CpuHardId::new) .collect() } - fn cpu_hard_id() -> crate::vhal::CpuHardId { + fn cpu_hard_id() -> CpuHardId { let mpidr = MPIDR_EL1.get() as usize; - crate::vhal::CpuHardId::new(mpidr) + CpuHardId::new(mpidr) } } diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 29d3a1b..b244e3d 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -37,11 +37,11 @@ impl ArchVm { }; // Create vCPUs - let mut vcpus = BTreeMap::new(); + // let mut vcpus = BTreeMap::new(); - for (hard_id, cpu_config) in config.phys_cpu_ls.iter() { - - } + // for (hard_id, cpu_config) in config.phys_cpu_ls.iter() { + + // } let vcpu_count = config.phys_cpu_ls.cpu_num(); diff --git a/src/vhal/cpu.rs b/src/vhal/cpu.rs new file mode 100644 index 0000000..c6afa27 --- /dev/null +++ b/src/vhal/cpu.rs @@ -0,0 +1,101 @@ +use core::fmt::Display; + +use bitmap_allocator::{BitAlloc, BitAlloc4K}; +use spin::Mutex; + +use crate::{ + arch::HCpu, + vhal::{ArchCpuData, precpu::PreCpuSet}, +}; + +pub(super) static PRE_CPU: PreCpuSet = PreCpuSet::new(); +pub(super) static HCPU_ALLOC: Mutex = Mutex::new(BitAlloc4K::DEFAULT); + +pub struct HCpuExclusive(CpuId); + +impl HCpuExclusive { + pub fn try_new(id: Option) -> Option { + let mut a = HCPU_ALLOC.lock(); + match id { + Some(id) => { + // Try to allocate the specific ID + let raw = a.alloc_contiguous(Some(id.raw()), 1, 1)?; + Some(HCpuExclusive(CpuId::new(raw))) + } + None => { + // Auto-allocate any available ID + let raw_id = a.alloc()?; + Some(HCpuExclusive(CpuId::new(raw_id))) + } + } + } + + pub fn with_cpu(&self, f: F) -> R + where + F: FnOnce(&HCpu) -> R, + { + unsafe { + for (id, cpu) in PRE_CPU.iter() { + if cpu.id == self.0 { + return f(cpu); + } + } + } + panic!("CPU data not found for CPU ID {}", self.0); + } + + pub fn cpu_id(&self) -> CpuId { + self.0 + } + + pub fn hard_id(&self) -> CpuHardId { + self.with_cpu(|cpu| cpu.hard_id()) + } +} + +impl Drop for HCpuExclusive { + fn drop(&mut self) { + let mut allocator = HCPU_ALLOC.lock(); + allocator.dealloc(self.0.raw()); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct CpuHardId(usize); + +impl CpuHardId { + pub fn new(id: usize) -> Self { + CpuHardId(id) + } + + pub fn raw(&self) -> usize { + self.0 + } +} + +impl Display for CpuHardId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CPU Hard({:#x})", self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct CpuId(usize); + +impl CpuId { + pub fn new(id: usize) -> Self { + CpuId(id) + } + + pub fn raw(&self) -> usize { + self.0 + } +} + +impl Display for CpuId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CPU({})", self.0) + } +} diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index d3f8d3a..d325880 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -1,25 +1,25 @@ use alloc::{collections::BTreeMap, vec::Vec}; +use bitmap_allocator::{BitAlloc, BitAlloc4K}; use core::{ - cell::UnsafeCell, fmt::Display, sync::atomic::{AtomicUsize, Ordering}, }; use spin::Mutex; -use vm_allocator::IdAllocator; use crate::{ arch::{HCpu, Hal}, - vhal::precpu::PreCpuSet, + vhal::{ + cpu::{CpuHardId, CpuId}, + precpu::PreCpuSet, + }, }; use axconfig::TASK_STACK_SIZE; use axtask::AxCpuMask; +pub(crate) mod cpu; pub(crate) mod precpu; mod timer; -static PRE_CPU: PreCpuSet = PreCpuSet::new(); -static HCPU_ALLOC: Mutex> = Mutex::new(None); - pub fn init() -> anyhow::Result<()> { Hal::init()?; @@ -28,7 +28,7 @@ pub fn init() -> anyhow::Result<()> { let cpu_count = cpu_count(); info!("Initializing VHal for {cpu_count} CPUs..."); - PRE_CPU.init(); + cpu::PRE_CPU.init(); for cpu_id in 0..cpu_count { let id = CpuId::new(cpu_id); let _handle = axtask::spawn_raw( @@ -43,7 +43,7 @@ pub fn init() -> anyhow::Result<()> { timer::init_percpu(); let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); - unsafe { PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; + unsafe { cpu::PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; let _ = CORES.fetch_add(1, Ordering::Release); }, format!("init-cpu-{}", cpu_id), @@ -62,7 +62,7 @@ pub fn init() -> anyhow::Result<()> { // handle.join(); // } - HCPU_ALLOC.lock().replace(IdAllocator::new(0, cpu_count)); + cpu::HCPU_ALLOC.lock().insert(0..cpu_count); info!("All cores have enabled hardware virtualization support."); Ok(()) @@ -72,33 +72,6 @@ pub fn cpu_count() -> usize { axruntime::cpu_count() } -pub struct HCpuExclusive(CpuId); - -impl HCpuExclusive { - pub fn try_new(id: Option) -> Option { - // let id = id.unwrap_or_else(|| { - // let hard_id = Hal::cpu_hard_id(); - // let cpu_list = Hal::cpu_list(); - // let index = cpu_list - // .iter() - // .position(|&h_id| h_id == hard_id) - // .expect("Current CPU hard ID not found in CPU list"); - // CpuId::new(index) - // }); - // let cpu_data = PRE_CPU.get(id).ok()?; - // Some(HCpuExclusive(id)) - } -} - -impl Drop for HCpuExclusive { - fn drop(&mut self) { - let mut allocator = HCPU_ALLOC.lock(); - if let Some(ref mut alloc) = *allocator { - let _ = alloc.free_id(self.0.raw() as u32); - } - } -} - pub(crate) trait ArchHal { fn init() -> anyhow::Result<()>; fn cpu_hard_id() -> CpuHardId; @@ -109,43 +82,3 @@ pub(crate) trait ArchHal { pub(crate) trait ArchCpuData { fn hard_id(&self) -> CpuHardId; } - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct CpuHardId(usize); - -impl CpuHardId { - pub fn new(id: usize) -> Self { - CpuHardId(id) - } - - pub fn raw(&self) -> usize { - self.0 - } -} - -impl Display for CpuHardId { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "CPU Hard({:#x})", self.0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct CpuId(usize); - -impl CpuId { - pub fn new(id: usize) -> Self { - CpuId(id) - } - - pub fn raw(&self) -> usize { - self.0 - } -} - -impl Display for CpuId { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "CPU({})", self.0) - } -} diff --git a/src/vhal/precpu.rs b/src/vhal/precpu.rs index c6c02ed..3d1b270 100644 --- a/src/vhal/precpu.rs +++ b/src/vhal/precpu.rs @@ -3,7 +3,7 @@ use core::{cell::UnsafeCell, ops::Deref}; use crate::{ arch::Hal, - vhal::{ArchHal, CpuHardId}, + vhal::{ArchHal, cpu::CpuHardId}, }; pub(crate) struct PreCpuSet(UnsafeCell>>); @@ -29,6 +29,12 @@ impl PreCpuSet { v.insert(cpu_id, None); } } + + pub fn iter(&self) -> impl Iterator { + let set = unsafe { &*self.0.get() }; + set.iter() + .map(|(k, v)| (*k, v.as_ref().expect("CPU data not initialized!"))) + } } impl Deref for PreCpuSet { From c9515294ac688a2f05083368f31c98a0fdfc16ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 25 Nov 2025 10:55:08 +0800 Subject: [PATCH 26/74] =?UTF-8?q?=E9=87=8D=E6=9E=84=20VCpu=20=E5=92=8C=20A?= =?UTF-8?q?rchVm=20=E7=BB=93=E6=9E=84=EF=BC=8C=E6=9B=B4=E6=96=B0=20CPU=20I?= =?UTF-8?q?D=20=E7=AE=A1=E7=90=86=EF=BC=8C=E4=BC=98=E5=8C=96=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=20CpuConfig=20=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E4=BD=93=E4=BB=A5=E6=94=AF=E6=8C=81=E7=89=A9=E7=90=86=20CPU=20?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E8=BF=AD=E4=BB=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 24 ++++++--- src/arch/aarch64/mod.rs | 80 +++++----------------------- src/arch/aarch64/vm.rs | 112 +++++++++++++++++++++++++--------------- src/config.rs | 16 ++++++ 4 files changed, 114 insertions(+), 118 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index d736aae..91ac731 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -82,20 +82,28 @@ pub struct VCpu { } impl VCpu { - pub fn new(host_cpuid: Option, dtb_addr: HostVirtAddr) -> anyhow::Result { + pub fn new(host_cpuid: Option, dtb_addr: GuestPhysAddr) -> anyhow::Result { let hcpu_exclusive = HCpuExclusive::try_new(host_cpuid) .ok_or_else(|| anyhow!("Failed to allocate cpu with id `{host_cpuid:?}`"))?; + let hard_id = hcpu_exclusive.hard_id(); + let vcpu = arm_vcpu::Aarch64VCpu::new(Aarch64VCpuCreateConfig { - mpidr_el1: hcpu_exclusive.hard_id().raw() as u64, + mpidr_el1: hard_id.raw() as u64, dtb_addr: dtb_addr.as_usize(), }) .unwrap(); - todo!() - // Ok(VCpu { - // v_hard_id, - // vcpu, - // hcpu: hcpu_id, - // }) + Ok(VCpu { + id: hard_id, + vcpu, + hcpu: hcpu_exclusive, + }) + } + + pub fn with_hcpu(&self, f: F) -> R + where + F: FnOnce(&HCpu) -> R, + { + self.hcpu.with_cpu(f) } } diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index b3b9873..ea35a45 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -4,18 +4,16 @@ use core::fmt; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use memory_addr::VirtAddr; -use crate::alloc::alloc::{self, Layout}; -use crate::alloc::collections::BTreeMap; -use crate::alloc::string::String; -use crate::alloc::sync::Arc; -use crate::alloc::vec; -use crate::alloc::vec::Vec; +use crate::alloc::{collections::BTreeMap, string::String, vec::Vec}; +use crate::arch::cpu::VCpu; use crate::fdt; -use crate::vhal::cpu::CpuHardId; -use crate::vhal::{ArchHal, cpu::CpuId}; +use crate::vhal::{ + ArchHal, + cpu::{CpuHardId, CpuId}, +}; use aarch64_cpu::registers::{ReadWriteable, Readable, Writeable}; -use axaddrspace::{AddrSpace, AxMmHal, GuestPhysAddr, HostPhysAddr, MappingFlags}; +use axaddrspace::{AxMmHal, MappingFlags}; use axerrno::{AxResult, ax_err}; use page_table_multiarch::PagingHandler; @@ -27,6 +25,8 @@ mod vm; pub use cpu::HCpu; pub use vm::*; +type AddrSpace = axaddrspace::AddrSpace; + pub struct Hal; impl ArchHal for Hal { @@ -66,67 +66,11 @@ impl fmt::Display for VmId { /// Data needed when VM is running pub struct RunData { - // vcpus: BTreeMap>, - // address_space: AddrSpace, + vcpus: Vec, + address_space: AddrSpace, devices: BTreeMap, } /// Information about a device in the VM #[derive(Debug, Clone)] -pub struct DeviceInfo { - /// Device type (emulated or passthrough) - pub device_type: DeviceType, - /// Base address in guest physical memory - pub gpa: GuestPhysAddr, - /// Base address in host physical memory (for passthrough) - pub hpa: Option, - /// Size of the device memory region - pub size: usize, - /// Device-specific configuration - pub config: DeviceConfig, -} - -/// Device type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DeviceType { - /// Emulated device - Emulated, - /// Passthrough device - Passthrough, -} - -/// Device-specific configuration -#[derive(Debug, Clone)] -pub enum DeviceConfig { - /// Generic MMIO device - Mmio { - /// Access flags - flags: MappingFlags, - }, - /// Generic PCI device - Pci { - /// PCI bus number - bus: u8, - /// PCI device number - device: u8, - /// PCI function number - function: u8, - }, - /// Interrupt controller - InterruptController { - /// Controller type (GICv2, GICv3, etc.) - controller_type: String, - /// Number of interrupt lines - num_interrupts: u32, - }, - /// Timer device - Timer { - /// Timer type - timer_type: String, - }, - /// Other device type - Other { - /// Device-specific data - data: Vec, - }, -} +pub struct DeviceInfo {} diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index b244e3d..47b2d40 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -1,17 +1,23 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use alloc::{collections::BTreeMap, string::String}; +use super::AddrSpace; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; use crate::{ - arch::RunData, + arch::{RunData, cpu::VCpu}, config::AxVMConfig, + vhal::cpu::CpuId, vm::{Status, VmId, VmOps}, }; +const VM_ASPACE_BASE: usize = 0x0; +const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; + /// AArch64 Virtual Machine implementation pub struct ArchVm { pub id: VmId, pub name: String, + pt_levels: usize, state: Option, stop_requested: AtomicBool, exit_code: AtomicUsize, @@ -23,6 +29,7 @@ impl ArchVm { let vm = Self { id: config.id().into(), name: config.name(), + pt_levels: 4, state: Some(StateMachine::Idle(config)), stop_requested: AtomicBool::new(false), exit_code: AtomicUsize::new(0), @@ -32,25 +39,46 @@ impl ArchVm { /// Initializes the VM, creating vCPUs and setting up memory pub fn init(&mut self) -> anyhow::Result<()> { + debug!("Initializing VM {} ({})", self.id, self.name); let StateMachine::Idle(config) = self.state.take().unwrap() else { return Err(anyhow::anyhow!("VM is not in Idle state")); }; // Create vCPUs - // let mut vcpus = BTreeMap::new(); - - // for (hard_id, cpu_config) in config.phys_cpu_ls.iter() { - - // } + let mut vcpus = Vec::new(); + let dtb_addr = config + .image_config + .dtb_load_gpa + .map(|d| d.as_usize()) + .unwrap_or_default(); + + for cfg in config.phys_cpu_ls.iter() { + let vcpu = VCpu::new(cfg.pcpu_id.map(|id| CpuId::new(id)), dtb_addr.into())?; + debug!("Created vCPU with {:?}", vcpu.id); + vcpus.push(vcpu); + } + let vcpu_count = vcpus.len(); - let vcpu_count = config.phys_cpu_ls.cpu_num(); + for vcpu in &vcpus { + let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); + if max_levels < self.pt_levels { + self.pt_levels = max_levels; + } + } - + debug!( + "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", + self.id, self.name, vcpu_count, self.pt_levels + ); - // // Create address space for the VM - // let address_space = AddrSpace::new_empty(GuestPhysAddr::from(0x0), 0x7fff_ffff_f000) - // .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; + // Create address space for the VM + let address_space = AddrSpace::new_empty( + self.pt_levels, + axaddrspace::GuestPhysAddr::from(VM_ASPACE_BASE), + VM_ASPACE_SIZE, + ) + .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; // // Initialize devices // let mut devices = BTreeMap::new(); @@ -196,13 +224,13 @@ impl ArchVm { match self.get_state_mut() { StateMachine::Running(data) => { // Transition to ShuttingDown state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::ShuttingDown(old_data))?; + // let new_data = RunData { + // // vcpus: BTreeMap::new(), + // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // devices: BTreeMap::new(), + // }; + // let old_data = core::mem::replace(data, new_data); + // self.transition_state(StateMachine::ShuttingDown(old_data))?; // Clean up resources self.cleanup_resources()?; @@ -258,13 +286,13 @@ impl VmOps for ArchVm { }; // Transition to Running state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::Running(old_data))?; + // let new_data = RunData { + // // vcpus: BTreeMap::new(), + // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // devices: BTreeMap::new(), + // }; + // let old_data = core::mem::replace(data, new_data); + // self.transition_state(StateMachine::Running(old_data))?; // // Start all vCPUs // let vcpus = self.get_vcpus(); @@ -372,14 +400,14 @@ impl ArchVm { _ => return Err(anyhow::anyhow!("VM is not in Running state")), }; - // Transition to Inited state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::Inited(old_data))?; + // // Transition to Inited state + // let new_data = RunData { + // // vcpus: BTreeMap::new(), + // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // devices: BTreeMap::new(), + // }; + // let old_data = core::mem::replace(data, new_data); + // self.transition_state(StateMachine::Inited(old_data))?; // // Unbind all vCPUs // let vcpus = self.get_vcpus(); @@ -401,14 +429,14 @@ impl ArchVm { _ => return Err(anyhow::anyhow!("VM is not in Inited state")), }; - // Transition to Running state - let new_data = RunData { - // vcpus: BTreeMap::new(), - // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - devices: BTreeMap::new(), - }; - let old_data = core::mem::replace(data, new_data); - self.transition_state(StateMachine::Running(old_data))?; + // // Transition to Running state + // let new_data = RunData { + // // vcpus: BTreeMap::new(), + // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), + // devices: BTreeMap::new(), + // }; + // let old_data = core::mem::replace(data, new_data); + // self.transition_state(StateMachine::Running(old_data))?; // // Bind all vCPUs // let vcpus = self.get_vcpus(); diff --git a/src/config.rs b/src/config.rs index cda4117..14c4c34 100644 --- a/src/config.rs +++ b/src/config.rs @@ -198,7 +198,23 @@ pub struct PhysCpuList { phys_cpu_sets: Option>, } +#[derive(Debug, Clone, Copy)] +pub struct CpuConfig { + pub vcpu_id: usize, + pub pcpu_id: Option, +} + impl PhysCpuList { + pub fn iter(&self) -> impl Iterator + '_ { + (0..self.cpu_num).map(move |vcpu_id| { + let pcpu_id = self + .phys_cpu_ids + .as_ref() + .and_then(|ids| ids.get(vcpu_id).cloned()); + CpuConfig { vcpu_id, pcpu_id } + }) + } + /// 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. From c9dd964d9f5266804402a1f13da29603b40f4530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 25 Nov 2025 16:03:23 +0800 Subject: [PATCH 27/74] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E6=9C=BA=E9=85=8D=E7=BD=AE=E5=92=8C=20vCPU=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E9=80=BB=E8=BE=91=EF=BC=8C=E6=9B=B4=E6=96=B0=20CPU=20?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E7=AE=A1=E7=90=86=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=86=85=E5=AD=98=E5=9C=B0=E5=9D=80=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm.rs | 34 ++++--- src/config.rs | 197 ++++++++++++++--------------------------- src/lib.rs | 2 + 3 files changed, 92 insertions(+), 141 deletions(-) diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 47b2d40..afa1a12 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -4,6 +4,7 @@ use super::AddrSpace; use alloc::{collections::BTreeMap, string::String, vec::Vec}; use crate::{ + GuestPhysAddr, arch::{RunData, cpu::VCpu}, config::AxVMConfig, vhal::cpu::CpuId, @@ -46,16 +47,29 @@ impl ArchVm { // Create vCPUs let mut vcpus = Vec::new(); - let dtb_addr = config - .image_config - .dtb_load_gpa - .map(|d| d.as_usize()) - .unwrap_or_default(); - - for cfg in config.phys_cpu_ls.iter() { - let vcpu = VCpu::new(cfg.pcpu_id.map(|id| CpuId::new(id)), dtb_addr.into())?; - debug!("Created vCPU with {:?}", vcpu.id); - vcpus.push(vcpu); + // let dtb_addr = config + // .image_config + // .dtb_load_gpa + // .map(|d| d.as_usize()) + // .unwrap_or_default(); + + let dtb_addr = GuestPhysAddr::from_usize(0); + + match config.cpu_num { + crate::config::CpuNumType::Alloc(num) => { + for i in 0..num { + let vcpu = VCpu::new(None, dtb_addr)?; + debug!("Created vCPU with {:?}", vcpu.id); + vcpus.push(vcpu); + } + } + crate::config::CpuNumType::Fixed(ref ids) => { + for id in ids { + let vcpu = VCpu::new(Some(*id), dtb_addr)?; + debug!("Created vCPU with {:?}", vcpu.id); + vcpus.push(vcpu); + } + } } let vcpu_count = vcpus.len(); diff --git a/src/config.rs b/src/config.rs index 14c4c34..4829bde 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,13 +4,15 @@ use alloc::string::String; use alloc::vec::Vec; -use axaddrspace::GuestPhysAddr; +use axvm_types::addr::GuestPhysAddr; pub use axvmconfig::{ AxVMCrateConfig, EmulatedDeviceConfig, PassThroughAddressConfig, PassThroughDeviceConfig, VMInterruptMode, VMType, VmMemConfig, VmMemMappingType, }; +use crate::vhal::cpu::CpuId; + // /// A part of `AxVCpuConfig`, which represents an architecture-dependent `VCpu`. // /// // /// The concrete type of configuration is defined in `AxArchVCpuImpl`. @@ -30,70 +32,86 @@ pub struct AxVCpuConfig { pub ap_entry: GuestPhysAddr, } -/// A part of `AxVMConfig`, which stores configuration attributes related to the load address of VM images. #[derive(Debug, Default, Clone)] pub struct VMImageConfig { + pub gpa: Option, + pub data: Vec, +} + +/// A part of `AxVMConfig`, which stores configuration attributes related to the load address of VM images. +#[derive(Debug, Default, Clone)] +pub struct VMImagesConfig { /// The load address in GPA for the kernel image. - pub kernel_load_gpa: GuestPhysAddr, + pub kernel: VMImageConfig, /// The load address in GPA for the BIOS image, `None` if not used. - pub bios_load_gpa: Option, + pub bios: Option, /// The load address in GPA for the device tree blob (DTB), `None` if not used. - pub dtb_load_gpa: Option, + pub dtb: Option, /// The load address in GPA for the ramdisk image, `None` if not used. - pub ramdisk_load_gpa: Option, + pub ramdisk: Option, } /// A part of `AxVMCrateConfig`, which represents a `VM`. #[derive(Debug, Default)] pub struct AxVMConfig { - id: usize, - name: String, - #[allow(dead_code)] - vm_type: VMType, - pub(crate) phys_cpu_ls: PhysCpuList, + pub id: usize, + pub name: String, + pub cpu_num: CpuNumType, pub cpu_config: AxVCpuConfig, - pub image_config: VMImageConfig, - emu_devices: Vec, - pass_through_devices: Vec, - excluded_devices: Vec>, - pass_through_addresses: Vec, + pub image_config: VMImagesConfig, + pub emu_devices: Vec, + pub pass_through_devices: Vec, + pub excluded_devices: Vec>, + pub pass_through_addresses: Vec, // TODO: improve interrupt passthrough - spi_list: Vec, - interrupt_mode: VMInterruptMode, + pub spi_list: Vec, + pub interrupt_mode: VMInterruptMode, +} + +#[derive(Debug, Clone)] +pub enum CpuNumType { + Alloc(usize), + Fixed(Vec), } -impl From for AxVMConfig { - fn from(cfg: AxVMCrateConfig) -> Self { - Self { - id: cfg.base.id, - name: cfg.base.name, - vm_type: VMType::from(cfg.base.vm_type), - 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), - }, - image_config: VMImageConfig { - kernel_load_gpa: GuestPhysAddr::from(cfg.kernel.kernel_load_addr), - bios_load_gpa: cfg.kernel.bios_load_addr.map(GuestPhysAddr::from), - 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, - 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, - } +impl Default for CpuNumType { + fn default() -> Self { + CpuNumType::Alloc(1) } } +// impl From for AxVMConfig { +// fn from(cfg: AxVMCrateConfig) -> Self { +// Self { +// id: cfg.base.id, +// name: cfg.base.name, +// vm_type: VMType::from(cfg.base.vm_type), +// 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), +// }, +// image_config: VMImagesConfig { +// kernel_load_gpa: GuestPhysAddr::from(cfg.kernel.kernel_load_addr), +// bios_load_gpa: cfg.kernel.bios_load_addr.map(GuestPhysAddr::from), +// 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, +// 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, +// } +// } +// } + impl AxVMConfig { /// Returns VM id. pub fn id(&self) -> usize { @@ -106,7 +124,7 @@ impl AxVMConfig { } /// Returns configurations related to VM image load addresses. - pub fn image_config(&self) -> &VMImageConfig { + pub fn image_config(&self) -> &VMImagesConfig { &self.image_config } @@ -122,10 +140,6 @@ impl AxVMConfig { self.cpu_config.ap_entry } - pub fn phys_cpu_ls_mut(&mut self) -> &mut PhysCpuList { - &mut self.phys_cpu_ls - } - pub fn excluded_devices(&self) -> &Vec> { &self.excluded_devices } @@ -190,82 +204,3 @@ impl AxVMConfig { self.interrupt_mode } } - -#[derive(Debug, Default, Clone)] -pub struct PhysCpuList { - cpu_num: usize, - phys_cpu_ids: Option>, - phys_cpu_sets: Option>, -} - -#[derive(Debug, Clone, Copy)] -pub struct CpuConfig { - pub vcpu_id: usize, - pub pcpu_id: Option, -} - -impl PhysCpuList { - pub fn iter(&self) -> impl Iterator + '_ { - (0..self.cpu_num).map(move |vcpu_id| { - let pcpu_id = self - .phys_cpu_ids - .as_ref() - .and_then(|ids| ids.get(vcpu_id).cloned()); - CpuConfig { vcpu_id, pcpu_id } - }) - } - - /// 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(); - - if let Some(phys_cpu_ids) = &self.phys_cpu_ids { - if 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)); - } - - 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 - } - - pub fn cpu_num(&self) -> usize { - self.cpu_num - } - - pub fn phys_cpu_ids(&self) -> &Option> { - &self.phys_cpu_ids - } - - pub fn phys_cpu_sets(&self) -> &Option> { - &self.phys_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/lib.rs b/src/lib.rs index 8b9ec21..66d6a6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,9 @@ mod vm; pub mod config; pub mod vhal; +pub use axvm_types::addr::*; pub use config::AxVMConfig; +pub use vhal::cpu::CpuId; pub use vm::*; /// Enable hardware virtualization support. From 70ccc7220c682bb128a71f7560497cda3256a71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 25 Nov 2025 17:25:32 +0800 Subject: [PATCH 28/74] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E6=9C=BA=E5=86=85=E5=AD=98=E7=AE=A1=E7=90=86=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=86=85=E5=AD=98=E5=8C=BA=E5=9F=9F=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=20RunData=20=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/mod.rs | 10 ------ src/arch/aarch64/vm.rs | 73 ++++++++++++++++++++++++++++++--------- src/config.rs | 13 ++++++- src/lib.rs | 1 + src/region.rs | 76 +++++++++++++++++++++++++++++++++++++++++ src/vhal/mod.rs | 13 +++++++ 6 files changed, 159 insertions(+), 27 deletions(-) create mode 100644 src/region.rs diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index ea35a45..6ddc684 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -64,13 +64,3 @@ impl fmt::Display for VmId { } } -/// Data needed when VM is running -pub struct RunData { - vcpus: Vec, - address_space: AddrSpace, - devices: BTreeMap, -} - -/// Information about a device in the VM -#[derive(Debug, Clone)] -pub struct DeviceInfo {} diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index afa1a12..7363062 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -4,11 +4,12 @@ use super::AddrSpace; use alloc::{collections::BTreeMap, string::String, vec::Vec}; use crate::{ - GuestPhysAddr, - arch::{RunData, cpu::VCpu}, - config::AxVMConfig, - vhal::cpu::CpuId, + arch::cpu::VCpu, + config::{AxVMConfig, MemoryKind}, + region::Region, + vhal::{cpu::CpuId, phys_to_virt}, vm::{Status, VmId, VmOps}, + {GuestPhysAddr, HostPhysAddr, HostVirtAddr}, }; const VM_ASPACE_BASE: usize = 0x0; @@ -47,11 +48,6 @@ impl ArchVm { // Create vCPUs let mut vcpus = Vec::new(); - // let dtb_addr = config - // .image_config - // .dtb_load_gpa - // .map(|d| d.as_usize()) - // .unwrap_or_default(); let dtb_addr = GuestPhysAddr::from_usize(0); @@ -94,8 +90,24 @@ impl ArchVm { ) .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; - // // Initialize devices - // let mut devices = BTreeMap::new(); + let mut run_data = RunData { + vcpus, + address_space, + regions: Vec::new(), + devices: BTreeMap::new(), + }; + + debug!("Mapping memory regions for VM {} ({})", self.id, self.name); + for memory_cfg in config.memory_regions { + run_data.add_memory_region(memory_cfg)?; + } + + debug!( + "Mapped {} memory regions for VM {} ({})", + run_data.regions.len(), + self.id, + self.name + ); // // Add emulated devices // for emu_device in config.emu_devices() { @@ -184,11 +196,7 @@ impl ArchVm { // .map_err(|e| anyhow::anyhow!("Failed to setup vCPU {}: {:?}", vcpu_id, e))?; // } - // self.state = Some(StateMachine::Inited(RunData { - // vcpus, - // address_space, - // devices, - // })); + self.state = Some(StateMachine::Inited(run_data)); Ok(()) } @@ -499,3 +507,36 @@ enum StateMachine { ShuttingDown(RunData), PoweredOff, } + +/// Data needed when VM is running +pub struct RunData { + vcpus: Vec, + address_space: AddrSpace, + regions: Vec, + devices: BTreeMap, +} + +impl RunData { + fn add_memory_region(&mut self, config: MemoryKind) -> anyhow::Result<()> { + let region = Region::new(config); + self.address_space + .map_linear( + region.gpa.as_usize().into(), + region.hva.as_usize().into(), + region.size, + axaddrspace::MappingFlags::READ + | axaddrspace::MappingFlags::WRITE + | axaddrspace::MappingFlags::EXECUTE + | axaddrspace::MappingFlags::USER, + ) + .map_err(|e| anyhow::anyhow!("Failed to map memory region: {:?}", e))?; + + self.regions.push(region); + + Ok(()) + } +} + +/// Information about a device in the VM +#[derive(Debug, Clone)] +pub struct DeviceInfo {} diff --git a/src/config.rs b/src/config.rs index 4829bde..0c84a2a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use alloc::string::String; use alloc::vec::Vec; -use axvm_types::addr::GuestPhysAddr; +use crate::{GuestPhysAddr, HostPhysAddr}; pub use axvmconfig::{ AxVMCrateConfig, EmulatedDeviceConfig, PassThroughAddressConfig, PassThroughDeviceConfig, @@ -51,6 +51,16 @@ pub struct VMImagesConfig { pub ramdisk: Option, } +#[derive(Debug, Clone)] +pub enum MemoryKind { + /// Use identical memory regions + Identical { size: usize }, + /// Use memory regions mapped from host physical address + Passthrough { hpa: HostPhysAddr, size: usize }, + /// Use fixed memory regions + Fixed { gpa: GuestPhysAddr, size: usize }, +} + /// A part of `AxVMCrateConfig`, which represents a `VM`. #[derive(Debug, Default)] pub struct AxVMConfig { @@ -63,6 +73,7 @@ pub struct AxVMConfig { pub pass_through_devices: Vec, pub excluded_devices: Vec>, pub pass_through_addresses: Vec, + pub memory_regions: Vec, // TODO: improve interrupt passthrough pub spi_list: Vec, pub interrupt_mode: VMInterruptMode, diff --git a/src/lib.rs b/src/lib.rs index 66d6a6e..082c23a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub(crate) mod arch; mod fdt; mod vcpu; mod vm; +mod region; pub mod config; pub mod vhal; diff --git a/src/region.rs b/src/region.rs new file mode 100644 index 0000000..b57a749 --- /dev/null +++ b/src/region.rs @@ -0,0 +1,76 @@ +use core::{alloc::Layout, ops::Range}; + +use alloc::vec::Vec; + +use crate::{ + GuestPhysAddr, HostVirtAddr, + config::MemoryKind, + vhal::{phys_to_virt, virt_to_phys}, +}; + +const ALIGN: usize = 1024 * 1024 * 2; + +#[derive(Debug, Clone)] +pub struct Region { + pub gpa: GuestPhysAddr, + pub hva: HostVirtAddr, + pub size: usize, + pub own: bool, +} + +impl Region { + pub fn new(kind: MemoryKind) -> Self { + match kind { + MemoryKind::Identical { size } => { + let hva = HostVirtAddr::from(unsafe { + alloc::alloc::alloc(Layout::from_size_align_unchecked(size, ALIGN)) + } as usize); + let gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); + Region { + gpa, + hva, + size, + own: true, + } + } + MemoryKind::Passthrough { hpa, size } => { + let hva = phys_to_virt(hpa); + let gpa = GuestPhysAddr::from_usize(hva.as_usize()); + Region { + gpa, + hva, + size, + own: false, + } + } + MemoryKind::Fixed { gpa, size } => { + let hva = HostVirtAddr::from(unsafe { + alloc::alloc::alloc(Layout::from_size_align_unchecked(size, ALIGN)) + } as usize); + Region { + gpa, + hva, + size, + own: true, + } + } + } + } + + pub fn buffer_mut(&self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.hva.as_mut_ptr(), self.size) } + } +} + +impl Drop for Region { + fn drop(&mut self) { + if self.own { + unsafe { + alloc::alloc::dealloc( + self.hva.as_mut_ptr(), + alloc::alloc::Layout::from_size_align(self.size, ALIGN).unwrap(), + ); + } + } + } +} diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index d325880..efa0214 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -12,6 +12,7 @@ use crate::{ cpu::{CpuHardId, CpuId}, precpu::PreCpuSet, }, + {HostPhysAddr, HostVirtAddr}, }; use axconfig::TASK_STACK_SIZE; use axtask::AxCpuMask; @@ -82,3 +83,15 @@ pub(crate) trait ArchHal { pub(crate) trait ArchCpuData { fn hard_id(&self) -> CpuHardId; } + +pub fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr { + axhal::mem::phys_to_virt(paddr.as_usize().into()) + .as_usize() + .into() +} + +pub fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr { + axhal::mem::virt_to_phys(vaddr.as_usize().into()) + .as_usize() + .into() +} From c43052a8b225ac84151c16989871897af6652795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 26 Nov 2025 14:37:10 +0800 Subject: [PATCH 29/74] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E6=9C=BA=E6=9E=B6=E6=9E=84=EF=BC=8C=E4=BC=98=E5=8C=96=E5=86=85?= =?UTF-8?q?=E5=AD=98=E5=8C=BA=E5=9F=9F=E7=AE=A1=E7=90=86=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=BC=93=E5=AD=98=E5=88=B7=E6=96=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E8=BF=9B=E8=99=9A=E6=8B=9F=E6=9C=BA=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/mod.rs | 7 +- src/arch/aarch64/vm.rs | 165 +++++++++++++++++++++++++++++++++++----- src/config.rs | 13 ---- src/region.rs | 16 ++-- src/vhal/mod.rs | 1 + src/vm.rs | 5 +- 6 files changed, 165 insertions(+), 42 deletions(-) diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 6ddc684..4225a2a 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,4 +1,6 @@ use aarch64_cpu::registers::MPIDR_EL1; +use aarch64_cpu_ext::asm::cache; +use aarch64_cpu_ext::cache::{CacheOp, dcache_range}; use axhal::percpu::this_cpu_id; use core::fmt; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -55,6 +57,10 @@ impl ArchHal for Hal { let mpidr = MPIDR_EL1.get() as usize; CpuHardId::new(mpidr) } + + fn cache_flush(vaddr: arm_vcpu::HostVirtAddr, size: usize) { + dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); + } } // Implement Display for VmId @@ -63,4 +69,3 @@ impl fmt::Display for VmId { write!(f, "VmId({:?})", self) } } - diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 7363062..9e5fa73 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -4,12 +4,12 @@ use super::AddrSpace; use alloc::{collections::BTreeMap, string::String, vec::Vec}; use crate::{ + GuestPhysAddr, HostPhysAddr, HostVirtAddr, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, region::Region, - vhal::{cpu::CpuId, phys_to_virt}, + vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, vm::{Status, VmId, VmOps}, - {GuestPhysAddr, HostPhysAddr, HostVirtAddr}, }; const VM_ASPACE_BASE: usize = 0x0; @@ -20,19 +20,19 @@ pub struct ArchVm { pub id: VmId, pub name: String, pt_levels: usize, - state: Option, + state: StateMachine, stop_requested: AtomicBool, exit_code: AtomicUsize, } impl ArchVm { /// Creates a new VM with the given configuration - pub fn new(config: AxVMConfig) -> anyhow::Result { + pub fn new(config: &AxVMConfig) -> anyhow::Result { let vm = Self { id: config.id().into(), name: config.name(), pt_levels: 4, - state: Some(StateMachine::Idle(config)), + state: StateMachine::Idle, stop_requested: AtomicBool::new(false), exit_code: AtomicUsize::new(0), }; @@ -40,9 +40,9 @@ impl ArchVm { } /// Initializes the VM, creating vCPUs and setting up memory - pub fn init(&mut self) -> anyhow::Result<()> { + pub fn init(&mut self, config: AxVMConfig) -> anyhow::Result<()> { debug!("Initializing VM {} ({})", self.id, self.name); - let StateMachine::Idle(config) = self.state.take().unwrap() else { + if !matches!(self.state, StateMachine::Idle) { return Err(anyhow::anyhow!("VM is not in Idle state")); }; @@ -95,10 +95,15 @@ impl ArchVm { address_space, regions: Vec::new(), devices: BTreeMap::new(), + kernel_entry: GuestPhysAddr::from_usize(0), + dtb_addr: GuestPhysAddr::from_usize(0), + dtb_data: Vec::new(), + ramdisk_data: Vec::new(), + bios_data: Vec::new(), }; debug!("Mapping memory regions for VM {} ({})", self.id, self.name); - for memory_cfg in config.memory_regions { + for memory_cfg in &config.memory_regions { run_data.add_memory_region(memory_cfg)?; } @@ -109,6 +114,12 @@ impl ArchVm { self.name ); + run_data.load_images(&config)?; + for vcpu in &mut run_data.vcpus { + vcpu.vcpu.set_entry(run_data.kernel_entry).unwrap(); + vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); + } + // // Add emulated devices // for emu_device in config.emu_devices() { // let device_info = DeviceInfo { @@ -196,7 +207,7 @@ impl ArchVm { // .map_err(|e| anyhow::anyhow!("Failed to setup vCPU {}: {:?}", vcpu_id, e))?; // } - self.state = Some(StateMachine::Inited(run_data)); + self.state = StateMachine::Inited(run_data); Ok(()) } @@ -208,12 +219,12 @@ impl ArchVm { /// Gets the current state of the VM fn get_state(&self) -> &StateMachine { - self.state.as_ref().unwrap() + &self.state } /// Gets a mutable reference to the current state of the VM fn get_state_mut(&mut self) -> &mut StateMachine { - self.state.as_mut().unwrap() + &mut self.state } /// Transitions the VM state from current to new state @@ -222,14 +233,14 @@ impl ArchVm { // Validate state transition match (current_state, &new_state) { - (StateMachine::Idle(_), StateMachine::Inited(_)) => {} + (StateMachine::Idle, StateMachine::Inited(_)) => {} (StateMachine::Inited(_), StateMachine::Running(_)) => {} (StateMachine::Running(_), StateMachine::ShuttingDown(_)) => {} (StateMachine::ShuttingDown(_), StateMachine::PoweredOff) => {} _ => return Err(anyhow::anyhow!("Invalid state transition")), } - self.state = Some(new_state); + self.state = new_state; Ok(()) } @@ -358,7 +369,7 @@ impl VmOps for ArchVm { fn status(&self) -> Status { match self.get_state() { - StateMachine::Idle(_) => Status::Idle, + StateMachine::Idle => Status::Idle, StateMachine::Inited(_) => Status::Idle, StateMachine::Running(_) => Status::Running, StateMachine::ShuttingDown(_) => Status::ShuttingDown, @@ -476,7 +487,7 @@ impl ArchVm { /// Gets the current state as a string pub fn state_str(&self) -> &'static str { match self.get_state() { - StateMachine::Idle(_) => "Idle", + StateMachine::Idle => "Idle", StateMachine::Inited(_) => "Inited", StateMachine::Running(_) => "Running", StateMachine::ShuttingDown(_) => "ShuttingDown", @@ -501,7 +512,7 @@ impl ArchVm { /// VM state machine enum StateMachine { - Idle(AxVMConfig), + Idle, Inited(RunData), Running(RunData), ShuttingDown(RunData), @@ -514,10 +525,15 @@ pub struct RunData { address_space: AddrSpace, regions: Vec, devices: BTreeMap, + kernel_entry: GuestPhysAddr, + dtb_addr: GuestPhysAddr, + dtb_data: Vec, + ramdisk_data: Vec, + bios_data: Vec, } impl RunData { - fn add_memory_region(&mut self, config: MemoryKind) -> anyhow::Result<()> { + fn add_memory_region(&mut self, config: &MemoryKind) -> anyhow::Result<()> { let region = Region::new(config); self.address_space .map_linear( @@ -535,6 +551,121 @@ impl RunData { Ok(()) } + + fn load_images(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { + // Load other images (BIOS, DTB, Ramdisk) similarly... + debug!( + "Loading kernel image for VM {} ({})", + config.id(), + config.name() + ); + let _main_region_idx = self.load_kernel_image(config)?; + self.load_dtb_image(config)?; + + Ok(()) + } + + /// Returns the loaded kernel region's index + fn load_kernel_image(&mut self, config: &AxVMConfig) -> anyhow::Result { + let mut idx = 0; + let image_cfg = config.image_config(); + let gpa = if let Some(gpa) = image_cfg.kernel.gpa { + let mut found = false; + for (i, region) in self.regions.iter().enumerate() { + if (region.gpa..region.gpa + region.size).contains(&gpa) { + idx = i; + found = true; + break; + } + } + if !found { + return Err(anyhow!( + "Kernel load GPA {:#x} not within any memory region", + gpa.as_usize() + )); + } + gpa + } else { + let mut gpa = None; + for (i, region) in self.regions.iter().enumerate() { + if region.size >= image_cfg.kernel.data.len() { + gpa = Some(region.gpa + 2 * 1024 * 1024); + idx = i; + break; + } else { + continue; + } + } + gpa.ok_or(anyhow!("No suitable memory region found for kernel image"))? + }; + + debug!( + "Loading kernel image into GPA @{:#x} for VM {} ({})", + gpa.as_usize(), + config.id(), + config.name() + ); + self.load_image_data(gpa, &image_cfg.kernel.data)?; + self.kernel_entry = gpa; + + Ok(idx) + } + + fn load_dtb_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { + let image_cfg = config.image_config(); + + if let Some(dtb_cfg) = &image_cfg.dtb { + let size = dtb_cfg.data.len(); + self.dtb_data = Vec::with_capacity(size / 4); + + let gpa = if let Some(gpa) = dtb_cfg.gpa { + gpa + } else { + (self.dtb_data.as_mut_ptr() as usize).into() + }; + self.address_space + .map_linear( + gpa.as_usize().into(), + virt_to_phys(HostVirtAddr::from(self.dtb_data.as_mut_ptr() as usize)) + .as_usize() + .into(), + size, + axaddrspace::MappingFlags::READ | axaddrspace::MappingFlags::USER, + ) + .map_err(|e| anyhow::anyhow!("Failed to map DTB region: {:?}", e))?; + + debug!( + "Loading DTB image into GPA @{:#x} for VM {} ({})", + gpa.as_usize(), + config.id(), + config.name() + ); + self.dtb_addr = gpa; + self.load_image_data(gpa, &dtb_cfg.data)?; + } + + Ok(()) + } + + fn load_image_data(&mut self, gpa: GuestPhysAddr, data: &[u8]) -> anyhow::Result<()> { + let hva = self + .address_space + .translated_byte_buffer(gpa.as_usize().into(), data.len()) + .ok_or(anyhow!("Fail to load [{gpa:?}, {:?})", gpa + data.len()))?; + let mut remain = data; + + for buff in hva { + let copy_size = core::cmp::min(remain.len(), buff.len()); + buff[..copy_size].copy_from_slice(&remain[..copy_size]); + crate::arch::Hal::cache_flush(HostVirtAddr::from(buff.as_ptr() as usize), copy_size); + remain = &remain[copy_size..]; + if remain.is_empty() { + break; + } + } + + Ok(()) + } } /// Information about a device in the VM diff --git a/src/config.rs b/src/config.rs index 0c84a2a..5ffcf9b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -67,7 +67,6 @@ pub struct AxVMConfig { pub id: usize, pub name: String, pub cpu_num: CpuNumType, - pub cpu_config: AxVCpuConfig, pub image_config: VMImagesConfig, pub emu_devices: Vec, pub pass_through_devices: Vec, @@ -139,18 +138,6 @@ impl AxVMConfig { &self.image_config } - /// Returns the entry address in GPA for the Bootstrap Processor (BSP). - pub fn bsp_entry(&self) -> GuestPhysAddr { - // Retrieves BSP entry from the CPU configuration. - self.cpu_config.bsp_entry - } - - /// Returns the entry address in GPA for the Application Processor (AP). - pub fn ap_entry(&self) -> GuestPhysAddr { - // Retrieves AP entry from the CPU configuration. - self.cpu_config.ap_entry - } - pub fn excluded_devices(&self) -> &Vec> { &self.excluded_devices } diff --git a/src/region.rs b/src/region.rs index b57a749..2affe5b 100644 --- a/src/region.rs +++ b/src/region.rs @@ -19,38 +19,38 @@ pub struct Region { } impl Region { - pub fn new(kind: MemoryKind) -> Self { + pub fn new(kind: &MemoryKind) -> Self { match kind { MemoryKind::Identical { size } => { let hva = HostVirtAddr::from(unsafe { - alloc::alloc::alloc(Layout::from_size_align_unchecked(size, ALIGN)) + alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) } as usize); let gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); Region { gpa, hva, - size, + size: *size, own: true, } } MemoryKind::Passthrough { hpa, size } => { - let hva = phys_to_virt(hpa); + let hva = phys_to_virt(*hpa); let gpa = GuestPhysAddr::from_usize(hva.as_usize()); Region { gpa, hva, - size, + size: *size, own: false, } } MemoryKind::Fixed { gpa, size } => { let hva = HostVirtAddr::from(unsafe { - alloc::alloc::alloc(Layout::from_size_align_unchecked(size, ALIGN)) + alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) } as usize); Region { - gpa, + gpa: *gpa, hva, - size, + size: *size, own: true, } } diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index efa0214..3aba349 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -75,6 +75,7 @@ pub fn cpu_count() -> usize { pub(crate) trait ArchHal { fn init() -> anyhow::Result<()>; + fn cache_flush(vaddr: HostVirtAddr, size: usize); fn cpu_hard_id() -> CpuHardId; fn cpu_list() -> Vec; fn current_cpu_init(id: CpuId) -> anyhow::Result; diff --git a/src/vm.rs b/src/vm.rs index ff8235c..26101a5 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -48,9 +48,8 @@ pub struct Vm { impl Vm { pub fn new(config: AxVMConfig) -> anyhow::Result { - let mut arch_vm = crate::arch::ArchVm::new(config)?; - arch_vm.init()?; - + let mut arch_vm = crate::arch::ArchVm::new(&config)?; + arch_vm.init(config)?; Ok(Vm { id: arch_vm.id(), name: arch_vm.name().into(), From db7db820c048f9186e8ca8ed8a7cdc45d685424f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 27 Nov 2025 10:36:28 +0800 Subject: [PATCH 30/74] feat: add cache flush --- Cargo.toml | 5 +- src/arch/aarch64/cpu.rs | 19 ++++++- src/arch/aarch64/mod.rs | 8 +-- src/arch/aarch64/vm.rs | 120 +++++++++++++++++++++------------------- src/lib.rs | 4 +- src/region.rs | 12 ++-- src/vhal/cpu.rs | 4 ++ src/vhal/mod.rs | 36 ++++++------ src/vm.rs | 8 +++ 9 files changed, 121 insertions(+), 95 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c536e1..0d41947 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,10 +36,9 @@ axaddrspace = "0.2" # axdevice_base = "0.1" # axvcpu = "0.1" axvmconfig = {version = "0.1", default-features = false} -axconfig = {workspace = true} -axhal.workspace = true axruntime.workspace = true -axtask.workspace = true +axhal.workspace = true +axstd.workspace = true axvm-types.workspace = true [target.'cfg(target_arch = "x86_64")'.dependencies] diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 91ac731..41f6224 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -6,9 +6,12 @@ use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; use axhal::percpu::this_cpu_id; use axvm_types::addr::*; -use crate::vhal::{ - ArchCpuData, - cpu::{CpuHardId, CpuId, HCpuExclusive}, +use crate::{ + TASK_STACK_SIZE, VmId, + vhal::{ + ArchCpuData, + cpu::{CpuHardId, CpuId, HCpuExclusive}, + }, }; pub struct HCpu { @@ -106,4 +109,14 @@ impl VCpu { { self.hcpu.with_cpu(f) } + + pub fn binded_cpu_id(&self) -> CpuId { + self.hcpu.cpu_id() + } + + pub fn run(&mut self) -> anyhow::Result<()> { + info!("Starting vCPU {}", self.id); + + Ok(()) + } } diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 4225a2a..6c8f3f2 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -42,6 +42,7 @@ impl ArchHal for Hal { fn init() -> anyhow::Result<()> { arm_vcpu::init_hal(&cpu::VCpuHal); + Ok(()) } @@ -62,10 +63,3 @@ impl ArchHal for Hal { dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); } } - -// Implement Display for VmId -impl fmt::Display for VmId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "VmId({:?})", self) - } -} diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 9e5fa73..8340c47 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -1,13 +1,15 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}; use super::AddrSpace; use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, HostPhysAddr, HostVirtAddr, + GuestPhysAddr, HostPhysAddr, HostVirtAddr, TASK_STACK_SIZE, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, - region::Region, + region::GuestRegion, vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, vm::{Status, VmId, VmOps}, }; @@ -115,12 +117,8 @@ impl ArchVm { ); run_data.load_images(&config)?; - for vcpu in &mut run_data.vcpus { - vcpu.vcpu.set_entry(run_data.kernel_entry).unwrap(); - vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); - } - // // Add emulated devices + // Add emulated devices // for emu_device in config.emu_devices() { // let device_info = DeviceInfo { // device_type: DeviceType::Emulated, @@ -176,36 +174,27 @@ impl ArchVm { // })?; // } - // // Setup vCPUs - // for (vcpu_id, vcpu) in &vcpus { - // let entry = if *vcpu_id == 0 { - // config.bsp_entry() - // } else { - // config.ap_entry() - // }; - - // let setup_config = AxVCpuSetupConfig { - // passthrough_interrupt: config.interrupt_mode() - // == axvmconfig::VMInterruptMode::Passthrough, - // passthrough_timer: config.interrupt_mode() - // == axvmconfig::VMInterruptMode::Passthrough, - // }; + // Setup vCPUs + for vcpu in &mut run_data.vcpus { + vcpu.vcpu.set_entry(run_data.kernel_entry).unwrap(); + vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); - // // Set entry point first - // vcpu.set_entry(entry).map_err(|e| { - // anyhow::anyhow!("Failed to set entry for vCPU {}: {:?}", vcpu_id, e) - // })?; + let setup_config = Aarch64VCpuSetupConfig { + passthrough_interrupt: config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + passthrough_timer: config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + }; - // // Set EPT root - // vcpu.set_ept_root(address_space.page_table_root()) - // .map_err(|e| { - // anyhow::anyhow!("Failed to set EPT root for vCPU {}: {:?}", vcpu_id, e) - // })?; + vcpu.vcpu + .setup(setup_config) + .map_err(|e| anyhow::anyhow!("Failed to setup vCPU : {e:?}"))?; - // // Setup vCPU with configuration - // vcpu.setup(setup_config) - // .map_err(|e| anyhow::anyhow!("Failed to setup vCPU {}: {:?}", vcpu_id, e))?; - // } + // Set EPT root + vcpu.vcpu + .set_ept_root(run_data.address_space.page_table_root()) + .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; + } self.state = StateMachine::Inited(run_data); @@ -318,29 +307,44 @@ impl VmOps for ArchVm { _ => return Err(anyhow::anyhow!("VM is not in Inited state")), }; - // Transition to Running state - // let new_data = RunData { - // // vcpus: BTreeMap::new(), - // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - // devices: BTreeMap::new(), - // }; - // let old_data = core::mem::replace(data, new_data); - // self.transition_state(StateMachine::Running(old_data))?; - - // // Start all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Starting vCPU {} for VM {}", vcpu_id, self.id); - // vcpu.bind() - // .map_err(|e| anyhow::anyhow!("Failed to bind vCPU {}: {:?}", vcpu_id, e))?; - // } + let mut vcpus = vec![]; + vcpus.append(&mut data.vcpus); + let mut vcpu_handles = vec![]; + let vm_id = self.id; + for mut vcpu in vcpus.into_iter() { + let vcpu_id = vcpu.id; + let bind_id = vcpu.binded_cpu_id(); + let handle = std::thread::Builder::new() + .name(format!("{}-{vcpu_id}", self.id,)) + .stack_size(TASK_STACK_SIZE) + .spawn(move || { + assert!( + set_current_affinity(AxCpuMask::one_shot(bind_id.raw())), + "Initialize CPU affinity failed!" + ); + match vcpu.run() { + Ok(()) => { + info!("vCPU {} of VM {} exited normally", vcpu_id, vm_id); + } + Err(e) => { + error!( + "vCPU {} of VM {} exited with error: {:?}", + vcpu_id, vm_id, e + ); + } + } + }) + .map_err(|e| anyhow!("{e}"))?; + + vcpu_handles.push(handle); + } - // info!( - // "VM {} ({}) booted successfully with {} vCPUs", - // self.id, - // self.name, - // vcpus.len() - // ); + info!( + "VM {} ({}) with {} cpus booted successfully.", + self.id, + self.name, + vcpu_handles.len() + ); Ok(()) } @@ -523,7 +527,7 @@ enum StateMachine { pub struct RunData { vcpus: Vec, address_space: AddrSpace, - regions: Vec, + regions: Vec, devices: BTreeMap, kernel_entry: GuestPhysAddr, dtb_addr: GuestPhysAddr, @@ -534,7 +538,7 @@ pub struct RunData { impl RunData { fn add_memory_region(&mut self, config: &MemoryKind) -> anyhow::Result<()> { - let region = Region::new(config); + let region = GuestRegion::new(config); self.address_space .map_linear( region.gpa.as_usize().into(), diff --git a/src/lib.rs b/src/lib.rs index 082c23a..9845964 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,9 @@ extern crate log; #[macro_use] extern crate anyhow; -const TASK_STACK_SIZE: usize = 0x40000; // 16KB +extern crate axstd as std; + +const TASK_STACK_SIZE: usize = 0x40000; // 256 KB #[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/mod.rs")] #[cfg_attr(target_arch = "x86_64", path = "arch/x86_64/mod.rs")] diff --git a/src/region.rs b/src/region.rs index 2affe5b..623311b 100644 --- a/src/region.rs +++ b/src/region.rs @@ -11,14 +11,14 @@ use crate::{ const ALIGN: usize = 1024 * 1024 * 2; #[derive(Debug, Clone)] -pub struct Region { +pub struct GuestRegion { pub gpa: GuestPhysAddr, pub hva: HostVirtAddr, pub size: usize, pub own: bool, } -impl Region { +impl GuestRegion { pub fn new(kind: &MemoryKind) -> Self { match kind { MemoryKind::Identical { size } => { @@ -26,7 +26,7 @@ impl Region { alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) } as usize); let gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); - Region { + GuestRegion { gpa, hva, size: *size, @@ -36,7 +36,7 @@ impl Region { MemoryKind::Passthrough { hpa, size } => { let hva = phys_to_virt(*hpa); let gpa = GuestPhysAddr::from_usize(hva.as_usize()); - Region { + GuestRegion { gpa, hva, size: *size, @@ -47,7 +47,7 @@ impl Region { let hva = HostVirtAddr::from(unsafe { alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) } as usize); - Region { + GuestRegion { gpa: *gpa, hva, size: *size, @@ -62,7 +62,7 @@ impl Region { } } -impl Drop for Region { +impl Drop for GuestRegion { fn drop(&mut self) { if self.own { unsafe { diff --git a/src/vhal/cpu.rs b/src/vhal/cpu.rs index c6afa27..26b388e 100644 --- a/src/vhal/cpu.rs +++ b/src/vhal/cpu.rs @@ -14,6 +14,10 @@ pub(super) static HCPU_ALLOC: Mutex = Mutex::new(BitAlloc4K::DEFAULT pub struct HCpuExclusive(CpuId); impl HCpuExclusive { + pub fn id(&self) -> CpuId { + self.0 + } + pub fn try_new(id: Option) -> Option { let mut a = HCPU_ALLOC.lock(); match id { diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index 3aba349..0cf7f21 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -1,4 +1,8 @@ use alloc::{collections::BTreeMap, vec::Vec}; +use axstd::{ + os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, + thread::yield_now, +}; use bitmap_allocator::{BitAlloc, BitAlloc4K}; use core::{ fmt::Display, @@ -7,15 +11,13 @@ use core::{ use spin::Mutex; use crate::{ + HostPhysAddr, HostVirtAddr, TASK_STACK_SIZE, arch::{HCpu, Hal}, vhal::{ cpu::{CpuHardId, CpuId}, precpu::PreCpuSet, }, - {HostPhysAddr, HostVirtAddr}, }; -use axconfig::TASK_STACK_SIZE; -use axtask::AxCpuMask; pub(crate) mod cpu; pub(crate) mod precpu; @@ -30,42 +32,42 @@ pub fn init() -> anyhow::Result<()> { info!("Initializing VHal for {cpu_count} CPUs..."); cpu::PRE_CPU.init(); + for cpu_id in 0..cpu_count { let id = CpuId::new(cpu_id); - let _handle = axtask::spawn_raw( - move || { + axstd::thread::Builder::new() + .name(format!("init-cpu-{}", cpu_id)) + .stack_size(TASK_STACK_SIZE) + .spawn(move || { info!("Core {cpu_id} is initializing hardware virtualization support..."); // Initialize cpu affinity here. assert!( - axtask::set_current_affinity(AxCpuMask::one_shot(cpu_id)), + set_current_affinity(AxCpuMask::one_shot(cpu_id)), "Initialize CPU affinity failed!" ); info!("Enabling hardware virtualization support on core {id}"); timer::init_percpu(); - let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); - unsafe { cpu::PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; + // let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); + // unsafe { cpu::PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; let _ = CORES.fetch_add(1, Ordering::Release); - }, - format!("init-cpu-{}", cpu_id), - TASK_STACK_SIZE, - ); - // handles.push(_handle); + }) + .map_err(|e| anyhow!("{e:?}"))?; } info!("Waiting for all cores to enable hardware virtualization..."); // Wait for all cores to enable virtualization. while CORES.load(Ordering::Acquire) != cpu_count { // Use `yield_now` instead of `core::hint::spin_loop` to avoid deadlock. - axtask::yield_now(); + yield_now(); } - // for handle in handles { - // handle.join(); - // } cpu::HCPU_ALLOC.lock().insert(0..cpu_count); info!("All cores have enabled hardware virtualization support."); + + + Ok(()) } diff --git a/src/vm.rs b/src/vm.rs index 26101a5..7cde3b8 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,3 +1,5 @@ +use core::fmt::{self, Display}; + use alloc::string::String; use spin::Mutex; @@ -11,6 +13,12 @@ impl VmId { VmId(id) } } +// Implement Display for VmId +impl fmt::Display for VmId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} impl From for VmId { fn from(value: usize) -> Self { From 8a32bdb09578b9393b9009cdb6cb8d998c3ba901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 27 Nov 2025 14:47:06 +0800 Subject: [PATCH 31/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=E5=92=8C?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=9B=B8=E5=85=B3=E7=BB=93=E6=9E=84=E4=BD=93?= =?UTF-8?q?=E5=92=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/arch/aarch64/mod.rs | 3 +- src/arch/aarch64/vm.rs | 308 ++++------------------------ src/region.rs | 4 +- src/vhal/cpu.rs | 8 +- src/vhal/mod.rs | 6 +- src/vm.rs | 80 -------- src/vm/machine.rs | 437 ++++++++++++++++++++++++++++++++++++++++ src/vm/mod.rs | 148 ++++++++++++++ 9 files changed, 627 insertions(+), 368 deletions(-) delete mode 100644 src/vm.rs create mode 100644 src/vm/machine.rs create mode 100644 src/vm/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 0d41947..0945b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ lazyinit = "0.2" log = "0.4" spin = "0.10" timer_list = "0.1" +thiserror = {version = "2", default-features = false} # System independent crates provided by ArceOS. axerrno = "0.1.0" diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 6c8f3f2..c1254a1 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,5 +1,4 @@ use aarch64_cpu::registers::MPIDR_EL1; -use aarch64_cpu_ext::asm::cache; use aarch64_cpu_ext::cache::{CacheOp, dcache_range}; use axhal::percpu::this_cpu_id; use core::fmt; @@ -42,7 +41,7 @@ impl ArchHal for Hal { fn init() -> anyhow::Result<()> { arm_vcpu::init_hal(&cpu::VCpuHal); - + Ok(()) } diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 8340c47..9901512 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -6,37 +6,38 @@ use alloc::{collections::BTreeMap, string::String, vec::Vec}; use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, HostPhysAddr, HostVirtAddr, TASK_STACK_SIZE, + GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmStatusInitOps, + VmStatusRunningOps, VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, region::GuestRegion, vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, - vm::{Status, VmId, VmOps}, + vm::{Status, VmId}, }; const VM_ASPACE_BASE: usize = 0x0; const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; /// AArch64 Virtual Machine implementation -pub struct ArchVm { +pub struct VmInit { pub id: VmId, pub name: String, pt_levels: usize, - state: StateMachine, stop_requested: AtomicBool, exit_code: AtomicUsize, + run_data: Option, } -impl ArchVm { +impl VmInit { /// Creates a new VM with the given configuration pub fn new(config: &AxVMConfig) -> anyhow::Result { let vm = Self { id: config.id().into(), name: config.name(), pt_levels: 4, - state: StateMachine::Idle, stop_requested: AtomicBool::new(false), exit_code: AtomicUsize::new(0), + run_data: None, }; Ok(vm) } @@ -44,9 +45,6 @@ impl ArchVm { /// Initializes the VM, creating vCPUs and setting up memory pub fn init(&mut self, config: AxVMConfig) -> anyhow::Result<()> { debug!("Initializing VM {} ({})", self.id, self.name); - if !matches!(self.state, StateMachine::Idle) { - return Err(anyhow::anyhow!("VM is not in Idle state")); - }; // Create vCPUs let mut vcpus = Vec::new(); @@ -196,103 +194,15 @@ impl ArchVm { .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; } - self.state = StateMachine::Inited(run_data); + self.run_data = Some(run_data); Ok(()) } - - /// Checks if the VM is active (not stopped) - fn is_active(&self) -> bool { - !self.stop_requested.load(Ordering::SeqCst) - } - - /// Gets the current state of the VM - fn get_state(&self) -> &StateMachine { - &self.state - } - - /// Gets a mutable reference to the current state of the VM - fn get_state_mut(&mut self) -> &mut StateMachine { - &mut self.state - } - - /// Transitions the VM state from current to new state - fn transition_state(&mut self, new_state: StateMachine) -> anyhow::Result<()> { - let current_state = self.get_state(); - - // Validate state transition - match (current_state, &new_state) { - (StateMachine::Idle, StateMachine::Inited(_)) => {} - (StateMachine::Inited(_), StateMachine::Running(_)) => {} - (StateMachine::Running(_), StateMachine::ShuttingDown(_)) => {} - (StateMachine::ShuttingDown(_), StateMachine::PoweredOff) => {} - _ => return Err(anyhow::anyhow!("Invalid state transition")), - } - - self.state = new_state; - Ok(()) - } - - /// Shuts down VM and transitions to PoweredOff state - pub fn shutdown(&mut self) -> anyhow::Result<()> { - // First check if we're in Running state - let is_running = matches!(self.get_state(), StateMachine::Running(_)); - - if is_running { - // Stop VM first - self.stop(); - } - - match self.get_state_mut() { - StateMachine::Running(data) => { - // Transition to ShuttingDown state - // let new_data = RunData { - // // vcpus: BTreeMap::new(), - // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - // devices: BTreeMap::new(), - // }; - // let old_data = core::mem::replace(data, new_data); - // self.transition_state(StateMachine::ShuttingDown(old_data))?; - - // Clean up resources - self.cleanup_resources()?; - - // Transition to PoweredOff state - self.transition_state(StateMachine::PoweredOff)?; - - info!("VM {} ({}) shut down successfully", self.id, self.name); - Ok(()) - } - StateMachine::ShuttingDown(_) => { - // Already shutting down - Ok(()) - } - StateMachine::PoweredOff => { - // Already powered off - Ok(()) - } - _ => Err(anyhow::anyhow!("VM is not in Running state")), - } - } - - /// Clean up VM resources - fn cleanup_resources(&mut self) -> anyhow::Result<()> { - match self.get_state_mut() { - StateMachine::ShuttingDown(data) => { - // Clear vCPUs - // data.vcpus.clear(); - - // Note: We don't destroy the address space here as it might be needed - // for debugging or inspection after shutdown - - Ok(()) - } - _ => Err(anyhow::anyhow!("VM is not in ShuttingDown state")), - } - } } -impl VmOps for ArchVm { +impl VmStatusInitOps for VmInit { + type Running = VmStatusRunning; + fn id(&self) -> VmId { self.id } @@ -301,13 +211,11 @@ impl VmOps for ArchVm { &self.name } - fn boot(&mut self) -> anyhow::Result<()> { - let data = match self.get_state_mut() { - StateMachine::Inited(data) => data, - _ => return Err(anyhow::anyhow!("VM is not in Inited state")), - }; + fn start(self) -> Result { + let mut data = self.run_data.unwrap(); let mut vcpus = vec![]; + vcpus.append(&mut data.vcpus); let mut vcpu_handles = vec![]; let vm_id = self.id; @@ -315,7 +223,7 @@ impl VmOps for ArchVm { let vcpu_id = vcpu.id; let bind_id = vcpu.binded_cpu_id(); let handle = std::thread::Builder::new() - .name(format!("{}-{vcpu_id}", self.id,)) + .name(format!("{vm_id}-{vcpu_id}")) .stack_size(TASK_STACK_SIZE) .spawn(move || { assert!( @@ -333,8 +241,9 @@ impl VmOps for ArchVm { ); } } + vcpu }) - .map_err(|e| anyhow!("{e}"))?; + .unwrap(); vcpu_handles.push(handle); } @@ -346,182 +255,33 @@ impl VmOps for ArchVm { vcpu_handles.len() ); - Ok(()) - } - - fn stop(&self) { - if !self.is_active() { - return; // Already stopped - } - - info!("Stopping VM {} ({})", self.id, self.name); - - // Set stop flag - self.stop_requested.store(true, Ordering::SeqCst); - - // // Unbind all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); - // if let Err(e) = vcpu.unbind() { - // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); - // } - // } - - info!("VM {} ({}) stopped", self.id, self.name); - } - - fn status(&self) -> Status { - match self.get_state() { - StateMachine::Idle => Status::Idle, - StateMachine::Inited(_) => Status::Idle, - StateMachine::Running(_) => Status::Running, - StateMachine::ShuttingDown(_) => Status::ShuttingDown, - StateMachine::PoweredOff => Status::PoweredOff, - } + Ok(VmStatusRunning { data }) } } -impl Drop for ArchVm { - fn drop(&mut self) { - // Ensure VM is properly shut down - if matches!(self.get_state(), StateMachine::Running(_)) { - let _ = self.shutdown(); - } - } +pub struct VmStatusRunning { + data: RunData, } - -impl ArchVm { - /// Gets the exit code of the VM - pub fn exit_code(&self) -> usize { - self.exit_code.load(Ordering::SeqCst) +impl VmStatusRunningOps for VmStatusRunning { + type Stopping = VmStatusStopping; + + fn stop(self) -> Result + where + Self: Sized, + { + Ok(VmStatusStopping {}) } - /// Sets the exit code of the VM - pub fn set_exit_code(&self, code: usize) { - self.exit_code.store(code, Ordering::SeqCst); - } + fn do_work(&mut self) -> Result<(), RunError> { - /// Checks if the VM has been stopped - pub fn is_stopped(&self) -> bool { - self.stop_requested.load(Ordering::SeqCst) - } - - /// Resets the VM to initial state - pub fn reset(&mut self) -> anyhow::Result<()> { - match self.get_state() { - StateMachine::Running(_) | StateMachine::ShuttingDown(_) => { - // Stop the VM first - self.stop(); - - // Transition to PoweredOff state - self.transition_state(StateMachine::PoweredOff)?; - - // Note: In a real implementation, we would need to: - // 1. Reset all vCPUs to initial state - // 2. Reset memory to initial state - // 3. Reset devices to initial state - // 4. Transition back to Idle state - - info!("VM {} ({}) reset", self.id, self.name); - Ok(()) - } - _ => Err(anyhow::anyhow!("VM is not in a state that can be reset")), - } - } - - /// Pauses the VM - pub fn pause(&mut self) -> anyhow::Result<()> { - let data = match self.get_state_mut() { - StateMachine::Running(data) => data, - _ => return Err(anyhow::anyhow!("VM is not in Running state")), - }; - - // // Transition to Inited state - // let new_data = RunData { - // // vcpus: BTreeMap::new(), - // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - // devices: BTreeMap::new(), - // }; - // let old_data = core::mem::replace(data, new_data); - // self.transition_state(StateMachine::Inited(old_data))?; - - // // Unbind all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Unbinding vCPU {} for VM {}", vcpu_id, self.id); - // if let Err(e) = vcpu.unbind() { - // warn!("Failed to unbind vCPU {}: {:?}", vcpu_id, e); - // } - // } - - info!("VM {} ({}) paused", self.id, self.name); - Ok(()) - } - - /// Resumes the VM - pub fn resume(&mut self) -> anyhow::Result<()> { - let data = match self.get_state_mut() { - StateMachine::Inited(data) => data, - _ => return Err(anyhow::anyhow!("VM is not in Inited state")), - }; - - // // Transition to Running state - // let new_data = RunData { - // // vcpus: BTreeMap::new(), - // // address_space: AddrSpace::new_empty(4, GuestPhysAddr::from(0), 0).unwrap(), - // devices: BTreeMap::new(), - // }; - // let old_data = core::mem::replace(data, new_data); - // self.transition_state(StateMachine::Running(old_data))?; - - // // Bind all vCPUs - // let vcpus = self.get_vcpus(); - // for (vcpu_id, vcpu) in vcpus.iter().enumerate() { - // debug!("Binding vCPU {} for VM {}", vcpu_id, self.id); - // if let Err(e) = vcpu.bind() { - // warn!("Failed to bind vCPU {}: {:?}", vcpu_id, e); - // } - // } - - info!("VM {} ({}) resumed", self.id, self.name); - Ok(()) - } - - /// Gets the current state as a string - pub fn state_str(&self) -> &'static str { - match self.get_state() { - StateMachine::Idle => "Idle", - StateMachine::Inited(_) => "Inited", - StateMachine::Running(_) => "Running", - StateMachine::ShuttingDown(_) => "ShuttingDown", - StateMachine::PoweredOff => "PoweredOff", - } - } - - /// Prints VM information - pub fn print_info(&self) { - info!("VM Information:"); - info!(" ID: {}", self.id); - info!(" Name: {}", self.name); - // info!(" State: {}", self.state_str()); - // info!(" vCPUs: {}", self.vcpu_count()); - // info!(" Devices: {}", self.get_devices().len()); - - // if let Some(root) = self.page_table_root() { - // info!(" Page Table Root: {:#x}", root); - // } + // Ok(()) + Err(RunError::Exit) } } -/// VM state machine -enum StateMachine { - Idle, - Inited(RunData), - Running(RunData), - ShuttingDown(RunData), - PoweredOff, -} +pub struct VmStatusStopping {} + +impl VmStatusStoppingOps for VmStatusStopping {} /// Data needed when VM is running pub struct RunData { diff --git a/src/region.rs b/src/region.rs index 623311b..0e7d48b 100644 --- a/src/region.rs +++ b/src/region.rs @@ -1,6 +1,4 @@ -use core::{alloc::Layout, ops::Range}; - -use alloc::vec::Vec; +use core::alloc::Layout; use crate::{ GuestPhysAddr, HostVirtAddr, diff --git a/src/vhal/cpu.rs b/src/vhal/cpu.rs index 26b388e..fb44e90 100644 --- a/src/vhal/cpu.rs +++ b/src/vhal/cpu.rs @@ -38,11 +38,9 @@ impl HCpuExclusive { where F: FnOnce(&HCpu) -> R, { - unsafe { - for (id, cpu) in PRE_CPU.iter() { - if cpu.id == self.0 { - return f(cpu); - } + for (id, cpu) in PRE_CPU.iter() { + if cpu.id == self.0 { + return f(cpu); } } panic!("CPU data not found for CPU ID {}", self.0); diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index 0cf7f21..a5ea345 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -48,8 +48,8 @@ pub fn init() -> anyhow::Result<()> { info!("Enabling hardware virtualization support on core {id}"); timer::init_percpu(); - // let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); - // unsafe { cpu::PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; + let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); + unsafe { cpu::PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; let _ = CORES.fetch_add(1, Ordering::Release); }) .map_err(|e| anyhow!("{e:?}"))?; @@ -66,8 +66,6 @@ pub fn init() -> anyhow::Result<()> { info!("All cores have enabled hardware virtualization support."); - - Ok(()) } diff --git a/src/vm.rs b/src/vm.rs deleted file mode 100644 index 7cde3b8..0000000 --- a/src/vm.rs +++ /dev/null @@ -1,80 +0,0 @@ -use core::fmt::{self, Display}; - -use alloc::string::String; -use spin::Mutex; - -use crate::AxVMConfig; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct VmId(usize); - -impl VmId { - pub fn new(id: usize) -> Self { - VmId(id) - } -} -// Implement Display for VmId -impl fmt::Display for VmId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for VmId { - fn from(value: usize) -> Self { - VmId(value) - } -} - -impl From for usize { - fn from(value: VmId) -> Self { - value.0 - } -} - -pub trait VmOps { - fn id(&self) -> VmId; - fn name(&self) -> &str; - fn boot(&mut self) -> anyhow::Result<()>; - fn stop(&self); - fn status(&self) -> Status; -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Status { - Idle, - Running, - ShuttingDown, - PoweredOff, -} - -pub struct Vm { - id: VmId, - name: String, - inner: Mutex, -} - -impl Vm { - pub fn new(config: AxVMConfig) -> anyhow::Result { - let mut arch_vm = crate::arch::ArchVm::new(&config)?; - arch_vm.init(config)?; - Ok(Vm { - id: arch_vm.id(), - name: arch_vm.name().into(), - inner: Mutex::new(arch_vm), - }) - } - - pub fn id(&self) -> VmId { - self.id - } - - pub fn name(&self) -> &str { - self.name.as_str() - } - - pub fn boot(&self) -> anyhow::Result<()> { - let mut arch_vm = self.inner.lock(); - arch_vm.boot() - } -} diff --git a/src/vm/machine.rs b/src/vm/machine.rs new file mode 100644 index 0000000..d69d21c --- /dev/null +++ b/src/vm/machine.rs @@ -0,0 +1,437 @@ +use alloc::{ + collections::VecDeque, + string::{String, ToString}, + sync::Arc, +}; +use core::{ + marker::PhantomData, + sync::atomic::{AtomicBool, AtomicU8, Ordering}, +}; +use spin::Mutex; +use std::{ + thread::{self, JoinHandle}, + time::Duration, +}; + +use crate::{ + RunError, Status, VmId, VmStatusInitOps, VmStatusRunningOps, + arch::{VmInit, VmStatusRunning, VmStatusStopping}, +}; + +/// Default interval in milliseconds for polling the VM status from +/// the background machine thread. +const STATE_POLL_INTERVAL_MS: u64 = 20; + +/// A lightweight container that stores the identifier and human readable name +/// for a VM instance. Shared between the public [`Vm`] object and the +/// background machine thread for logging and observability. +#[derive(Debug, Clone)] +pub struct VmCommon { + pub id: VmId, + pub name: String, +} + +#[derive(Clone)] +struct CommandResponder { + inner: Arc, +} + +struct CommandResponderInner { + ready: AtomicBool, + worker_alive: Arc, + result: Mutex>>, +} + +impl CommandResponder { + fn new(worker_alive: &Arc) -> Self { + Self { + inner: Arc::new(CommandResponderInner { + ready: AtomicBool::new(false), + worker_alive: worker_alive.clone(), + result: Mutex::new(None), + }), + } + } + + fn complete(&self, result: anyhow::Result<()>) { + *self.inner.result.lock() = Some(result); + self.inner.ready.store(true, Ordering::Release); + } + + fn wait(self) -> anyhow::Result<()> { + loop { + if self.inner.ready.load(Ordering::Acquire) { + return self.inner.result.lock().take().unwrap_or_else(|| Ok(())); + } + if !self.inner.worker_alive.load(Ordering::Acquire) { + return Err(anyhow::anyhow!( + "vm worker stopped before completing command" + )); + } + thread::yield_now(); + } + } +} + +enum MachineCommand { + Start { responder: CommandResponder }, + Shutdown { responder: CommandResponder }, +} + +pub struct CommandMailbox { + queue: Mutex>, +} + +impl CommandMailbox { + pub fn new() -> Self { + Self { + queue: Mutex::new(VecDeque::new()), + } + } + + pub fn push(&self, cmd: MachineCommand) { + self.queue.lock().push_back(cmd); + } + + pub fn pop(&self) -> Option { + self.queue.lock().pop_front() + } +} + +#[derive(Clone)] +pub struct VmHandle { + pub common: VmCommon, + state: Arc, + commands: Arc, + worker_alive: Arc, +} + +impl VmHandle { + fn new(vm: &VmInit) -> Self { + Self { + common: VmCommon { + id: vm.id(), + name: vm.name().to_string(), + }, + state: Arc::new(AtomicState::new(VMStatus::Loaded)), + commands: Arc::new(CommandMailbox::new()), + worker_alive: Arc::new(AtomicBool::new(true)), + } + } + + pub fn status(&self) -> VMStatus { + self.state.load() + } + + pub fn start(&self) -> anyhow::Result<()> { + let responder = CommandResponder::new(&self.worker_alive); + self.send_command(MachineCommand::Start { + responder: responder.clone(), + })?; + responder.wait() + } + + pub fn shutdown(&self) -> anyhow::Result<()> { + let responder = CommandResponder::new(&self.worker_alive); + self.send_command(MachineCommand::Shutdown { + responder: responder.clone(), + })?; + responder.wait() + } + + fn send_command(&self, cmd: MachineCommand) -> anyhow::Result<()> { + if !self.worker_alive.load(Ordering::Acquire) { + return Err(anyhow::anyhow!("vm worker already stopped")); + } + self.commands.push(cmd); + Ok(()) + } +} + +enum VmMachineState { + Init(VmInit), + Running(VmStatusRunning), + Stopping(VmStatusStopping), + Stopped, +} + +impl VmMachineState { + fn do_work(&mut self) -> Result<(), RunError> { + match self { + VmMachineState::Running(running_vm) => running_vm.do_work()?, + _ => { + std::thread::sleep(Duration::from_millis(STATE_POLL_INTERVAL_MS)); + } + } + Ok(()) + } +} + +/// State machine that owns a VM implementation (`V`) and executes commands in +/// a dedicated worker thread. The public side can enqueue commands and read +/// status without blocking the main control thread. +pub struct VmMachine { + handle: VmHandle, + vm: Option, +} + +impl VmMachine { + pub(crate) fn new(vm: VmInit) -> anyhow::Result { + let handle = VmHandle::new(&vm); + Ok(Self { + handle, + vm: Some(VmMachineState::Init(vm)), + }) + } + + pub(crate) fn id(&self) -> VmId { + self.handle.common.id + } + + pub(crate) fn name(&self) -> &str { + self.handle.common.name.as_str() + } + + pub(crate) fn status(&self) -> VMStatus { + self.handle.state.load() + } + + pub fn handle(&self) -> VmHandle { + self.handle.clone() + } + + fn is_active(&self) -> bool { + self.status() < VMStatus::Stopping + } + + pub fn run(&mut self) -> Result<(), RunError> { + let res = self.run_loop(); + self.handle.state.store(VMStatus::Stopped); + res + } + + fn run_loop(&mut self) -> Result<(), RunError> { + while self.is_active() { + self.run_loop_once()?; + } + Ok(()) + } + + fn run_loop_once(&mut self) -> Result<(), RunError> { + if let Some(cmd) = self.handle.commands.pop() { + match cmd { + MachineCommand::Start { responder } => { + let result = match self.vm.take() { + Some(VmMachineState::Init(vm_init)) => match vm_init.start() { + Ok(running_vm) => { + self.vm = Some(VmMachineState::Running(running_vm)); + self.handle.state.store(VMStatus::Running); + Ok(()) + } + Err((e, vm_init)) => { + self.vm = Some(VmMachineState::Init(vm_init)); + Err(e) + } + }, + Some(state) => { + self.vm = Some(state); + Err(anyhow::anyhow!("VM is not in a startable state")) + } + None => panic!("VM state is missing"), + }; + responder.complete(result); + } + MachineCommand::Shutdown { responder } => { + let result = match self.vm.take() { + Some(VmMachineState::Running(running_vm)) => match running_vm.stop() { + Ok(stopping_vm) => { + self.vm = Some(VmMachineState::Stopping(stopping_vm)); + self.handle.state.store(VMStatus::Stopping); + Ok(()) + } + Err((e, running_vm)) => { + self.vm = Some(VmMachineState::Running(running_vm)); + Err(e) + } + }, + Some(state) => { + self.vm = Some(state); + Err(anyhow::anyhow!("VM is not in a stoppable state")) + } + None => panic!("VM state is missing"), + }; + responder.complete(result); + } + } + } else { + if let Some(vm_state) = &mut self.vm { + vm_state.do_work()?; + } + } + + Ok(()) + } + + // pub(crate) fn start(&self) -> anyhow::Result<()> {} + + // fn worker_loop( + // mut vm: V, + // state: Arc, + // commands: Arc, + // worker_alive: Arc, + // ) { + // let mut tracked_state = VMStatus::Loaded; + // state.store(tracked_state); + // let poll_interval = Duration::from_millis(STATE_POLL_INTERVAL_MS); + + // loop { + // if let Some(cmd) = commands.pop() { + // match cmd { + // MachineCommand::Start { responder } => { + // let result = Self::handle_start(&mut vm, &state, &mut tracked_state); + // responder.complete(result); + // Self::sync_state(&vm, &state, &mut tracked_state); + // } + // MachineCommand::Shutdown { responder } => { + // let result = Self::handle_shutdown(&mut vm, &state, &mut tracked_state); + // responder.complete(result); + // Self::sync_state(&vm, &state, &mut tracked_state); + // } + // MachineCommand::Exit => { + // if matches!(tracked_state, VMStatus::Running | VMStatus::Stopping) { + // vm.stop(); + // } + // break; + // } + // } + // } else { + // Self::sync_state(&vm, &state, &mut tracked_state); + // thread::sleep(poll_interval); + // } + // } + + // worker_alive.store(false, Ordering::Release); + // state.store(VMStatus::Stopped); + // } + + // fn handle_start( + // vm: &mut V, + // state: &Arc, + // tracked_state: &mut VMStatus, + // ) -> anyhow::Result<()> { + // match tracked_state { + // VMStatus::Loading => Err(anyhow::anyhow!("VM is still loading")), + // VMStatus::Running => Err(anyhow::anyhow!("VM is already running")), + // VMStatus::Stopping => Err(anyhow::anyhow!("VM is stopping")), + // _ => { + // *tracked_state = VMStatus::Running; + // state.store(*tracked_state); + // if let Err(e) = vm.run() { + // *tracked_state = VMStatus::Stopped; + // state.store(*tracked_state); + // Err(e) + // } else { + // Ok(()) + // } + // } + // } + // } + + // fn handle_shutdown( + // vm: &mut V, + // state: &Arc, + // tracked_state: &mut VMStatus, + // ) -> anyhow::Result<()> { + // match tracked_state { + // VMStatus::Loading => Err(anyhow::anyhow!("VM is still loading")), + // VMStatus::Stopped => Ok(()), + // _ => { + // *tracked_state = VMStatus::Stopping; + // state.store(*tracked_state); + // vm.stop(); + + // loop { + // match vm.status() { + // Status::PoweredOff | Status::Idle => break, + // _ => thread::yield_now(), + // } + // } + + // *tracked_state = VMStatus::Stopped; + // state.store(*tracked_state); + // Ok(()) + // } + // } + // } + + // fn sync_state(vm: &V, state: &Arc, tracked_state: &mut VMStatus) { + // let hardware_state = VMStatus::from(vm.status()); + // if *tracked_state != hardware_state { + // *tracked_state = hardware_state; + // state.store(hardware_state); + // } + // } +} + +/// Auxiliary wrapper that stores the current machine status in an atomically +/// readable form so management threads can query it without synchronisation +/// overhead. +pub(crate) struct AtomicState(AtomicU8); + +impl AtomicState { + pub fn new(state: VMStatus) -> Self { + Self(AtomicU8::new(state as u8)) + } + + pub fn load(&self) -> VMStatus { + VMStatus::from_u8(self.0.load(Ordering::Acquire)) + } + + pub fn store(&self, new_state: VMStatus) { + self.0.store(new_state as u8, Ordering::Release); + } +} + +/// High-level VM lifecycle that is visible to callers of the [`Vm`] API. +/// This is intentionally richer than the low-level `Status` that is returned +/// by the architecture specific implementation so that the shell and +/// management layers can express user-friendly states. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum VMStatus { + Loading = 0, + Loaded = 1, + Running = 2, + Suspended = 3, + Stopping = 4, + Stopped = 5, +} + +impl Default for VMStatus { + fn default() -> Self { + VMStatus::Loading + } +} + +impl VMStatus { + fn from_u8(raw: u8) -> Self { + match raw { + 0 => VMStatus::Loading, + 1 => VMStatus::Loaded, + 2 => VMStatus::Running, + 3 => VMStatus::Suspended, + 4 => VMStatus::Stopping, + _ => VMStatus::Stopped, + } + } +} + +impl From for VMStatus { + fn from(status: Status) -> Self { + match status { + Status::Idle => VMStatus::Loaded, + Status::Running => VMStatus::Running, + Status::ShuttingDown => VMStatus::Stopping, + Status::PoweredOff => VMStatus::Stopped, + } + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs new file mode 100644 index 0000000..3d1e45f --- /dev/null +++ b/src/vm/mod.rs @@ -0,0 +1,148 @@ +use core::fmt; + +use alloc::sync::Arc; +use spin::Mutex; +use std::thread; + +use crate::{AxVMConfig, arch::VmInit}; + +mod machine; +use machine::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VmId(usize); + +impl VmId { + pub fn new_fixed(id: usize) -> Self { + VmId(id) + } + + pub fn new() -> Self { + use core::sync::atomic::{AtomicUsize, Ordering}; + static VM_ID_COUNTER: AtomicUsize = AtomicUsize::new(1); + let id = VM_ID_COUNTER.fetch_add(1, Ordering::Relaxed); + VmId(id) + } +} + +impl Default for VmId { + fn default() -> Self { + VmId::new() + } +} + +// Implement Display for VmId +impl fmt::Display for VmId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl From for VmId { + fn from(value: usize) -> Self { + VmId(value) + } +} + +impl From for usize { + fn from(value: VmId) -> Self { + value.0 + } +} + +pub trait VmStatusInitOps { + type Running: VmStatusRunningOps; + fn id(&self) -> VmId; + fn name(&self) -> &str; + fn start(self) -> Result + where + Self: Sized; +} + +#[derive(thiserror::Error, Debug)] +pub enum RunError { + #[error("VM exited normally")] + Exit, + #[error("VM exited with error: {0}")] + ExitWithError(#[from] anyhow::Error), +} + +pub trait VmStatusRunningOps { + type Stopping: VmStatusStoppingOps; + fn do_work(&mut self) -> Result<(), RunError>; + fn stop(self) -> Result + where + Self: Sized; +} + +pub trait VmStatusStoppingOps {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + Idle, + Running, + ShuttingDown, + PoweredOff, +} + +pub struct Vm { + handle: VmHandle, + res: Arc>>>, +} + +impl Vm { + pub fn new(config: AxVMConfig) -> anyhow::Result { + let mut arch_vm = VmInit::new(&config)?; + arch_vm.init(config)?; + let mut machine = VmMachine::new(arch_vm)?; + let handle = machine.handle(); + let res = Arc::new(Mutex::new(None)); + let res_arc = res.clone(); + + thread::Builder::new() + .name(format!("{}-main", handle.common.id.0)) + .spawn(move || { + let res = machine.run(); + let mut guard = res_arc.lock(); + guard.replace(res); + }) + .map_err(|e| anyhow::anyhow!("Failed to spawn VM thread: {:?}", e))?; + + Ok(Vm { handle, res }) + } + + pub fn id(&self) -> VmId { + self.handle.common.id + } + + pub fn name(&self) -> &str { + &self.handle.common.name + } + + pub fn boot(&self) -> anyhow::Result<()> { + self.handle.start() + } + + pub fn shutdown(&self) -> anyhow::Result<()> { + self.handle.shutdown() + } + + pub fn status(&self) -> VMStatus { + self.handle.status() + } + + pub fn wait(&self) -> Result<(), RunError> { + while !matches!(self.status(), VMStatus::Stopped) { + thread::sleep(std::time::Duration::from_millis(50)); + } + let guard = self.res.lock(); + let res = guard.as_ref().unwrap(); + match res { + Ok(()) => Ok(()), + Err(e) => match e { + RunError::Exit => Ok(()), + RunError::ExitWithError(err) => Err(RunError::ExitWithError(anyhow!("{err}"))), + }, + } + } +} From f484c894bbdba2389ce88775f87dd9b1c1dea428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 27 Nov 2025 15:17:23 +0800 Subject: [PATCH 32/74] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A0=E7=94=A8=E7=9A=84=E8=BD=AE=E8=AF=A2?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm.rs | 31 +++++++----- src/vm/machine.rs | 105 +---------------------------------------- 2 files changed, 20 insertions(+), 116 deletions(-) diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 9901512..ec3eeed 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -2,7 +2,7 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}; use super::AddrSpace; -use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ @@ -25,7 +25,7 @@ pub struct VmInit { pt_levels: usize, stop_requested: AtomicBool, exit_code: AtomicUsize, - run_data: Option, + run_data: Option, } impl VmInit { @@ -90,7 +90,7 @@ impl VmInit { ) .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; - let mut run_data = RunData { + let mut run_data = VmStatusRunning { vcpus, address_space, regions: Vec::new(), @@ -100,6 +100,7 @@ impl VmInit { dtb_data: Vec::new(), ramdisk_data: Vec::new(), bios_data: Vec::new(), + vcpu_running_count: Arc::new(AtomicUsize::new(0)), }; debug!("Mapping memory regions for VM {} ({})", self.id, self.name); @@ -192,6 +193,8 @@ impl VmInit { vcpu.vcpu .set_ept_root(run_data.address_space.page_table_root()) .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; + + run_data.vcpu_running_count.fetch_add(1, Ordering::SeqCst); } self.run_data = Some(run_data); @@ -219,8 +222,10 @@ impl VmStatusInitOps for VmInit { vcpus.append(&mut data.vcpus); let mut vcpu_handles = vec![]; let vm_id = self.id; + for mut vcpu in vcpus.into_iter() { let vcpu_id = vcpu.id; + let vcpu_running_count = data.vcpu_running_count.clone(); let bind_id = vcpu.binded_cpu_id(); let handle = std::thread::Builder::new() .name(format!("{vm_id}-{vcpu_id}")) @@ -241,6 +246,7 @@ impl VmStatusInitOps for VmInit { ); } } + vcpu_running_count.fetch_sub(1, Ordering::SeqCst); vcpu }) .unwrap(); @@ -254,14 +260,10 @@ impl VmStatusInitOps for VmInit { self.name, vcpu_handles.len() ); - - Ok(VmStatusRunning { data }) + Ok(data) } } -pub struct VmStatusRunning { - data: RunData, -} impl VmStatusRunningOps for VmStatusRunning { type Stopping = VmStatusStopping; @@ -273,9 +275,11 @@ impl VmStatusRunningOps for VmStatusRunning { } fn do_work(&mut self) -> Result<(), RunError> { - - // Ok(()) - Err(RunError::Exit) + if self.vcpu_running_count.load(Ordering::SeqCst) == 0 { + Err(RunError::Exit) + } else { + Ok(()) + } } } @@ -284,7 +288,7 @@ pub struct VmStatusStopping {} impl VmStatusStoppingOps for VmStatusStopping {} /// Data needed when VM is running -pub struct RunData { +pub struct VmStatusRunning { vcpus: Vec, address_space: AddrSpace, regions: Vec, @@ -294,9 +298,10 @@ pub struct RunData { dtb_data: Vec, ramdisk_data: Vec, bios_data: Vec, + vcpu_running_count: Arc, } -impl RunData { +impl VmStatusRunning { fn add_memory_region(&mut self, config: &MemoryKind) -> anyhow::Result<()> { let region = GuestRegion::new(config); self.address_space diff --git a/src/vm/machine.rs b/src/vm/machine.rs index d69d21c..aa8c060 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -159,9 +159,7 @@ impl VmMachineState { fn do_work(&mut self) -> Result<(), RunError> { match self { VmMachineState::Running(running_vm) => running_vm.do_work()?, - _ => { - std::thread::sleep(Duration::from_millis(STATE_POLL_INTERVAL_MS)); - } + _ => {} } Ok(()) } @@ -213,6 +211,7 @@ impl VmMachine { fn run_loop(&mut self) -> Result<(), RunError> { while self.is_active() { self.run_loop_once()?; + thread::yield_now(); } Ok(()) } @@ -271,106 +270,6 @@ impl VmMachine { Ok(()) } - - // pub(crate) fn start(&self) -> anyhow::Result<()> {} - - // fn worker_loop( - // mut vm: V, - // state: Arc, - // commands: Arc, - // worker_alive: Arc, - // ) { - // let mut tracked_state = VMStatus::Loaded; - // state.store(tracked_state); - // let poll_interval = Duration::from_millis(STATE_POLL_INTERVAL_MS); - - // loop { - // if let Some(cmd) = commands.pop() { - // match cmd { - // MachineCommand::Start { responder } => { - // let result = Self::handle_start(&mut vm, &state, &mut tracked_state); - // responder.complete(result); - // Self::sync_state(&vm, &state, &mut tracked_state); - // } - // MachineCommand::Shutdown { responder } => { - // let result = Self::handle_shutdown(&mut vm, &state, &mut tracked_state); - // responder.complete(result); - // Self::sync_state(&vm, &state, &mut tracked_state); - // } - // MachineCommand::Exit => { - // if matches!(tracked_state, VMStatus::Running | VMStatus::Stopping) { - // vm.stop(); - // } - // break; - // } - // } - // } else { - // Self::sync_state(&vm, &state, &mut tracked_state); - // thread::sleep(poll_interval); - // } - // } - - // worker_alive.store(false, Ordering::Release); - // state.store(VMStatus::Stopped); - // } - - // fn handle_start( - // vm: &mut V, - // state: &Arc, - // tracked_state: &mut VMStatus, - // ) -> anyhow::Result<()> { - // match tracked_state { - // VMStatus::Loading => Err(anyhow::anyhow!("VM is still loading")), - // VMStatus::Running => Err(anyhow::anyhow!("VM is already running")), - // VMStatus::Stopping => Err(anyhow::anyhow!("VM is stopping")), - // _ => { - // *tracked_state = VMStatus::Running; - // state.store(*tracked_state); - // if let Err(e) = vm.run() { - // *tracked_state = VMStatus::Stopped; - // state.store(*tracked_state); - // Err(e) - // } else { - // Ok(()) - // } - // } - // } - // } - - // fn handle_shutdown( - // vm: &mut V, - // state: &Arc, - // tracked_state: &mut VMStatus, - // ) -> anyhow::Result<()> { - // match tracked_state { - // VMStatus::Loading => Err(anyhow::anyhow!("VM is still loading")), - // VMStatus::Stopped => Ok(()), - // _ => { - // *tracked_state = VMStatus::Stopping; - // state.store(*tracked_state); - // vm.stop(); - - // loop { - // match vm.status() { - // Status::PoweredOff | Status::Idle => break, - // _ => thread::yield_now(), - // } - // } - - // *tracked_state = VMStatus::Stopped; - // state.store(*tracked_state); - // Ok(()) - // } - // } - // } - - // fn sync_state(vm: &V, state: &Arc, tracked_state: &mut VMStatus) { - // let hardware_state = VMStatus::from(vm.status()); - // if *tracked_state != hardware_state { - // *tracked_state = hardware_state; - // state.store(hardware_state); - // } - // } } /// Auxiliary wrapper that stores the current machine status in an atomically From 67e03843b3a4326b1de4800f47b945584e26edae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 27 Nov 2025 17:42:49 +0800 Subject: [PATCH 33/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20VCpuHandle?= =?UTF-8?q?=20=E7=BB=93=E6=9E=84=EF=BC=8C=E4=BC=98=E5=8C=96=20vCPU=20?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=BF=80=E6=B4=BB=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 36 +++++++++++++++++++++++++++++++++++- src/arch/aarch64/vm.rs | 17 ++++++++++------- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 41f6224..c4d4ed0 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -1,4 +1,5 @@ -use core::fmt::Display; +use core::{fmt::Display, sync::atomic::AtomicBool}; +use std::sync::Arc; use aarch64_cpu::registers::*; use alloc::sync::Weak; @@ -78,10 +79,33 @@ impl arm_vcpu::CpuHal for VCpuHal { } } +#[derive(Clone)] +pub struct VCpuHandle { + is_active: Arc, +} + +impl VCpuHandle { + pub fn new() -> Self { + VCpuHandle { + is_active: Arc::new(AtomicBool::new(true)), + } + } + + pub fn stop(&self) { + self.is_active + .store(false, core::sync::atomic::Ordering::Release); + } + + pub fn is_active(&self) -> bool { + self.is_active.load(core::sync::atomic::Ordering::Acquire) + } +} + pub struct VCpu { pub id: CpuHardId, pub vcpu: arm_vcpu::Aarch64VCpu, hcpu: HCpuExclusive, + handle: VCpuHandle, } impl VCpu { @@ -100,9 +124,14 @@ impl VCpu { id: hard_id, vcpu, hcpu: hcpu_exclusive, + handle: VCpuHandle::new(), }) } + pub fn handle(&self) -> VCpuHandle { + self.handle.clone() + } + pub fn with_hcpu(&self, f: F) -> R where F: FnOnce(&HCpu) -> R, @@ -117,6 +146,11 @@ impl VCpu { pub fn run(&mut self) -> anyhow::Result<()> { info!("Starting vCPU {}", self.id); + while self.handle.is_active() { + let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; + debug!("vCPU {} exited with reason: {:?}", self.id, exit_reason); + } + Ok(()) } } diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index ec3eeed..e0b9f19 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -6,13 +6,7 @@ use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmStatusInitOps, - VmStatusRunningOps, VmStatusStoppingOps, - arch::cpu::VCpu, - config::{AxVMConfig, MemoryKind}, - region::GuestRegion, - vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, - vm::{Status, VmId}, + GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmStatusInitOps, VmStatusRunningOps, VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, fdt::fdt, region::GuestRegion, vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, vm::{Status, VmId} }; const VM_ASPACE_BASE: usize = 0x0; @@ -411,6 +405,15 @@ impl VmStatusRunning { ); self.dtb_addr = gpa; self.load_image_data(gpa, &dtb_cfg.data)?; + } else { + debug!( + "No dtb provided, generating new dtb for {} ({})", + config.id(), + config.name() + ); + let fdt = fdt().unwrap(); + let dtb_bytes = fdt.as_slice(); + } Ok(()) From e57b57791aef6df19dbbdc5eea9327c7ec401a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 28 Nov 2025 14:12:25 +0800 Subject: [PATCH 34/74] fmt code --- src/arch/aarch64/vm.rs | 10 ++++++++-- src/lib.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index e0b9f19..533d0b9 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -6,7 +6,14 @@ use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmStatusInitOps, VmStatusRunningOps, VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, fdt::fdt, region::GuestRegion, vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, vm::{Status, VmId} + GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmStatusInitOps, + VmStatusRunningOps, VmStatusStoppingOps, + arch::cpu::VCpu, + config::{AxVMConfig, MemoryKind}, + fdt::fdt, + region::GuestRegion, + vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, + vm::{Status, VmId}, }; const VM_ASPACE_BASE: usize = 0x0; @@ -413,7 +420,6 @@ impl VmStatusRunning { ); let fdt = fdt().unwrap(); let dtb_bytes = fdt.as_slice(); - } Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 9845964..7154cd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,9 +25,9 @@ const TASK_STACK_SIZE: usize = 0x40000; // 256 KB pub(crate) mod arch; mod fdt; +mod region; mod vcpu; mod vm; -mod region; pub mod config; pub mod vhal; From 84b54ff5f1d900f6266d3010cba0b56f0f6f8173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 1 Dec 2025 15:22:18 +0800 Subject: [PATCH 35/74] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E5=86=85=E5=AD=98=E7=AE=A1=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=86=97=E4=BD=99=E4=BB=A3=E7=A0=81=E5=B9=B6?= =?UTF-8?q?=E5=BC=95=E5=85=A5=20VmData=20=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm.rs | 330 ++++++++++++++--------------------------- src/config.rs | 2 +- src/lib.rs | 1 - src/region.rs | 74 --------- src/vm/data.rs | 235 +++++++++++++++++++++++++++++ src/vm/mod.rs | 2 + 6 files changed, 349 insertions(+), 295 deletions(-) delete mode 100644 src/region.rs create mode 100644 src/vm/data.rs diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 533d0b9..fe1b909 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -6,12 +6,11 @@ use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmStatusInitOps, + GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, fdt::fdt, - region::GuestRegion, vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, vm::{Status, VmId}, }; @@ -83,20 +82,9 @@ impl VmInit { self.id, self.name, vcpu_count, self.pt_levels ); - // Create address space for the VM - let address_space = AddrSpace::new_empty( - self.pt_levels, - axaddrspace::GuestPhysAddr::from(VM_ASPACE_BASE), - VM_ASPACE_SIZE, - ) - .map_err(|e| anyhow::anyhow!("Failed to create address space: {:?}", e))?; - let mut run_data = VmStatusRunning { vcpus, - address_space, - regions: Vec::new(), - devices: BTreeMap::new(), - kernel_entry: GuestPhysAddr::from_usize(0), + data: VmData::new(self.pt_levels)?, dtb_addr: GuestPhysAddr::from_usize(0), dtb_data: Vec::new(), ramdisk_data: Vec::new(), @@ -106,77 +94,27 @@ impl VmInit { debug!("Mapping memory regions for VM {} ({})", self.id, self.name); for memory_cfg in &config.memory_regions { - run_data.add_memory_region(memory_cfg)?; + use crate::vm::MappingFlags; + let m = run_data.data.new_memory( + memory_cfg, + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::USER, + ); + run_data.data.add_memory(m); } - debug!( - "Mapped {} memory regions for VM {} ({})", - run_data.regions.len(), - self.id, - self.name - ); + run_data.data.load_kernel_image(&config)?; - run_data.load_images(&config)?; - - // Add emulated devices - // for emu_device in config.emu_devices() { - // let device_info = DeviceInfo { - // device_type: DeviceType::Emulated, - // gpa: GuestPhysAddr::from(emu_device.base_gpa), - // hpa: None, - // size: emu_device.length, - // config: DeviceConfig::Mmio { - // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // }, - // }; - - // devices.insert(emu_device.name.clone(), device_info); - - // // Map device memory - // self.map_region( - // GuestPhysAddr::from(emu_device.base_gpa), - // HostPhysAddr::from(emu_device.base_gpa), // Use identity mapping for emulated devices - // emu_device.length, - // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // ) - // .map_err(|e| { - // anyhow::anyhow!("Failed to map emulated device {}: {:?}", emu_device.name, e) - // })?; - // } - - // // Add passthrough devices - // for pt_device in config.pass_through_devices() { - // let device_info = DeviceInfo { - // device_type: DeviceType::Passthrough, - // gpa: GuestPhysAddr::from(pt_device.base_gpa), - // hpa: Some(HostPhysAddr::from(pt_device.base_hpa)), - // size: pt_device.length, - // config: DeviceConfig::Mmio { - // flags: MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // }, - // }; - - // devices.insert(pt_device.name.clone(), device_info); - - // // Map device memory - // self.map_region( - // GuestPhysAddr::from(pt_device.base_gpa), - // HostPhysAddr::from(pt_device.base_hpa), - // pt_device.length, - // MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, - // ) - // .map_err(|e| { - // anyhow::anyhow!( - // "Failed to map passthrough device {}: {:?}", - // pt_device.name, - // e - // ) - // })?; - // } + run_data.make_dtb(&config)?; + + let kernel_entry = run_data.data.kernel_entry(); + let gpt_root = run_data.data.gpt_root(); // Setup vCPUs for vcpu in &mut run_data.vcpus { - vcpu.vcpu.set_entry(run_data.kernel_entry).unwrap(); + vcpu.vcpu.set_entry(kernel_entry).unwrap(); vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); let setup_config = Aarch64VCpuSetupConfig { @@ -192,7 +130,7 @@ impl VmInit { // Set EPT root vcpu.vcpu - .set_ept_root(run_data.address_space.page_table_root()) + .set_ept_root(gpt_root) .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; run_data.vcpu_running_count.fetch_add(1, Ordering::SeqCst); @@ -291,10 +229,7 @@ impl VmStatusStoppingOps for VmStatusStopping {} /// Data needed when VM is running pub struct VmStatusRunning { vcpus: Vec, - address_space: AddrSpace, - regions: Vec, - devices: BTreeMap, - kernel_entry: GuestPhysAddr, + data: VmData, dtb_addr: GuestPhysAddr, dtb_data: Vec, ramdisk_data: Vec, @@ -303,147 +238,104 @@ pub struct VmStatusRunning { } impl VmStatusRunning { - fn add_memory_region(&mut self, config: &MemoryKind) -> anyhow::Result<()> { - let region = GuestRegion::new(config); - self.address_space - .map_linear( - region.gpa.as_usize().into(), - region.hva.as_usize().into(), - region.size, - axaddrspace::MappingFlags::READ - | axaddrspace::MappingFlags::WRITE - | axaddrspace::MappingFlags::EXECUTE - | axaddrspace::MappingFlags::USER, - ) - .map_err(|e| anyhow::anyhow!("Failed to map memory region: {:?}", e))?; - - self.regions.push(region); + // fn load_images(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { + // // Load other images (BIOS, DTB, Ramdisk) similarly... + // debug!( + // "Loading kernel image for VM {} ({})", + // config.id(), + // config.name() + // ); + // let _main_region_idx = self.load_kernel_image(config)?; - Ok(()) - } + // Ok(()) + // } - fn load_images(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { - // Load other images (BIOS, DTB, Ramdisk) similarly... - debug!( - "Loading kernel image for VM {} ({})", - config.id(), - config.name() - ); - let _main_region_idx = self.load_kernel_image(config)?; - self.load_dtb_image(config)?; + fn make_dtb(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { + // self.load_dtb_image(config)?; Ok(()) } - /// Returns the loaded kernel region's index - fn load_kernel_image(&mut self, config: &AxVMConfig) -> anyhow::Result { - let mut idx = 0; - let image_cfg = config.image_config(); - let gpa = if let Some(gpa) = image_cfg.kernel.gpa { - let mut found = false; - for (i, region) in self.regions.iter().enumerate() { - if (region.gpa..region.gpa + region.size).contains(&gpa) { - idx = i; - found = true; - break; - } - } - if !found { - return Err(anyhow!( - "Kernel load GPA {:#x} not within any memory region", - gpa.as_usize() - )); - } - gpa - } else { - let mut gpa = None; - for (i, region) in self.regions.iter().enumerate() { - if region.size >= image_cfg.kernel.data.len() { - gpa = Some(region.gpa + 2 * 1024 * 1024); - idx = i; - break; - } else { - continue; - } - } - gpa.ok_or(anyhow!("No suitable memory region found for kernel image"))? - }; - - debug!( - "Loading kernel image into GPA @{:#x} for VM {} ({})", - gpa.as_usize(), - config.id(), - config.name() - ); - self.load_image_data(gpa, &image_cfg.kernel.data)?; - self.kernel_entry = gpa; - - Ok(idx) - } - - fn load_dtb_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { - let image_cfg = config.image_config(); - - if let Some(dtb_cfg) = &image_cfg.dtb { - let size = dtb_cfg.data.len(); - self.dtb_data = Vec::with_capacity(size / 4); - - let gpa = if let Some(gpa) = dtb_cfg.gpa { - gpa - } else { - (self.dtb_data.as_mut_ptr() as usize).into() - }; - self.address_space - .map_linear( - gpa.as_usize().into(), - virt_to_phys(HostVirtAddr::from(self.dtb_data.as_mut_ptr() as usize)) - .as_usize() - .into(), - size, - axaddrspace::MappingFlags::READ | axaddrspace::MappingFlags::USER, - ) - .map_err(|e| anyhow::anyhow!("Failed to map DTB region: {:?}", e))?; - - debug!( - "Loading DTB image into GPA @{:#x} for VM {} ({})", - gpa.as_usize(), - config.id(), - config.name() - ); - self.dtb_addr = gpa; - self.load_image_data(gpa, &dtb_cfg.data)?; - } else { - debug!( - "No dtb provided, generating new dtb for {} ({})", - config.id(), - config.name() - ); - let fdt = fdt().unwrap(); - let dtb_bytes = fdt.as_slice(); - } - - Ok(()) - } - - fn load_image_data(&mut self, gpa: GuestPhysAddr, data: &[u8]) -> anyhow::Result<()> { - let hva = self - .address_space - .translated_byte_buffer(gpa.as_usize().into(), data.len()) - .ok_or(anyhow!("Fail to load [{gpa:?}, {:?})", gpa + data.len()))?; - let mut remain = data; - - for buff in hva { - let copy_size = core::cmp::min(remain.len(), buff.len()); - buff[..copy_size].copy_from_slice(&remain[..copy_size]); - crate::arch::Hal::cache_flush(HostVirtAddr::from(buff.as_ptr() as usize), copy_size); - remain = &remain[copy_size..]; - if remain.is_empty() { - break; - } - } - - Ok(()) - } + // fn load_dtb_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { + // let image_cfg = config.image_config(); + + // if let Some(dtb_cfg) = &image_cfg.dtb { + // let size = dtb_cfg.data.len(); + // self.dtb_data = Vec::with_capacity(size / 4); + + // let gpa = if let Some(gpa) = dtb_cfg.gpa { + // gpa + // } else { + // (self.dtb_data.as_mut_ptr() as usize).into() + // }; + // self.address_space + // .map_linear( + // gpa.as_usize().into(), + // virt_to_phys(HostVirtAddr::from(self.dtb_data.as_mut_ptr() as usize)) + // .as_usize() + // .into(), + // size, + // axaddrspace::MappingFlags::READ | axaddrspace::MappingFlags::USER, + // ) + // .map_err(|e| anyhow::anyhow!("Failed to map DTB region: {:?}", e))?; + + // debug!( + // "Loading DTB image into GPA @{:#x} for VM {} ({})", + // gpa.as_usize(), + // config.id(), + // config.name() + // ); + // self.dtb_addr = gpa; + // self.load_image_data(gpa, &dtb_cfg.data)?; + // } else { + // debug!( + // "No dtb provided, generating new dtb for {} ({})", + // config.id(), + // config.name() + // ); + // let fdt = fdt().unwrap(); + // let dtb_bytes = fdt.as_slice(); + // let data = dtb_bytes + // .chunks_exact(4) + // .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) + // .collect::>(); + // let size = dtb_bytes.len(); + // self.dtb_data = data; + // let gpa = self.dtb_data.as_mut_ptr() as usize; + // self.address_space + // .map_linear( + // gpa.into(), + // virt_to_phys(HostVirtAddr::from(self.dtb_data.as_mut_ptr() as usize)) + // .as_usize() + // .into(), + // size, + // axaddrspace::MappingFlags::READ | axaddrspace::MappingFlags::USER, + // ) + // .map_err(|e| anyhow::anyhow!("Failed to map DTB region: {e:?}"))?; + // } + + // Ok(()) + // } + + // fn load_image_data(&mut self, gpa: GuestPhysAddr, data: &[u8]) -> anyhow::Result<()> { + // let hva = self + // .address_space + // .translated_byte_buffer(gpa.as_usize().into(), data.len()) + // .ok_or(anyhow!("Fail to load [{gpa:?}, {:?})", gpa + data.len()))?; + // let mut remain = data; + + // for buff in hva { + // let copy_size = core::cmp::min(remain.len(), buff.len()); + // buff[..copy_size].copy_from_slice(&remain[..copy_size]); + // crate::arch::Hal::cache_flush(HostVirtAddr::from(buff.as_ptr() as usize), copy_size); + // remain = &remain[copy_size..]; + // if remain.is_empty() { + // break; + // } + // } + + // Ok(()) + // } } /// Information about a device in the VM diff --git a/src/config.rs b/src/config.rs index 5ffcf9b..aeebb26 100644 --- a/src/config.rs +++ b/src/config.rs @@ -58,7 +58,7 @@ pub enum MemoryKind { /// Use memory regions mapped from host physical address Passthrough { hpa: HostPhysAddr, size: usize }, /// Use fixed memory regions - Fixed { gpa: GuestPhysAddr, size: usize }, + Vmem { gpa: GuestPhysAddr, size: usize }, } /// A part of `AxVMCrateConfig`, which represents a `VM`. diff --git a/src/lib.rs b/src/lib.rs index 7154cd5..ad72f13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,6 @@ const TASK_STACK_SIZE: usize = 0x40000; // 256 KB pub(crate) mod arch; mod fdt; -mod region; mod vcpu; mod vm; diff --git a/src/region.rs b/src/region.rs deleted file mode 100644 index 0e7d48b..0000000 --- a/src/region.rs +++ /dev/null @@ -1,74 +0,0 @@ -use core::alloc::Layout; - -use crate::{ - GuestPhysAddr, HostVirtAddr, - config::MemoryKind, - vhal::{phys_to_virt, virt_to_phys}, -}; - -const ALIGN: usize = 1024 * 1024 * 2; - -#[derive(Debug, Clone)] -pub struct GuestRegion { - pub gpa: GuestPhysAddr, - pub hva: HostVirtAddr, - pub size: usize, - pub own: bool, -} - -impl GuestRegion { - pub fn new(kind: &MemoryKind) -> Self { - match kind { - MemoryKind::Identical { size } => { - let hva = HostVirtAddr::from(unsafe { - alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) - } as usize); - let gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); - GuestRegion { - gpa, - hva, - size: *size, - own: true, - } - } - MemoryKind::Passthrough { hpa, size } => { - let hva = phys_to_virt(*hpa); - let gpa = GuestPhysAddr::from_usize(hva.as_usize()); - GuestRegion { - gpa, - hva, - size: *size, - own: false, - } - } - MemoryKind::Fixed { gpa, size } => { - let hva = HostVirtAddr::from(unsafe { - alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) - } as usize); - GuestRegion { - gpa: *gpa, - hva, - size: *size, - own: true, - } - } - } - } - - pub fn buffer_mut(&self) -> &mut [u8] { - unsafe { core::slice::from_raw_parts_mut(self.hva.as_mut_ptr(), self.size) } - } -} - -impl Drop for GuestRegion { - fn drop(&mut self) { - if self.own { - unsafe { - alloc::alloc::dealloc( - self.hva.as_mut_ptr(), - alloc::alloc::Layout::from_size_align(self.size, ALIGN).unwrap(), - ); - } - } - } -} diff --git a/src/vm/data.rs b/src/vm/data.rs new file mode 100644 index 0000000..48f5549 --- /dev/null +++ b/src/vm/data.rs @@ -0,0 +1,235 @@ +use core::alloc::Layout; +use std::{ + sync::{Arc, Mutex}, + vec::Vec, +}; + +pub use axaddrspace::MappingFlags; + +use crate::vhal::ArchHal; +use crate::{ + AxVMConfig, GuestPhysAddr, HostPhysAddr, HostVirtAddr, + config::MemoryKind, + vhal::{phys_to_virt, virt_to_phys}, +}; + +const VM_ASPACE_BASE: usize = 0x0; +const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; +const ALIGN: usize = 1024 * 1024 * 2; + +type AddrSpace = axaddrspace::AddrSpace; + +#[derive(Clone)] +pub struct VmData { + shared: Arc>, + addrspace: Arc>, +} + +impl VmData { + pub fn new(gpt_levels: usize) -> anyhow::Result { + // Create address space for the VM + let address_space = AddrSpace::new_empty( + gpt_levels, + axaddrspace::GuestPhysAddr::from(VM_ASPACE_BASE), + VM_ASPACE_SIZE, + ) + .map_err(|e| anyhow!("Failed to create address space: {e:?}"))?; + Ok(Self { + addrspace: Arc::new(Mutex::new(address_space)), + shared: Arc::new(Mutex::new(SharedData::default())), + }) + } + + pub fn add_memory(&self, m: GuestMemory) { + let mut s = self.shared.lock(); + s.memories.push(m); + } + + pub fn add_reserved_memory(&self, r: GuestMemory) { + self.shared.lock().reserved_memories.push(r); + } + + pub fn new_memory(&self, kind: &MemoryKind, flags: MappingFlags) -> GuestMemory { + let _gpa; + let _size; + let mut hva = HostVirtAddr::from(0); + + match kind { + MemoryKind::Identical { size } => { + hva = HostVirtAddr::from(unsafe { + alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) + } as usize); + _gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); + _size = *size; + let mut g = self.addrspace.lock(); + g.map_linear(_gpa.as_usize().into(), hva.as_usize().into(), _size, flags) + .unwrap(); + } + MemoryKind::Passthrough { hpa, size } => { + hva = phys_to_virt(*hpa); + _gpa = GuestPhysAddr::from_usize(hva.as_usize()); + _size = *size; + let mut g = self.addrspace.lock(); + g.map_linear(_gpa.as_usize().into(), hva.as_usize().into(), _size, flags) + .unwrap(); + } + MemoryKind::Vmem { gpa, size } => { + _gpa = *gpa; + _size = *size; + let mut g = self.addrspace.lock(); + g.map_alloc(_gpa.as_usize().into(), _size, flags, true) + .unwrap(); + } + } + + GuestMemory { + gpa: _gpa, + hva, + size: _size, + kind: kind.clone(), + owner: self.clone(), + } + } + + pub fn load_kernel_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { + let mut idx = 0; + let image_cfg = config.image_config(); + let mut s = self.shared.lock(); + let gpa = if let Some(gpa) = image_cfg.kernel.gpa { + let mut found = false; + for (i, region) in s.memories.iter().enumerate() { + if (region.gpa..region.gpa + region.size).contains(&gpa) { + idx = i; + found = true; + break; + } + } + if !found { + return Err(anyhow!( + "Kernel load GPA {:#x} not within any memory region", + gpa.as_usize() + )); + } + gpa + } else { + let mut gpa = None; + for (i, region) in s.memories.iter().enumerate() { + if region.size >= image_cfg.kernel.data.len() { + gpa = Some(region.gpa + 2 * 1024 * 1024); + idx = i; + break; + } else { + continue; + } + } + gpa.ok_or(anyhow!("No suitable memory region found for kernel image"))? + }; + + debug!( + "Loading kernel image into GPA @{:#x} for VM {} ({})", + gpa.as_usize(), + config.id(), + config.name() + ); + let offset = gpa.as_usize() - s.memories[idx].gpa().as_usize(); + s.memories[idx].copy_from_slice(offset, &image_cfg.kernel.data); + s.kernel_region_index = idx; + s.kernel_entry = gpa; + Ok(()) + } + + pub fn gpt_root(&self) -> HostPhysAddr { + let g = self.addrspace.lock(); + g.page_table_root().as_usize().into() + } + + pub fn kernel_entry(&self) -> GuestPhysAddr { + let s = self.shared.lock(); + s.kernel_entry + } +} + +#[derive(Default)] +struct SharedData { + memories: Vec, + reserved_memories: Vec, + kernel_region_index: usize, + kernel_entry: GuestPhysAddr, +} + +pub struct GuestMemory { + gpa: GuestPhysAddr, + hva: HostVirtAddr, + size: usize, + kind: MemoryKind, + owner: VmData, +} + +impl GuestMemory { + pub fn copy_from_slice(&self, offset: usize, data: &[u8]) { + assert!(data.len() <= self.size - offset); + let mut g = self.owner.addrspace.lock(); + let hva = g + .translated_byte_buffer(self.gpa.as_usize().into(), self.size) + .expect("Failed to translate kernel image load address"); + let mut remain = data; + let mut skip = offset; + + for buff in hva { + if skip >= buff.len() { + skip -= buff.len(); + continue; + } + let buff = &mut buff[skip..]; + skip = 0; + + let copy_size = core::cmp::min(remain.len(), buff.len()); + buff[..copy_size].copy_from_slice(&remain[..copy_size]); + crate::arch::Hal::cache_flush(HostVirtAddr::from(buff.as_ptr() as usize), copy_size); + remain = &remain[copy_size..]; + if remain.is_empty() { + break; + } + } + } + + pub fn gpa(&self) -> GuestPhysAddr { + self.gpa + } + + pub fn size(&self) -> usize { + self.size + } + + pub fn to_vec(&self) -> Vec { + let mut result = vec![]; + let mut g = self.owner.addrspace.lock(); + let hva = g + .translated_byte_buffer(self.gpa.as_usize().into(), self.size) + .expect("Failed to translate memory region"); + for buff in hva { + result.extend_from_slice(buff); + } + result.resize(self.size, 0); + result + } +} + +impl Drop for GuestMemory { + fn drop(&mut self) { + let mut g = self.owner.addrspace.lock(); + match &self.kind { + MemoryKind::Identical { .. } => { + unsafe { + alloc::alloc::dealloc( + HostVirtAddr::from(self.hva.as_usize()).as_mut_ptr(), + Layout::from_size_align(self.size, ALIGN).unwrap(), + ) + }; + } + _ => { + g.unmap(self.gpa.as_usize().into(), self.size).unwrap(); + } + } + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 3d1e45f..10adcaf 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -6,8 +6,10 @@ use std::thread; use crate::{AxVMConfig, arch::VmInit}; +mod data; mod machine; use machine::*; +pub(crate) use data::*; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct VmId(usize); From cd4c8e2333c514fa31106458d9ad030b38e50217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 1 Dec 2025 16:02:43 +0800 Subject: [PATCH 36/74] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20vCPU=20?= =?UTF-8?q?=E9=80=80=E5=87=BA=E5=8E=9F=E5=9B=A0=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=B7=BB=E5=8A=A0=20MMIO=20=E5=92=8C?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=AF=84=E5=AD=98=E5=99=A8=E8=AF=BB=E5=86=99?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 30 +++++++++++++++++++++ src/arch/aarch64/vm.rs | 59 ++++++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index c4d4ed0..d393cf0 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -149,6 +149,36 @@ impl VCpu { while self.handle.is_active() { let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; debug!("vCPU {} exited with reason: {:?}", self.id, exit_reason); + match exit_reason { + arm_vcpu::AxVCpuExitReason::Hypercall { nr, args } => todo!(), + arm_vcpu::AxVCpuExitReason::MmioRead { + addr, + width, + reg, + reg_width, + signed_ext, + } => todo!(), + arm_vcpu::AxVCpuExitReason::MmioWrite { addr, width, data } => todo!(), + arm_vcpu::AxVCpuExitReason::SysRegRead { addr, reg } => todo!(), + arm_vcpu::AxVCpuExitReason::SysRegWrite { addr, value } => todo!(), + arm_vcpu::AxVCpuExitReason::ExternalInterrupt => todo!(), + arm_vcpu::AxVCpuExitReason::CpuUp { + target_cpu, + entry_point, + arg, + } => todo!(), + arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), + arm_vcpu::AxVCpuExitReason::SystemDown => todo!(), + arm_vcpu::AxVCpuExitReason::Nothing => todo!(), + arm_vcpu::AxVCpuExitReason::SendIPI { + target_cpu, + target_cpu_aux, + send_to_all, + send_to_self, + vector, + } => todo!(), + _ => todo!(), + } } Ok(()) diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index fe1b909..4d017df 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -1,5 +1,8 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}; +use std::os::arceos::{ + api::{config, task::AxCpuMask}, + modules::axtask::set_current_affinity, +}; use super::AddrSpace; use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; @@ -12,7 +15,7 @@ use crate::{ config::{AxVMConfig, MemoryKind}, fdt::fdt, vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, - vm::{Status, VmId}, + vm::{MappingFlags, Status, VmId}, }; const VM_ASPACE_BASE: usize = 0x0; @@ -86,9 +89,6 @@ impl VmInit { vcpus, data: VmData::new(self.pt_levels)?, dtb_addr: GuestPhysAddr::from_usize(0), - dtb_data: Vec::new(), - ramdisk_data: Vec::new(), - bios_data: Vec::new(), vcpu_running_count: Arc::new(AtomicUsize::new(0)), }; @@ -231,9 +231,6 @@ pub struct VmStatusRunning { vcpus: Vec, data: VmData, dtb_addr: GuestPhysAddr, - dtb_data: Vec, - ramdisk_data: Vec, - bios_data: Vec, vcpu_running_count: Arc, } @@ -251,7 +248,51 @@ impl VmStatusRunning { // } fn make_dtb(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { - // self.load_dtb_image(config)?; + let flags = + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::WRITE | MappingFlags::USER; + + if let Some(dtb_cfg) = &config.image_config().dtb { + debug!( + "Loading DTB image into GPA @{:#x} for VM {} ({})", + dtb_cfg.gpa.unwrap_or(0.into()).as_usize(), + config.id(), + config.name() + ); + let kind = if let Some(gpa) = dtb_cfg.gpa { + MemoryKind::Vmem { + gpa: gpa.into(), + size: dtb_cfg.data.len(), + } + } else { + MemoryKind::Identical { + size: dtb_cfg.data.len(), + } + }; + + let mut guest_mem = self.data.new_memory(&kind, flags); + + self.dtb_addr = guest_mem.gpa(); + + guest_mem.copy_from_slice(0, &dtb_cfg.data); + self.data.add_reserved_memory(guest_mem); + } else { + debug!( + "No dtb provided, generating new dtb for {} ({})", + config.id(), + config.name() + ); + let fdt = fdt().unwrap(); + let dtb_bytes = fdt.as_slice(); + let mut guest_mem = self.data.new_memory( + &MemoryKind::Identical { + size: dtb_bytes.len(), + }, + flags, + ); + self.dtb_addr = guest_mem.gpa(); + guest_mem.copy_from_slice(0, dtb_bytes); + self.data.add_reserved_memory(guest_mem); + } Ok(()) } From f6a5e4d310186dbc176d682ec3f02af444bccb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 1 Dec 2025 17:00:34 +0800 Subject: [PATCH 37/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20FDT=20?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8=EF=BC=8C=E4=BC=98=E5=8C=96=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E5=86=85=E5=AD=98=E7=AE=A1=E7=90=86=E5=92=8C?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E8=8A=82=E7=82=B9=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/arch/aarch64/cpu.rs | 11 +- src/arch/aarch64/mod.rs | 14 +- src/arch/aarch64/vm.rs | 110 +----- src/fdt/gen.rs | 733 ++++++++++++++++++++++++++++++++++++++++ src/fdt/mod.rs | 4 + src/vhal/mod.rs | 14 +- src/vm/data.rs | 19 +- src/vm/machine.rs | 10 +- 9 files changed, 779 insertions(+), 137 deletions(-) create mode 100644 src/fdt/gen.rs diff --git a/Cargo.toml b/Cargo.toml index 0945b0e..9d0feee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ page_table_multiarch = "0.5" percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true bitmap-allocator = "0.2.1" +vm-fdt.workspace = true # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index d393cf0..4061dce 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -2,17 +2,12 @@ use core::{fmt::Display, sync::atomic::AtomicBool}; use std::sync::Arc; use aarch64_cpu::registers::*; -use alloc::sync::Weak; use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; -use axhal::percpu::this_cpu_id; use axvm_types::addr::*; -use crate::{ - TASK_STACK_SIZE, VmId, - vhal::{ - ArchCpuData, - cpu::{CpuHardId, CpuId, HCpuExclusive}, - }, +use crate::vhal::{ + ArchCpuData, + cpu::{CpuHardId, CpuId, HCpuExclusive}, }; pub struct HCpu { diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index c1254a1..e6767d7 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,24 +1,14 @@ use aarch64_cpu::registers::MPIDR_EL1; use aarch64_cpu_ext::cache::{CacheOp, dcache_range}; -use axhal::percpu::this_cpu_id; -use core::fmt; -use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use memory_addr::VirtAddr; -use crate::alloc::{collections::BTreeMap, string::String, vec::Vec}; -use crate::arch::cpu::VCpu; +use crate::alloc::vec::Vec; use crate::fdt; use crate::vhal::{ ArchHal, cpu::{CpuHardId, CpuId}, }; -use aarch64_cpu::registers::{ReadWriteable, Readable, Writeable}; -use axaddrspace::{AxMmHal, MappingFlags}; -use axerrno::{AxResult, ax_err}; -use page_table_multiarch::PagingHandler; - -use crate::{config::AxVMConfig, vm::*}; +use aarch64_cpu::registers::Readable; pub mod cpu; mod vm; diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 4d017df..f5087c1 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -1,21 +1,15 @@ +use alloc::{string::String, sync::Arc, vec::Vec}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::os::arceos::{ - api::{config, task::AxCpuMask}, - modules::axtask::set_current_affinity, -}; +use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}; -use super::AddrSpace; -use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, HostPhysAddr, HostVirtAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, - VmStatusRunningOps, VmStatusStoppingOps, + GuestPhysAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, + VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, - fdt::fdt, - vhal::{ArchHal, cpu::CpuId, phys_to_virt, virt_to_phys}, - vm::{MappingFlags, Status, VmId}, + vm::{MappingFlags, VmId}, }; const VM_ASPACE_BASE: usize = 0x0; @@ -56,7 +50,7 @@ impl VmInit { match config.cpu_num { crate::config::CpuNumType::Alloc(num) => { - for i in 0..num { + for _ in 0..num { let vcpu = VCpu::new(None, dtb_addr)?; debug!("Created vCPU with {:?}", vcpu.id); vcpus.push(vcpu); @@ -281,8 +275,13 @@ impl VmStatusRunning { config.id(), config.name() ); - let fdt = fdt().unwrap(); - let dtb_bytes = fdt.as_slice(); + let fdt = crate::fdt::FdtGen { + cpu_hard_ids: self.vcpus.iter().map(|vcpu| vcpu.id).collect(), + memories: self.data.memories(), + }; + + let dtb_bytes = fdt.generate(&config)?; + let mut guest_mem = self.data.new_memory( &MemoryKind::Identical { size: dtb_bytes.len(), @@ -290,93 +289,12 @@ impl VmStatusRunning { flags, ); self.dtb_addr = guest_mem.gpa(); - guest_mem.copy_from_slice(0, dtb_bytes); + guest_mem.copy_from_slice(0, &dtb_bytes); self.data.add_reserved_memory(guest_mem); } Ok(()) } - - // fn load_dtb_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { - // let image_cfg = config.image_config(); - - // if let Some(dtb_cfg) = &image_cfg.dtb { - // let size = dtb_cfg.data.len(); - // self.dtb_data = Vec::with_capacity(size / 4); - - // let gpa = if let Some(gpa) = dtb_cfg.gpa { - // gpa - // } else { - // (self.dtb_data.as_mut_ptr() as usize).into() - // }; - // self.address_space - // .map_linear( - // gpa.as_usize().into(), - // virt_to_phys(HostVirtAddr::from(self.dtb_data.as_mut_ptr() as usize)) - // .as_usize() - // .into(), - // size, - // axaddrspace::MappingFlags::READ | axaddrspace::MappingFlags::USER, - // ) - // .map_err(|e| anyhow::anyhow!("Failed to map DTB region: {:?}", e))?; - - // debug!( - // "Loading DTB image into GPA @{:#x} for VM {} ({})", - // gpa.as_usize(), - // config.id(), - // config.name() - // ); - // self.dtb_addr = gpa; - // self.load_image_data(gpa, &dtb_cfg.data)?; - // } else { - // debug!( - // "No dtb provided, generating new dtb for {} ({})", - // config.id(), - // config.name() - // ); - // let fdt = fdt().unwrap(); - // let dtb_bytes = fdt.as_slice(); - // let data = dtb_bytes - // .chunks_exact(4) - // .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) - // .collect::>(); - // let size = dtb_bytes.len(); - // self.dtb_data = data; - // let gpa = self.dtb_data.as_mut_ptr() as usize; - // self.address_space - // .map_linear( - // gpa.into(), - // virt_to_phys(HostVirtAddr::from(self.dtb_data.as_mut_ptr() as usize)) - // .as_usize() - // .into(), - // size, - // axaddrspace::MappingFlags::READ | axaddrspace::MappingFlags::USER, - // ) - // .map_err(|e| anyhow::anyhow!("Failed to map DTB region: {e:?}"))?; - // } - - // Ok(()) - // } - - // fn load_image_data(&mut self, gpa: GuestPhysAddr, data: &[u8]) -> anyhow::Result<()> { - // let hva = self - // .address_space - // .translated_byte_buffer(gpa.as_usize().into(), data.len()) - // .ok_or(anyhow!("Fail to load [{gpa:?}, {:?})", gpa + data.len()))?; - // let mut remain = data; - - // for buff in hva { - // let copy_size = core::cmp::min(remain.len(), buff.len()); - // buff[..copy_size].copy_from_slice(&remain[..copy_size]); - // crate::arch::Hal::cache_flush(HostVirtAddr::from(buff.as_ptr() as usize), copy_size); - // remain = &remain[copy_size..]; - // if remain.is_empty() { - // break; - // } - // } - - // Ok(()) - // } } /// Information about a device in the VM diff --git a/src/fdt/gen.rs b/src/fdt/gen.rs new file mode 100644 index 0000000..89302c0 --- /dev/null +++ b/src/fdt/gen.rs @@ -0,0 +1,733 @@ +use std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + string::{String, ToString}, + vec::Vec, +}; + +use fdt_parser::{Fdt, Node}; +use vm_fdt::{FdtWriter, FdtWriterNode}; + +use crate::{AxVMConfig, GuestPhysAddr, vhal::cpu::CpuHardId}; + +pub struct FdtGen { + pub cpu_hard_ids: Vec, + pub memories: Vec<(GuestPhysAddr, usize)>, // (start, size) +} + +impl FdtGen { + pub fn generate(&self, vm_cfg: &AxVMConfig) -> anyhow::Result> { + let mut fdt_writer = FdtWriter::new().unwrap(); + // Track the level of the previously processed node for level change handling + let mut previous_node_level = 0; + // Maintain a stack of FDT nodes to correctly start and end nodes + let mut node_stack: Vec = Vec::new(); + let fdt = super::fdt().ok_or_else(|| anyhow!("No FDT found"))?; + + let passthrough_device_names = find_all_passthrough_devices(vm_cfg, &fdt); + + let all_nodes = fdt.all_nodes(); + + for (index, node) in all_nodes.iter().enumerate() { + let node_path = build_node_path(&all_nodes, index); + let node_action = determine_node_action(node, &node_path, &passthrough_device_names); + + match node_action { + NodeAction::RootNode => { + node_stack.push(fdt_writer.begin_node("").unwrap()); + } + NodeAction::CpuNode => { + let need = need_cpu_node(&self.cpu_hard_ids, node, &node_path); + if need { + handle_node_level_change( + &mut fdt_writer, + &mut node_stack, + node.level(), + previous_node_level, + ); + node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); + } else { + continue; + } + } + NodeAction::Skip => { + continue; + } + _ => { + trace!( + "Found exact passthrough device node: {}, path: {}", + node.name(), + node_path + ); + handle_node_level_change( + &mut fdt_writer, + &mut node_stack, + node.level(), + previous_node_level, + ); + node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); + } + } + + previous_node_level = node.level(); + + // Copy all properties of the node + for prop in node.properties() { + fdt_writer.property(prop.name, prop.raw_value()).unwrap(); + } + } + + // End all unclosed nodes + while let Some(node) = node_stack.pop() { + previous_node_level -= 1; + fdt_writer.end_node(node).unwrap(); + } + assert_eq!(previous_node_level, 0); + + let out = fdt_writer.finish().unwrap(); + + Ok(out) + } +} + +/// Determine if CPU node is needed +fn need_cpu_node(phys_cpu_ids: &[CpuHardId], node: &Node, node_path: &str) -> bool { + let mut should_include_node = false; + + if !node_path.starts_with("/cpus/cpu@") { + should_include_node = true; + } else if let Ok(mut cpu_reg) = node.reg() + && let Some(reg_entry) = cpu_reg.first() + { + let cpu_address = reg_entry.address as usize; + debug!( + "Checking CPU node {} with address 0x{:x}", + node.name(), + cpu_address + ); + // Check if this CPU address is in the configured phys_cpu_ids + if phys_cpu_ids.contains(&CpuHardId::new(cpu_address)) { + should_include_node = true; + debug!( + "CPU node {} with address 0x{:x} is in phys_cpu_ids, including in guest FDT", + node.name(), + cpu_address + ); + } else { + debug!( + "CPU node {} with address 0x{:x} is NOT in phys_cpu_ids, skipping", + node.name(), + cpu_address + ); + } + } + should_include_node +} + +/// Build the full path of a node based on node level relationships +/// Build the path by traversing all nodes and constructing paths based on level relationships to avoid path conflicts for nodes with the same name +pub fn build_node_path(all_nodes: &[Node], target_index: usize) -> String { + let mut path_stack: Vec = Vec::new(); + + for node in all_nodes.iter().take(target_index + 1) { + let level = node.level(); + + if level == 1 { + path_stack.clear(); + if node.name() != "/" { + path_stack.push(node.name().to_string()); + } + } else { + while path_stack.len() >= level - 1 { + path_stack.pop(); + } + path_stack.push(node.name().to_string()); + } + } + + // Build the full path of the current node + if path_stack.is_empty() || (path_stack.len() == 1 && path_stack[0] == "/") { + "/".to_string() + } else { + "/".to_string() + &path_stack.join("/") + } +} + +/// Determine node processing action +fn determine_node_action( + node: &Node, + node_path: &str, + passthrough_device_names: &[String], +) -> NodeAction { + if node.name() == "/" { + // Special handling for root node + NodeAction::RootNode + } else if node.name().starts_with("memory") { + // Skip memory nodes, will add them later + NodeAction::Skip + } else if node_path.starts_with("/cpus") { + NodeAction::CpuNode + } else if passthrough_device_names.contains(&node_path.to_string()) { + // Fully matched passthrough device node + NodeAction::IncludeAsPassthroughDevice + } + // Check if the node is a descendant of a passthrough device (by path inclusion and level validation) + else if is_descendant_of_passthrough_device(node_path, node.level(), passthrough_device_names) + { + NodeAction::IncludeAsChildNode + } + // Check if the node is an ancestor of a passthrough device (by path inclusion and level validation) + else if is_ancestor_of_passthrough_device(node_path, passthrough_device_names) { + NodeAction::IncludeAsAncestorNode + } else { + NodeAction::Skip + } +} + +/// Node processing action enumeration +enum NodeAction { + /// Skip node, not included in guest FDT + Skip, + /// Root node + RootNode, + /// CPU node + CpuNode, + /// Include node as passthrough device node + IncludeAsPassthroughDevice, + /// Include node as child node of passthrough device + IncludeAsChildNode, + /// Include node as ancestor node of passthrough device + IncludeAsAncestorNode, +} + +/// Handle node level changes to ensure correct FDT structure +fn handle_node_level_change( + fdt_writer: &mut FdtWriter, + node_stack: &mut Vec, + current_level: usize, + previous_level: usize, +) { + if current_level <= previous_level { + for _ in current_level..=previous_level { + if let Some(end_node) = node_stack.pop() { + fdt_writer.end_node(end_node).unwrap(); + } + } + } +} + +/// Determine if node is a descendant of passthrough device +/// When node path contains a path from passthrough_device_names and is longer than it, it is its descendant node +/// Also use node_level as validation condition +fn is_descendant_of_passthrough_device( + node_path: &str, + node_level: usize, + passthrough_device_names: &[String], +) -> bool { + for passthrough_path in passthrough_device_names { + // Check if the current node is a descendant of a passthrough device + if node_path.starts_with(passthrough_path) && node_path.len() > passthrough_path.len() { + // Ensure it is a true descendant path (separated by /) + if passthrough_path == "/" || node_path.chars().nth(passthrough_path.len()) == Some('/') + { + // Use level relationship for validation: the level of a descendant node should be higher than its parent + // Note: The level of the root node is 1, its direct child node level is 2, and so on + let expected_parent_level = passthrough_path.matches('/').count(); + let current_node_level = node_level; + + // If passthrough_path is the root node "/", then its child node level should be 2 + // Otherwise, the child node level should be higher than the parent node level + if (passthrough_path == "/" && current_node_level >= 2) + || (passthrough_path != "/" && current_node_level > expected_parent_level) + { + return true; + } + } + } + } + false +} + +/// Determine if node is an ancestor of passthrough device +fn is_ancestor_of_passthrough_device(node_path: &str, passthrough_device_names: &[String]) -> bool { + for passthrough_path in passthrough_device_names { + // Check if the current node is an ancestor of a passthrough device + if passthrough_path.starts_with(node_path) && passthrough_path.len() > node_path.len() { + // Ensure it is a true ancestor path (separated by /) + let next_char = passthrough_path.chars().nth(node_path.len()).unwrap_or(' '); + if next_char == '/' || node_path == "/" { + return true; + } + } + } + false +} + +/// Return the collection of all passthrough devices in the configuration file and newly added devices found +pub fn find_all_passthrough_devices(vm_cfg: &AxVMConfig, fdt: &Fdt) -> Vec { + let initial_device_count = vm_cfg.pass_through_devices().len(); + + // Pre-build node cache, store all nodes by path to improve lookup performance + let node_cache: BTreeMap> = build_optimized_node_cache(fdt); + + // Get the list of configured device names + let initial_device_names: Vec = vm_cfg + .pass_through_devices() + .iter() + .map(|dev| dev.name.clone()) + .collect(); + + // Phase 1: Discover descendant nodes of all passthrough devices in the configuration file + // Build a set of configured devices, using BTreeSet to improve lookup efficiency + let mut configured_device_names: BTreeSet = + initial_device_names.iter().cloned().collect(); + + // Used to store newly discovered related device names + let mut additional_device_names = Vec::new(); + + // Phase 1: Process initial devices and their descendant nodes + // Note: Directly use device paths instead of device names + for device_name in &initial_device_names { + // Get all descendant node paths for this device + let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_name); + trace!( + "Found {} descendant paths for {}", + descendant_paths.len(), + device_name + ); + + for descendant_path in descendant_paths { + if !configured_device_names.contains(&descendant_path) { + trace!("Found descendant device: {descendant_path}"); + configured_device_names.insert(descendant_path.clone()); + + additional_device_names.push(descendant_path.clone()); + } else { + trace!("Device already exists: {descendant_path}"); + } + } + } + + info!( + "Phase 1 completed: Found {} new descendant device names", + additional_device_names.len() + ); + + // Phase 2: Discover dependency nodes for all existing devices (including descendant devices) + let mut dependency_device_names = Vec::new(); + // Use a work queue of device names, including initial devices and descendant device names + let mut devices_to_process: Vec = configured_device_names.iter().cloned().collect(); + let mut processed_devices: BTreeSet = BTreeSet::new(); + + // Build phandle mapping table + let phandle_map = build_phandle_map(fdt); + + // Use work queue to recursively find all dependent devices + while let Some(device_node_path) = devices_to_process.pop() { + // Avoid processing the same device repeatedly + if processed_devices.contains(&device_node_path) { + continue; + } + processed_devices.insert(device_node_path.clone()); + + trace!("Analyzing dependencies for device: {device_node_path}"); + + // Find direct dependencies of the current device + let dependencies = find_device_dependencies(&device_node_path, &phandle_map, &node_cache); + trace!( + "Found {} dependencies: {:?}", + dependencies.len(), + dependencies + ); + for dep_node_name in dependencies { + // Check if dependency is already in configuration + if !configured_device_names.contains(&dep_node_name) { + trace!("Found new dependency device: {dep_node_name}"); + dependency_device_names.push(dep_node_name.clone()); + + // Add dependency device name to work queue to further find its dependencies + devices_to_process.push(dep_node_name.clone()); + configured_device_names.insert(dep_node_name.clone()); + } + } + } + + info!( + "Phase 2 completed: Found {} new dependency device names", + dependency_device_names.len() + ); + + // Phase 3: Find all excluded devices and remove them from the list + // Convert Vec> to Vec + let excluded_device_path: Vec = vm_cfg + .excluded_devices() + .iter() + .flatten() + .cloned() + .collect(); + let mut all_excludes_devices = excluded_device_path.clone(); + let mut process_excludeds: BTreeSet = excluded_device_path.iter().cloned().collect(); + + for device_path in &excluded_device_path { + // Get all descendant node paths for this device + let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_path); + info!( + "Found {} descendant paths for {}", + descendant_paths.len(), + device_path + ); + + for descendant_path in descendant_paths { + if !process_excludeds.contains(&descendant_path) { + trace!("Found descendant device: {descendant_path}"); + process_excludeds.insert(descendant_path.clone()); + + all_excludes_devices.push(descendant_path.clone()); + } else { + trace!("Device already exists: {descendant_path}"); + } + } + } + info!("Found excluded devices: {all_excludes_devices:?}"); + + // Merge all device name lists + let mut all_device_names = initial_device_names.clone(); + all_device_names.extend(additional_device_names); + all_device_names.extend(dependency_device_names); + + // Remove excluded devices from the final list + if !all_excludes_devices.is_empty() { + info!( + "Removing {} excluded devices from the list", + all_excludes_devices.len() + ); + let excluded_set: BTreeSet = all_excludes_devices.into_iter().collect(); + + // Filter out excluded devices + all_device_names.retain(|device_name| { + let should_keep = !excluded_set.contains(device_name); + if !should_keep { + info!("Excluding device: {device_name}"); + } + should_keep + }); + } + + // Phase 4: remove root node from the list + all_device_names.retain(|device_name| device_name != "/"); + + let final_device_count = all_device_names.len(); + info!( + "Passthrough devices analysis completed. Total devices: {} (added: {})", + final_device_count, + final_device_count - initial_device_count + ); + + // Print final device list + for (i, device_name) in all_device_names.iter().enumerate() { + trace!("Final passthrough device[{i}]: {device_name}"); + } + + all_device_names +} + +/// Build a simplified node cache table, traverse all nodes once and group by full path +/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name +pub fn build_optimized_node_cache<'a>(fdt: &'a Fdt) -> BTreeMap> { + let mut node_cache: BTreeMap> = BTreeMap::new(); + + let all_nodes = fdt.all_nodes(); + + for (index, node) in all_nodes.iter().enumerate() { + let node_path = build_node_path(&all_nodes, index); + if let Some(existing_nodes) = node_cache.get(&node_path) + && !existing_nodes.is_empty() + { + error!( + "Duplicate node path found: {} for node '{}' at level {}, existing node: '{}'", + node_path, + node.name(), + node.level(), + existing_nodes[0].name() + ); + } + + trace!( + "Adding node to cache: {} (level: {}, index: {})", + node_path, node.level(), index + ); + node_cache.entry(node_path).or_default().push(node.clone()); + } + + debug!( + "Built simplified node cache with {} unique device paths", + node_cache.len() + ); + node_cache +} + +/// Build a mapping table from phandle to node information, optimized version using fdt-parser convenience methods +/// Use full path instead of node name +/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name +fn build_phandle_map(fdt: &Fdt) -> BTreeMap)> { + let mut phandle_map = BTreeMap::new(); + + let all_nodes = fdt.all_nodes(); + + for (index, node) in all_nodes.iter().enumerate() { + let node_path = build_node_path(&all_nodes, index); + + // Collect node properties + let mut phandle = None; + let mut cells_map = BTreeMap::new(); + for prop in node.properties() { + match prop.name { + "phandle" | "linux,phandle" => { + phandle = Some(prop.u32().unwrap()); + } + "#address-cells" + | "#size-cells" + | "#clock-cells" + | "#reset-cells" + | "#gpio-cells" + | "#interrupt-cells" + | "#power-domain-cells" + | "#thermal-sensor-cells" + | "#phy-cells" + | "#dma-cells" + | "#sound-dai-cells" + | "#mbox-cells" + | "#pwm-cells" + | "#iommu-cells" => { + cells_map.insert(prop.name.to_string(), prop.u32().unwrap()); + } + _ => {} + } + } + + // If phandle is found, store it together with the node's full path + if let Some(ph) = phandle { + phandle_map.insert(ph, (node_path, cells_map)); + } + } + phandle_map +} + +/// Parse properties containing phandle references intelligently based on #*-cells properties +/// Supports multiple formats: +/// - Single phandle: +/// - phandle+specifier: +/// - Multiple phandle references: +fn parse_phandle_property_with_cells( + prop_data: &[u8], + prop_name: &str, + phandle_map: &BTreeMap)>, +) -> Vec<(u32, Vec)> { + let mut results = Vec::new(); + + debug!( + "Parsing property '{}' with cells info, data length: {} bytes", + prop_name, + prop_data.len() + ); + + if prop_data.is_empty() || prop_data.len() % 4 != 0 { + warn!( + "Property '{}' data length ({} bytes) is invalid", + prop_name, + prop_data.len() + ); + return results; + } + + let u32_values: Vec = prop_data + .chunks(4) + .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) + .collect(); + + let mut i = 0; + while i < u32_values.len() { + let potential_phandle = u32_values[i]; + + // Check if it's a valid phandle + if let Some((device_name, cells_info)) = phandle_map.get(&potential_phandle) { + // Determine the number of cells required based on property name + let cells_count = get_cells_count_for_property(prop_name, cells_info); + trace!( + "Property '{prop_name}' requires {cells_count} cells for device '{device_name}'" + ); + + // Check if there's enough data + if i + cells_count < u32_values.len() { + let specifiers: Vec = u32_values[i + 1..=i + cells_count].to_vec(); + debug!( + "Parsed phandle reference: phandle={potential_phandle:#x}, specifiers={specifiers:?}" + ); + results.push((potential_phandle, specifiers)); + i += cells_count + 1; // Skip phandle and all specifiers + } else { + warn!( + "Property:{} not enough data for phandle {:#x}, expected {} cells but only {} values remaining", + prop_name, + potential_phandle, + cells_count, + u32_values.len() - i - 1 + ); + break; + } + } else { + // If not a valid phandle, skip this value + i += 1; + } + } + + results +} + +/// Determine the required number of cells based on property name and target node's cells information +fn get_cells_count_for_property(prop_name: &str, cells_info: &BTreeMap) -> usize { + let cells_property = match prop_name { + "clocks" | "assigned-clocks" => "#clock-cells", + "resets" => "#reset-cells", + "power-domains" => "#power-domain-cells", + "phys" => "#phy-cells", + "interrupts" | "interrupts-extended" => "#interrupt-cells", + "gpios" => "#gpio-cells", + _ if prop_name.ends_with("-gpios") || prop_name.ends_with("-gpio") => "#gpio-cells", + "dmas" => "#dma-cells", + "thermal-sensors" => "#thermal-sensor-cells", + "sound-dai" => "#sound-dai-cells", + "mboxes" => "#mbox-cells", + "pwms" => "#pwm-cells", + _ => { + debug!("Unknown property '{prop_name}', defaulting to 0 cell"); + return 0; + } + }; + + cells_info.get(cells_property).copied().unwrap_or(0) as usize +} + +/// Generic phandle property parsing function +/// Parse phandle references according to cells information with correct block size +/// Support single phandle and multiple phandle+specifier formats +/// Return full path instead of node name +fn parse_phandle_property( + prop_data: &[u8], + prop_name: &str, + phandle_map: &BTreeMap)>, +) -> Vec { + let mut dependencies = Vec::new(); + + let phandle_refs = parse_phandle_property_with_cells(prop_data, prop_name, phandle_map); + + for (phandle, specifiers) in phandle_refs { + if let Some((device_path, _cells_info)) = phandle_map.get(&phandle) { + let spec_info = if !specifiers.is_empty() { + format!(" (specifiers: {specifiers:?})") + } else { + String::new() + }; + debug!( + "Found {prop_name} dependency: phandle={phandle:#x}, device={device_path}{spec_info}" + ); + dependencies.push(device_path.clone()); + } + } + + dependencies +} + +/// Device property classifier - used to identify properties that require special handling +struct DevicePropertyClassifier; + +impl DevicePropertyClassifier { + /// Phandle properties that require special handling - includes all properties that need dependency resolution + const PHANDLE_PROPERTIES: &'static [&'static str] = &[ + "clocks", + "power-domains", + "phys", + "resets", + "dmas", + "thermal-sensors", + "mboxes", + "assigned-clocks", + "interrupt-parent", + "phy-handle", + "msi-parent", + "memory-region", + "syscon", + "regmap", + "iommus", + "interconnects", + "nvmem-cells", + "sound-dai", + "pinctrl-0", + "pinctrl-1", + "pinctrl-2", + "pinctrl-3", + "pinctrl-4", + ]; + + /// Determine if it's a phandle property that requires handling + fn is_phandle_property(prop_name: &str) -> bool { + Self::PHANDLE_PROPERTIES.contains(&prop_name) + || prop_name.ends_with("-supply") + || prop_name == "gpios" + || prop_name.ends_with("-gpios") + || prop_name.ends_with("-gpio") + || (prop_name.contains("cells") && !prop_name.starts_with("#") && prop_name.len() >= 4) + } +} + +/// Find device dependencies +fn find_device_dependencies( + device_node_path: &str, + phandle_map: &BTreeMap)>, + node_cache: &BTreeMap>, // Add node_cache parameter +) -> Vec { + let mut dependencies = Vec::new(); + + // Directly find nodes from node_cache, avoiding traversing all nodes + if let Some(nodes) = node_cache.get(device_node_path) { + // Traverse all properties of nodes to find dependencies + for node in nodes { + for prop in node.properties() { + // Determine if it's a phandle property that needs to be processed + if DevicePropertyClassifier::is_phandle_property(prop.name) { + let mut prop_deps = + parse_phandle_property(prop.raw_value(), prop.name, phandle_map); + dependencies.append(&mut prop_deps); + } + } + } + } + + dependencies +} + +/// Get all descendant nodes based on parent node path (including child nodes, grandchild nodes, etc.) +/// Find all descendant nodes by looking up nodes with parent node path as prefix in node_cache +fn get_descendant_nodes_by_path<'a>( + node_cache: &'a BTreeMap>, + parent_path: &str, +) -> Vec { + let mut descendant_paths = Vec::new(); + + // Special handling if parent path is root path + let search_prefix = if parent_path == "/" { + "/".to_string() + } else { + parent_path.to_string() + "/" + }; + + // Traverse node_cache, find all nodes with parent path as prefix + for path in node_cache.keys() { + // Check if path has parent path as prefix (and is not the parent path itself) + if path.starts_with(&search_prefix) && path.len() > search_prefix.len() { + // This is a descendant node path, add to results + descendant_paths.push(path.clone()); + } + } + + descendant_paths +} diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index 875aefd..8d4bd21 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -1,6 +1,10 @@ use alloc::vec::Vec; use fdt_parser::{Fdt, Status}; +mod r#gen; + +pub use r#gen::FdtGen; + pub(crate) fn fdt() -> Option { let addr = axhal::get_bootarg(); if addr == 0 { diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index a5ea345..f6f1bd8 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -1,22 +1,16 @@ -use alloc::{collections::BTreeMap, vec::Vec}; +use alloc::vec::Vec; use axstd::{ os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, thread::yield_now, }; -use bitmap_allocator::{BitAlloc, BitAlloc4K}; -use core::{ - fmt::Display, - sync::atomic::{AtomicUsize, Ordering}, -}; +use bitmap_allocator::BitAlloc; +use core::sync::atomic::{AtomicUsize, Ordering}; use spin::Mutex; use crate::{ HostPhysAddr, HostVirtAddr, TASK_STACK_SIZE, arch::{HCpu, Hal}, - vhal::{ - cpu::{CpuHardId, CpuId}, - precpu::PreCpuSet, - }, + vhal::cpu::{CpuHardId, CpuId}, }; pub(crate) mod cpu; diff --git a/src/vm/data.rs b/src/vm/data.rs index 48f5549..fd55074 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -147,6 +147,19 @@ impl VmData { let s = self.shared.lock(); s.kernel_entry } + + pub fn memories(&self) -> Vec<(GuestPhysAddr, usize)> { + let s = self.shared.lock(); + s.memories.iter().map(|m| (m.gpa(), m.size())).collect() + } + + pub fn reserved_memories(&self) -> Vec<(GuestPhysAddr, usize)> { + let s = self.shared.lock(); + s.reserved_memories + .iter() + .map(|m| (m.gpa(), m.size())) + .collect() + } } #[derive(Default)] @@ -166,9 +179,9 @@ pub struct GuestMemory { } impl GuestMemory { - pub fn copy_from_slice(&self, offset: usize, data: &[u8]) { + pub fn copy_from_slice(&mut self, offset: usize, data: &[u8]) { assert!(data.len() <= self.size - offset); - let mut g = self.owner.addrspace.lock(); + let g = self.owner.addrspace.lock(); let hva = g .translated_byte_buffer(self.gpa.as_usize().into(), self.size) .expect("Failed to translate kernel image load address"); @@ -203,7 +216,7 @@ impl GuestMemory { pub fn to_vec(&self) -> Vec { let mut result = vec![]; - let mut g = self.owner.addrspace.lock(); + let g = self.owner.addrspace.lock(); let hva = g .translated_byte_buffer(self.gpa.as_usize().into(), self.size) .expect("Failed to translate memory region"); diff --git a/src/vm/machine.rs b/src/vm/machine.rs index aa8c060..ff9ac0d 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -3,15 +3,9 @@ use alloc::{ string::{String, ToString}, sync::Arc, }; -use core::{ - marker::PhantomData, - sync::atomic::{AtomicBool, AtomicU8, Ordering}, -}; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use spin::Mutex; -use std::{ - thread::{self, JoinHandle}, - time::Duration, -}; +use std::thread::{self}; use crate::{ RunError, Status, VmId, VmStatusInitOps, VmStatusRunningOps, From ce088b145811df39ddf2d844489c116e78757cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 3 Dec 2025 14:29:41 +0800 Subject: [PATCH 38/74] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20FDT=20?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=86=85=E5=AD=98=E6=98=A0=E5=B0=84=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E8=8A=82=E7=82=B9=E7=9A=84=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 9 +- src/arch/aarch64/vm.rs | 48 ++++++-- src/fdt/gen.rs | 273 +++++++++++++++++++++++++++++++---------- src/fdt/mod.rs | 18 +-- src/vm/data.rs | 24 +++- src/vm/mod.rs | 2 +- 6 files changed, 282 insertions(+), 92 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9d0feee..b5fafec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,12 @@ fdt-parser = "0.5" lazyinit = "0.2" log = "0.4" spin = "0.10" -timer_list = "0.1" thiserror = {version = "2", default-features = false} +timer_list = "0.1" # System independent crates provided by ArceOS. axerrno = "0.1.0" +bitmap-allocator = "0.2.1" cpumask = "0.1.0" kspin = "0.1" memory_addr = "0.4" @@ -29,7 +30,6 @@ page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true -bitmap-allocator = "0.2.1" vm-fdt.workspace = true # System dependent modules provided by ArceOS-Hypervisor. @@ -37,11 +37,12 @@ axaddrspace = "0.2" # axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} # axdevice_base = "0.1" # axvcpu = "0.1" -axvmconfig = {version = "0.1", default-features = false} -axruntime.workspace = true axhal.workspace = true +axruntime.workspace = true axstd.workspace = true axvm-types.workspace = true +axvmconfig = {version = "0.1", default-features = false} +fdt-edit = {git = "https://github.com/drivercraft/fdt-parser.git"} [target.'cfg(target_arch = "x86_64")'.dependencies] # x86_vcpu = "0.1" diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index f5087c1..53c7b7a 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -3,6 +3,7 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}; use arm_vcpu::Aarch64VCpuSetupConfig; +use fdt_edit::{Node, Property, RawProperty}; use crate::{ GuestPhysAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, @@ -275,21 +276,44 @@ impl VmStatusRunning { config.id(), config.name() ); - let fdt = crate::fdt::FdtGen { - cpu_hard_ids: self.vcpus.iter().map(|vcpu| vcpu.id).collect(), - memories: self.data.memories(), - }; + let mut fdt = crate::fdt::fdt_edit().expect("Need fdt"); + let nodes = fdt + .find_all_by_path("/memory") + .into_iter() + .map(|o| o.1) + .collect::>(); + for path in nodes { + let _ = fdt.remove_node(&path); + } + let root_address_cells = fdt.root().address_cells().unwrap_or(2); + let root_size_cells = fdt.root().size_cells().unwrap_or(2); + + for (i, m) in self.data.memories().iter().enumerate() { + let mut node = Node::new(format!("memory@{i}")); + node.add_property(Property::Raw(RawProperty::from_string( + "device_type", + "memory", + ))); + node.add_property(Property::Reg(vec![fdt_edit::RegInfo { + address: m.0.as_usize() as u64, + size: Some(m.1 as _), + }])); + fdt.root_mut().add_child(node); + } - let dtb_bytes = fdt.generate(&config)?; + let dtb_data = fdt.to_bytes(); + + let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); + debug!("Generated DTB:\n{f}"); + + let kind = MemoryKind::Identical { + size: dtb_data.len(), + }; + let mut guest_mem = self.data.new_memory(&kind, flags); - let mut guest_mem = self.data.new_memory( - &MemoryKind::Identical { - size: dtb_bytes.len(), - }, - flags, - ); self.dtb_addr = guest_mem.gpa(); - guest_mem.copy_from_slice(0, &dtb_bytes); + + guest_mem.copy_from_slice(0, &dtb_data); self.data.add_reserved_memory(guest_mem); } diff --git a/src/fdt/gen.rs b/src/fdt/gen.rs index 89302c0..3d122fb 100644 --- a/src/fdt/gen.rs +++ b/src/fdt/gen.rs @@ -1,91 +1,222 @@ use std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + collections::{btree_map::BTreeMap, btree_map::Entry, btree_set::BTreeSet}, string::{String, ToString}, vec::Vec, }; +use anyhow::Result; use fdt_parser::{Fdt, Node}; use vm_fdt::{FdtWriter, FdtWriterNode}; -use crate::{AxVMConfig, GuestPhysAddr, vhal::cpu::CpuHardId}; +use crate::{AxVMConfig, GuestPhysAddr, fdt::fdt, vhal::cpu::CpuHardId}; -pub struct FdtGen { +pub struct FdtBuilder { pub cpu_hard_ids: Vec, pub memories: Vec<(GuestPhysAddr, usize)>, // (start, size) } -impl FdtGen { - pub fn generate(&self, vm_cfg: &AxVMConfig) -> anyhow::Result> { - let mut fdt_writer = FdtWriter::new().unwrap(); - // Track the level of the previously processed node for level change handling - let mut previous_node_level = 0; - // Maintain a stack of FDT nodes to correctly start and end nodes - let mut node_stack: Vec = Vec::new(); - let fdt = super::fdt().ok_or_else(|| anyhow!("No FDT found"))?; +impl FdtBuilder { + pub fn generate(&self, _vm_cfg: &AxVMConfig) -> Result> { + let mut generator = Gen::new(); + generator.generate() + } + + // pub fn generate2(&self, vm_cfg: &AxVMConfig) -> anyhow::Result> { + // let mut fdt_writer = FdtWriter::new().unwrap(); + // // Track the level of the previously processed node for level change handling + // let mut previous_node_level = 0; + // // Maintain a stack of FDT nodes to correctly start and end nodes + // let mut node_stack: Vec = Vec::new(); + // let fdt = super::fdt().ok_or_else(|| anyhow!("No FDT found"))?; + + // let passthrough_device_names = find_all_passthrough_devices(vm_cfg, &fdt); + + // let all_nodes = fdt.all_nodes(); + + // for (index, node) in all_nodes.iter().enumerate() { + // let node_path = build_node_path(&all_nodes, index); + // let node_action = determine_node_action(node, &node_path, &passthrough_device_names); + + // match node_action { + // NodeAction::RootNode => { + // node_stack.push(fdt_writer.begin_node("").unwrap()); + // } + // NodeAction::CpuNode => { + // let need = need_cpu_node(&self.cpu_hard_ids, node, &node_path); + // if need { + // handle_node_level_change( + // &mut fdt_writer, + // &mut node_stack, + // node.level(), + // previous_node_level, + // ); + // node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); + // } else { + // continue; + // } + // } + // NodeAction::Skip => { + // continue; + // } + // _ => { + // trace!( + // "Found exact passthrough device node: {}, path: {}", + // node.name(), + // node_path + // ); + // handle_node_level_change( + // &mut fdt_writer, + // &mut node_stack, + // node.level(), + // previous_node_level, + // ); + // node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); + // } + // } + + // previous_node_level = node.level(); + + // // Copy all properties of the node + // for prop in node.properties() { + // fdt_writer.property(prop.name, prop.raw_value()).unwrap(); + // } + // } + + // // End all unclosed nodes + // while let Some(node) = node_stack.pop() { + // previous_node_level -= 1; + // fdt_writer.end_node(node).unwrap(); + // } + // assert_eq!(previous_node_level, 0); + + // let out = fdt_writer.finish().unwrap(); + + // Ok(out) + // } +} - let passthrough_device_names = find_all_passthrough_devices(vm_cfg, &fdt); +struct Gen { + tree: Tree, +} + +impl Gen { + fn new() -> Self { + Self { + tree: Tree::default(), + } + } + fn generate(&mut self) -> Result> { + let fdt = fdt().ok_or_else(|| anyhow::anyhow!("No FDT found"))?; let all_nodes = fdt.all_nodes(); for (index, node) in all_nodes.iter().enumerate() { - let node_path = build_node_path(&all_nodes, index); - let node_action = determine_node_action(node, &node_path, &passthrough_device_names); + let path = build_node_path(&all_nodes, index); + self.tree.insert(&path, node.clone())?; + } - match node_action { - NodeAction::RootNode => { - node_stack.push(fdt_writer.begin_node("").unwrap()); - } - NodeAction::CpuNode => { - let need = need_cpu_node(&self.cpu_hard_ids, node, &node_path); - if need { - handle_node_level_change( - &mut fdt_writer, - &mut node_stack, - node.level(), - previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - } else { - continue; - } - } - NodeAction::Skip => { - continue; - } - _ => { - trace!( - "Found exact passthrough device node: {}, path: {}", - node.name(), - node_path - ); - handle_node_level_change( - &mut fdt_writer, - &mut node_stack, - node.level(), - previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - } - } + self.tree.finalize()?; + self.to_data() + } - previous_node_level = node.level(); + fn to_data(&self) -> Result> { + let mut fdt_writer = FdtWriter::new().map_err(|e| anyhow::anyhow!("{e}"))?; + self.tree.write(&mut fdt_writer)?; + let data = fdt_writer.finish().map_err(|e| anyhow::anyhow!("{e}"))?; - // Copy all properties of the node - for prop in node.properties() { - fdt_writer.property(prop.name, prop.raw_value()).unwrap(); + let fdt = Fdt::from_bytes(&data)?; + print_fdt(&fdt); + Ok(data) + } +} + +#[derive(Default)] +struct Tree { + nodes: BTreeMap, + pending_links: Vec<(String, String)>, +} + +impl Tree { + fn insert(&mut self, path: &str, node: Node) -> Result<()> { + match self.nodes.entry(path.to_string()) { + Entry::Occupied(mut occ) => occ.get_mut().node = node, + Entry::Vacant(vac) => { + vac.insert(TreeNode::new(node)); } } - // End all unclosed nodes - while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - fdt_writer.end_node(node).unwrap(); + if let Some(parent) = parent_path(path) { + self.pending_links.push((parent, path.to_string())); } - assert_eq!(previous_node_level, 0); - let out = fdt_writer.finish().unwrap(); + Ok(()) + } + + fn finalize(&mut self) -> Result<()> { + for (parent, child) in self.pending_links.drain(..) { + let parent_node = self + .nodes + .get_mut(&parent) + .ok_or_else(|| anyhow::anyhow!("Parent node {parent} missing for {child}"))?; + parent_node.children.push(child); + } + Ok(()) + } - Ok(out) + fn write(&self, writer: &mut FdtWriter) -> Result<()> { + self.write_node(writer, "/") + } + + fn write_node(&self, writer: &mut FdtWriter, path: &str) -> Result<()> { + let entry = self + .nodes + .get(path) + .ok_or_else(|| anyhow::anyhow!("Node {path} not found"))?; + debug!("Writing node: {}", path); + let name = if path == "/" { "" } else { entry.node.name() }; + let handle = writer + .begin_node(name) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + for prop in entry.node.properties() { + writer + .property(prop.name, prop.raw_value()) + .map_err(|e| anyhow::anyhow!("{e}"))?; + } + + for child in &entry.children { + self.write_node(writer, child)?; + } + + writer.end_node(handle).map_err(|e| anyhow::anyhow!("{e}")) + } +} + +struct TreeNode { + node: Node, + children: Vec, +} + +impl TreeNode { + fn new(node: Node) -> Self { + Self { + node, + children: Vec::new(), + } + } +} + +fn parent_path(path: &str) -> Option { + if path == "/" { + None + } else if let Some(idx) = path.rfind('/') { + if idx == 0 { + Some("/".to_string()) + } else { + Some(path[..idx].to_string()) + } + } else { + None } } @@ -453,7 +584,9 @@ pub fn build_optimized_node_cache<'a>(fdt: &'a Fdt) -> BTreeMap( descendant_paths } + +fn print_fdt(fdt: &Fdt) { + debug!("FDT Structure:"); + for node in fdt.all_nodes() { + let indent = " ".repeat(node.level().saturating_sub(1)); + debug!("{}Node: {}", indent, node.name()); + for prop in node.properties() { + debug!( + "{} Property: {} = {:?}", + indent, + prop.name, + prop.raw_value() + ); + } + } +} diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index 8d4bd21..8afd84c 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -1,16 +1,20 @@ use alloc::vec::Vec; -use fdt_parser::{Fdt, Status}; -mod r#gen; - -pub use r#gen::FdtGen; +pub(crate) fn fdt_edit() -> Option { + let addr = axhal::get_bootarg(); + if addr == 0 { + return None; + } + let fdt = unsafe { fdt_edit::Fdt::from_ptr(addr as *mut u8).ok()? }; + Some(fdt) +} -pub(crate) fn fdt() -> Option { +pub(crate) fn fdt() -> Option { let addr = axhal::get_bootarg(); if addr == 0 { return None; } - let fdt = unsafe { Fdt::from_ptr(addr as *mut u8).ok()? }; + let fdt = unsafe { fdt_parser::Fdt::from_ptr(addr as *mut u8).ok()? }; Some(fdt) } @@ -21,7 +25,7 @@ pub fn cpu_list() -> Option> { let cpus = nodes .into_iter() .filter(|node| node.name().contains("cpu@")) - .filter(|node| !matches!(node.status(), Some(Status::Disabled))) + .filter(|node| !matches!(node.status(), Some(fdt_parser::Status::Disabled))) .map(|node| { let reg = node .reg() diff --git a/src/vm/data.rs b/src/vm/data.rs index fd55074..21e6037 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -5,6 +5,7 @@ use std::{ }; pub use axaddrspace::MappingFlags; +use memory_addr::MemoryAddr; use crate::vhal::ArchHal; use crate::{ @@ -62,22 +63,32 @@ impl VmData { _gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); _size = *size; let mut g = self.addrspace.lock(); - g.map_linear(_gpa.as_usize().into(), hva.as_usize().into(), _size, flags) - .unwrap(); + g.map_linear( + _gpa.as_usize().into(), + hva.as_usize().into(), + _size.align_up_4k(), + flags, + ) + .unwrap(); } MemoryKind::Passthrough { hpa, size } => { hva = phys_to_virt(*hpa); _gpa = GuestPhysAddr::from_usize(hva.as_usize()); _size = *size; let mut g = self.addrspace.lock(); - g.map_linear(_gpa.as_usize().into(), hva.as_usize().into(), _size, flags) - .unwrap(); + g.map_linear( + _gpa.as_usize().into(), + hva.as_usize().into(), + _size.align_up_4k(), + flags, + ) + .unwrap(); } MemoryKind::Vmem { gpa, size } => { _gpa = *gpa; _size = *size; let mut g = self.addrspace.lock(); - g.map_alloc(_gpa.as_usize().into(), _size, flags, true) + g.map_alloc(_gpa.as_usize().into(), _size.align_up_4k(), flags, true) .unwrap(); } } @@ -241,7 +252,8 @@ impl Drop for GuestMemory { }; } _ => { - g.unmap(self.gpa.as_usize().into(), self.size).unwrap(); + g.unmap(self.gpa.as_usize().into(), self.size.align_up_4k()) + .unwrap(); } } } diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 10adcaf..ebef17f 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -8,8 +8,8 @@ use crate::{AxVMConfig, arch::VmInit}; mod data; mod machine; -use machine::*; pub(crate) use data::*; +use machine::*; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct VmId(usize); From 59133e102f78b59f204cf054488f41fb683dcc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 3 Dec 2025 16:50:45 +0800 Subject: [PATCH 39/74] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20VCpu=20?= =?UTF-8?q?=E9=80=80=E5=87=BA=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87=E5=9C=B0=E5=9D=80=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E6=98=A0=E5=B0=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 2 +- src/arch/aarch64/vm.rs | 90 +++++++++++++++++++++++++++++++++++++---- src/vm/data.rs | 2 +- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 4061dce..f9f4263 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -164,7 +164,7 @@ impl VCpu { } => todo!(), arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), arm_vcpu::AxVCpuExitReason::SystemDown => todo!(), - arm_vcpu::AxVCpuExitReason::Nothing => todo!(), + arm_vcpu::AxVCpuExitReason::Nothing => {} arm_vcpu::AxVCpuExitReason::SendIPI { target_cpu, target_cpu_aux, diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index 53c7b7a..f1883ca 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -4,6 +4,7 @@ use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinit use arm_vcpu::Aarch64VCpuSetupConfig; use fdt_edit::{Node, Property, RawProperty}; +use memory_addr::{MemoryAddr, align_down_4k, align_up_4k}; use crate::{ GuestPhysAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, @@ -285,6 +286,60 @@ impl VmStatusRunning { for path in nodes { let _ = fdt.remove_node(&path); } + + let mut pt_dev_region = vec![]; + + for node in fdt.all_nodes() { + for prop in &node.properties { + if let Property::Reg(ls) = prop { + for reg in ls { + if let Some(size) = reg.size { + // Align the base address and length to 4K boundaries. + pt_dev_region.push(( + align_down_4k(reg.address as _), + align_up_4k(size as _), + )); + } + } + } + } + } + pt_dev_region.sort_by_key(|(gpa, _)| *gpa); + + // Merge overlapping regions. + let pt_dev_region = pt_dev_region.into_iter().fold( + Vec::<(usize, usize)>::new(), + |mut acc, (gpa, len)| { + if let Some(last) = acc.last_mut() { + if last.0 + last.1 >= gpa { + // Merge with the last region. + last.1 = (last.0 + last.1).max(gpa + len) - last.0; + } else { + acc.push((gpa, len)); + } + } else { + acc.push((gpa, len)); + } + acc + }, + ); + + for (gpa, len) in &pt_dev_region { + self.data + .addrspace + .lock() + .map_linear( + (*gpa).into(), + (*gpa).into(), + *len, + MappingFlags::DEVICE + | MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::USER, + ) + .map_err(|e| anyhow!("{e}"))?; + } + let root_address_cells = fdt.root().address_cells().unwrap_or(2); let root_size_cells = fdt.root().size_cells().unwrap_or(2); @@ -306,19 +361,38 @@ impl VmStatusRunning { let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); debug!("Generated DTB:\n{f}"); - let kind = MemoryKind::Identical { - size: dtb_data.len(), - }; - let mut guest_mem = self.data.new_memory(&kind, flags); - - self.dtb_addr = guest_mem.gpa(); + let mut guest_mem = self.data.memories().into_iter().next().unwrap(); + let mut dtb_start = + (guest_mem.0.as_usize() + guest_mem.1.min(512 * 1024 * 1024)) - dtb_data.len(); + dtb_start = dtb_start.align_down_4k(); - guest_mem.copy_from_slice(0, &dtb_data); - self.data.add_reserved_memory(guest_mem); + self.dtb_addr = GuestPhysAddr::from(dtb_start); + debug!( + "Loading generated DTB into GPA @{:#x} for VM {} ({})", + dtb_start, + config.id(), + config.name() + ); + self.copy_to_guest(self.dtb_addr, &dtb_data); } Ok(()) } + + fn copy_to_guest(&mut self, gpa: GuestPhysAddr, data: &[u8]) { + let parts = self + .data + .addrspace + .lock() + .translated_byte_buffer(gpa.as_usize().into(), data.len()) + .unwrap(); + let mut offset = 0; + for part in parts { + let len = part.len().min(data.len() - offset); + part.copy_from_slice(&data[offset..offset + len]); + offset += len; + } + } } /// Information about a device in the VM diff --git a/src/vm/data.rs b/src/vm/data.rs index 21e6037..c1883e9 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -23,7 +23,7 @@ type AddrSpace = axaddrspace::AddrSpace; #[derive(Clone)] pub struct VmData { shared: Arc>, - addrspace: Arc>, + pub(crate) addrspace: Arc>, } impl VmData { From 59cedb714d8aea890f5f3652a0625d3147189011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Wed, 10 Dec 2025 17:31:56 +0800 Subject: [PATCH 40/74] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20fdt=5Fedit?= =?UTF-8?q?=20=E4=BE=9D=E8=B5=96=E7=89=88=E6=9C=AC=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=99=9A=E6=8B=9F=E6=9C=BA=E5=86=85=E5=AD=98=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- src/arch/aarch64/vm.rs | 65 +++++++++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b5fafec..a143928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ axruntime.workspace = true axstd.workspace = true axvm-types.workspace = true axvmconfig = {version = "0.1", default-features = false} -fdt-edit = {git = "https://github.com/drivercraft/fdt-parser.git"} +fdt-edit = "0.1" [target.'cfg(target_arch = "x86_64")'.dependencies] # x86_vcpu = "0.1" diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index f1883ca..a286b41 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -3,7 +3,7 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}; use arm_vcpu::Aarch64VCpuSetupConfig; -use fdt_edit::{Node, Property, RawProperty}; +use fdt_edit::{Node, NodeRef, Property, RegInfo}; use memory_addr::{MemoryAddr, align_down_4k, align_up_4k}; use crate::{ @@ -279,9 +279,9 @@ impl VmStatusRunning { ); let mut fdt = crate::fdt::fdt_edit().expect("Need fdt"); let nodes = fdt - .find_all_by_path("/memory") + .find_by_path("/memory") .into_iter() - .map(|o| o.1) + .map(|o| o.path()) .collect::>(); for path in nodes { let _ = fdt.remove_node(&path); @@ -290,16 +290,33 @@ impl VmStatusRunning { let mut pt_dev_region = vec![]; for node in fdt.all_nodes() { - for prop in &node.properties { - if let Property::Reg(ls) = prop { - for reg in ls { - if let Some(size) = reg.size { - // Align the base address and length to 4K boundaries. - pt_dev_region.push(( - align_down_4k(reg.address as _), - align_up_4k(size as _), - )); - } + if matches!(node.status(), Some(fdt_edit::Status::Disabled)) { + continue; + } + + if let Some(regs) = node.regs() { + for reg in regs { + if let Some(size) = reg.size + && size > 0 + { + // Align the base address and length to 4K boundaries. + pt_dev_region + .push((align_down_4k(reg.address as _), align_up_4k(size as _))); + } + } + } + + if let NodeRef::Pci(pci) = &node + && let Some(ranges) = pci.ranges() + { + for range in ranges { + if range.size > 0 + { + // Align the base address and length to 4K boundaries. + pt_dev_region.push(( + align_down_4k(range.cpu_address as _), + align_up_4k(range.size as _), + )); } } } @@ -344,19 +361,21 @@ impl VmStatusRunning { let root_size_cells = fdt.root().size_cells().unwrap_or(2); for (i, m) in self.data.memories().iter().enumerate() { - let mut node = Node::new(format!("memory@{i}")); - node.add_property(Property::Raw(RawProperty::from_string( - "device_type", - "memory", - ))); - node.add_property(Property::Reg(vec![fdt_edit::RegInfo { - address: m.0.as_usize() as u64, - size: Some(m.1 as _), - }])); + let mut node = Node::new(&format!("memory@{i}")); + let mut prop = Property::new("device_type", vec![]); + prop.set_string("memory"); + node.add_property(prop); fdt.root_mut().add_child(node); + let mut node = fdt + .get_by_path_mut(&format!("/memory@{i}")) + .expect("must has node"); + node.set_regs(&[RegInfo { + address: m.0.as_usize() as u64, + size: Some(m.1 as u64), + }]); } - let dtb_data = fdt.to_bytes(); + let dtb_data = fdt.encode(); let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); debug!("Generated DTB:\n{f}"); From c68731498e87bf0ff19099205c13097bb43bd210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 12 Dec 2025 09:34:44 +0800 Subject: [PATCH 41/74] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=98=A0=E5=B0=84=E5=A4=84=E7=90=86=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AE=BE=E5=A4=87=E5=90=8D=E7=A7=B0=E5=88=B0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=8C=BA=E5=9F=9F=E5=90=88=E5=B9=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm.rs | 78 +++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index a286b41..e9ae2b9 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -1,6 +1,9 @@ use alloc::{string::String, sync::Arc, vec::Vec}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}; +use std::{ + os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, + string::ToString, +}; use arm_vcpu::Aarch64VCpuSetupConfig; use fdt_edit::{Node, NodeRef, Property, RegInfo}; @@ -293,6 +296,7 @@ impl VmStatusRunning { if matches!(node.status(), Some(fdt_edit::Status::Disabled)) { continue; } + let name = node.name().to_string(); if let Some(regs) = node.regs() { for reg in regs { @@ -300,8 +304,11 @@ impl VmStatusRunning { && size > 0 { // Align the base address and length to 4K boundaries. - pt_dev_region - .push((align_down_4k(reg.address as _), align_up_4k(size as _))); + pt_dev_region.push(( + align_down_4k(reg.address as _), + align_up_4k(size as _), + name.clone(), + )); } } } @@ -310,38 +317,62 @@ impl VmStatusRunning { && let Some(ranges) = pci.ranges() { for range in ranges { - if range.size > 0 - { + if range.size > 0 { // Align the base address and length to 4K boundaries. pt_dev_region.push(( align_down_4k(range.cpu_address as _), align_up_4k(range.size as _), + name.clone(), )); } } } } - pt_dev_region.sort_by_key(|(gpa, _)| *gpa); + pt_dev_region.sort_by_key(|(gpa, ..)| *gpa); + + let root_address_cells = fdt.root().address_cells().unwrap_or(2); + let root_size_cells = fdt.root().size_cells().unwrap_or(2); + + for (i, m) in self.data.memories().iter().enumerate() { + let mut node = Node::new(&format!("memory@{i}")); + let mut prop = Property::new("device_type", vec![]); + prop.set_string("memory"); + node.add_property(prop); + fdt.root_mut().add_child(node); + let mut node = fdt + .get_by_path_mut(&format!("/memory@{i}")) + .expect("must has node"); + node.set_regs(&[RegInfo { + address: m.0.as_usize() as u64, + size: Some(m.1 as u64), + }]); + } + + let dtb_data = fdt.encode(); + + let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); + debug!("Generated DTB:\n{f}"); // Merge overlapping regions. let pt_dev_region = pt_dev_region.into_iter().fold( - Vec::<(usize, usize)>::new(), - |mut acc, (gpa, len)| { + Vec::<(usize, usize, String)>::new(), + |mut acc, (gpa, len, name)| { if let Some(last) = acc.last_mut() { + let last_name = last.2.clone(); if last.0 + last.1 >= gpa { // Merge with the last region. last.1 = (last.0 + last.1).max(gpa + len) - last.0; } else { - acc.push((gpa, len)); + acc.push((gpa, len, last_name)); } } else { - acc.push((gpa, len)); + acc.push((gpa, len, name)); } acc }, ); - for (gpa, len) in &pt_dev_region { + for (gpa, len, name) in &pt_dev_region { self.data .addrspace .lock() @@ -354,32 +385,9 @@ impl VmStatusRunning { | MappingFlags::WRITE | MappingFlags::USER, ) - .map_err(|e| anyhow!("{e}"))?; + .map_err(|e| anyhow!("`{name}` map fail:\n {e}"))?; } - let root_address_cells = fdt.root().address_cells().unwrap_or(2); - let root_size_cells = fdt.root().size_cells().unwrap_or(2); - - for (i, m) in self.data.memories().iter().enumerate() { - let mut node = Node::new(&format!("memory@{i}")); - let mut prop = Property::new("device_type", vec![]); - prop.set_string("memory"); - node.add_property(prop); - fdt.root_mut().add_child(node); - let mut node = fdt - .get_by_path_mut(&format!("/memory@{i}")) - .expect("must has node"); - node.set_regs(&[RegInfo { - address: m.0.as_usize() as u64, - size: Some(m.1 as u64), - }]); - } - - let dtb_data = fdt.encode(); - - let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); - debug!("Generated DTB:\n{f}"); - let mut guest_mem = self.data.memories().into_iter().next().unwrap(); let mut dtb_start = (guest_mem.0.as_usize() + guest_mem.1.min(512 * 1024 * 1024)) - dtb_data.len(); From f953635fc0cf9a2eac529a1d6ee5fd6039503667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 12 Dec 2025 14:48:51 +0800 Subject: [PATCH 42/74] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E7=9A=84=20FDT=20=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=20fdt=5Fedit=20=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 - src/arch/aarch64/vm.rs | 23 +- src/fdt/gen.rs | 882 ----------------------------------------- src/fdt/mod.rs | 27 +- 4 files changed, 19 insertions(+), 914 deletions(-) delete mode 100644 src/fdt/gen.rs diff --git a/Cargo.toml b/Cargo.toml index a143928..43e2fcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ vmx = [] [dependencies] anyhow = {version = "1.0", default-features = false} cfg-if = "1.0" -fdt-parser = "0.5" lazyinit = "0.2" log = "0.4" spin = "0.10" diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm.rs index e9ae2b9..a0060ba 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm.rs @@ -234,18 +234,6 @@ pub struct VmStatusRunning { } impl VmStatusRunning { - // fn load_images(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { - // // Load other images (BIOS, DTB, Ramdisk) similarly... - // debug!( - // "Loading kernel image for VM {} ({})", - // config.id(), - // config.name() - // ); - // let _main_region_idx = self.load_kernel_image(config)?; - - // Ok(()) - // } - fn make_dtb(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { let flags = MappingFlags::READ | MappingFlags::WRITE | MappingFlags::WRITE | MappingFlags::USER; @@ -385,7 +373,7 @@ impl VmStatusRunning { | MappingFlags::WRITE | MappingFlags::USER, ) - .map_err(|e| anyhow!("`{name}` map fail:\n {e}"))?; + .map_err(|e| anyhow!("`{name}` map [{:#x}, {:#x}) fail:\n {e}", *gpa, len))?; } let mut guest_mem = self.data.memories().into_iter().next().unwrap(); @@ -406,6 +394,8 @@ impl VmStatusRunning { Ok(()) } + fn handle_node_regs(dev_vec: &mut [DevMapConfig], node: &NodeRef<'_>) {} + fn copy_to_guest(&mut self, gpa: GuestPhysAddr, data: &[u8]) { let parts = self .data @@ -425,3 +415,10 @@ impl VmStatusRunning { /// Information about a device in the VM #[derive(Debug, Clone)] pub struct DeviceInfo {} + +#[derive(Debug, Clone)] +struct DevMapConfig { + gpa: GuestPhysAddr, + size: usize, + name: String, +} diff --git a/src/fdt/gen.rs b/src/fdt/gen.rs deleted file mode 100644 index 3d122fb..0000000 --- a/src/fdt/gen.rs +++ /dev/null @@ -1,882 +0,0 @@ -use std::{ - collections::{btree_map::BTreeMap, btree_map::Entry, btree_set::BTreeSet}, - string::{String, ToString}, - vec::Vec, -}; - -use anyhow::Result; -use fdt_parser::{Fdt, Node}; -use vm_fdt::{FdtWriter, FdtWriterNode}; - -use crate::{AxVMConfig, GuestPhysAddr, fdt::fdt, vhal::cpu::CpuHardId}; - -pub struct FdtBuilder { - pub cpu_hard_ids: Vec, - pub memories: Vec<(GuestPhysAddr, usize)>, // (start, size) -} - -impl FdtBuilder { - pub fn generate(&self, _vm_cfg: &AxVMConfig) -> Result> { - let mut generator = Gen::new(); - generator.generate() - } - - // pub fn generate2(&self, vm_cfg: &AxVMConfig) -> anyhow::Result> { - // let mut fdt_writer = FdtWriter::new().unwrap(); - // // Track the level of the previously processed node for level change handling - // let mut previous_node_level = 0; - // // Maintain a stack of FDT nodes to correctly start and end nodes - // let mut node_stack: Vec = Vec::new(); - // let fdt = super::fdt().ok_or_else(|| anyhow!("No FDT found"))?; - - // let passthrough_device_names = find_all_passthrough_devices(vm_cfg, &fdt); - - // let all_nodes = fdt.all_nodes(); - - // for (index, node) in all_nodes.iter().enumerate() { - // let node_path = build_node_path(&all_nodes, index); - // let node_action = determine_node_action(node, &node_path, &passthrough_device_names); - - // match node_action { - // NodeAction::RootNode => { - // node_stack.push(fdt_writer.begin_node("").unwrap()); - // } - // NodeAction::CpuNode => { - // let need = need_cpu_node(&self.cpu_hard_ids, node, &node_path); - // if need { - // handle_node_level_change( - // &mut fdt_writer, - // &mut node_stack, - // node.level(), - // previous_node_level, - // ); - // node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - // } else { - // continue; - // } - // } - // NodeAction::Skip => { - // continue; - // } - // _ => { - // trace!( - // "Found exact passthrough device node: {}, path: {}", - // node.name(), - // node_path - // ); - // handle_node_level_change( - // &mut fdt_writer, - // &mut node_stack, - // node.level(), - // previous_node_level, - // ); - // node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - // } - // } - - // previous_node_level = node.level(); - - // // Copy all properties of the node - // for prop in node.properties() { - // fdt_writer.property(prop.name, prop.raw_value()).unwrap(); - // } - // } - - // // End all unclosed nodes - // while let Some(node) = node_stack.pop() { - // previous_node_level -= 1; - // fdt_writer.end_node(node).unwrap(); - // } - // assert_eq!(previous_node_level, 0); - - // let out = fdt_writer.finish().unwrap(); - - // Ok(out) - // } -} - -struct Gen { - tree: Tree, -} - -impl Gen { - fn new() -> Self { - Self { - tree: Tree::default(), - } - } - - fn generate(&mut self) -> Result> { - let fdt = fdt().ok_or_else(|| anyhow::anyhow!("No FDT found"))?; - let all_nodes = fdt.all_nodes(); - - for (index, node) in all_nodes.iter().enumerate() { - let path = build_node_path(&all_nodes, index); - self.tree.insert(&path, node.clone())?; - } - - self.tree.finalize()?; - self.to_data() - } - - fn to_data(&self) -> Result> { - let mut fdt_writer = FdtWriter::new().map_err(|e| anyhow::anyhow!("{e}"))?; - self.tree.write(&mut fdt_writer)?; - let data = fdt_writer.finish().map_err(|e| anyhow::anyhow!("{e}"))?; - - let fdt = Fdt::from_bytes(&data)?; - print_fdt(&fdt); - Ok(data) - } -} - -#[derive(Default)] -struct Tree { - nodes: BTreeMap, - pending_links: Vec<(String, String)>, -} - -impl Tree { - fn insert(&mut self, path: &str, node: Node) -> Result<()> { - match self.nodes.entry(path.to_string()) { - Entry::Occupied(mut occ) => occ.get_mut().node = node, - Entry::Vacant(vac) => { - vac.insert(TreeNode::new(node)); - } - } - - if let Some(parent) = parent_path(path) { - self.pending_links.push((parent, path.to_string())); - } - - Ok(()) - } - - fn finalize(&mut self) -> Result<()> { - for (parent, child) in self.pending_links.drain(..) { - let parent_node = self - .nodes - .get_mut(&parent) - .ok_or_else(|| anyhow::anyhow!("Parent node {parent} missing for {child}"))?; - parent_node.children.push(child); - } - Ok(()) - } - - fn write(&self, writer: &mut FdtWriter) -> Result<()> { - self.write_node(writer, "/") - } - - fn write_node(&self, writer: &mut FdtWriter, path: &str) -> Result<()> { - let entry = self - .nodes - .get(path) - .ok_or_else(|| anyhow::anyhow!("Node {path} not found"))?; - debug!("Writing node: {}", path); - let name = if path == "/" { "" } else { entry.node.name() }; - let handle = writer - .begin_node(name) - .map_err(|e| anyhow::anyhow!("{e}"))?; - - for prop in entry.node.properties() { - writer - .property(prop.name, prop.raw_value()) - .map_err(|e| anyhow::anyhow!("{e}"))?; - } - - for child in &entry.children { - self.write_node(writer, child)?; - } - - writer.end_node(handle).map_err(|e| anyhow::anyhow!("{e}")) - } -} - -struct TreeNode { - node: Node, - children: Vec, -} - -impl TreeNode { - fn new(node: Node) -> Self { - Self { - node, - children: Vec::new(), - } - } -} - -fn parent_path(path: &str) -> Option { - if path == "/" { - None - } else if let Some(idx) = path.rfind('/') { - if idx == 0 { - Some("/".to_string()) - } else { - Some(path[..idx].to_string()) - } - } else { - None - } -} - -/// Determine if CPU node is needed -fn need_cpu_node(phys_cpu_ids: &[CpuHardId], node: &Node, node_path: &str) -> bool { - let mut should_include_node = false; - - if !node_path.starts_with("/cpus/cpu@") { - should_include_node = true; - } else if let Ok(mut cpu_reg) = node.reg() - && let Some(reg_entry) = cpu_reg.first() - { - let cpu_address = reg_entry.address as usize; - debug!( - "Checking CPU node {} with address 0x{:x}", - node.name(), - cpu_address - ); - // Check if this CPU address is in the configured phys_cpu_ids - if phys_cpu_ids.contains(&CpuHardId::new(cpu_address)) { - should_include_node = true; - debug!( - "CPU node {} with address 0x{:x} is in phys_cpu_ids, including in guest FDT", - node.name(), - cpu_address - ); - } else { - debug!( - "CPU node {} with address 0x{:x} is NOT in phys_cpu_ids, skipping", - node.name(), - cpu_address - ); - } - } - should_include_node -} - -/// Build the full path of a node based on node level relationships -/// Build the path by traversing all nodes and constructing paths based on level relationships to avoid path conflicts for nodes with the same name -pub fn build_node_path(all_nodes: &[Node], target_index: usize) -> String { - let mut path_stack: Vec = Vec::new(); - - for node in all_nodes.iter().take(target_index + 1) { - let level = node.level(); - - if level == 1 { - path_stack.clear(); - if node.name() != "/" { - path_stack.push(node.name().to_string()); - } - } else { - while path_stack.len() >= level - 1 { - path_stack.pop(); - } - path_stack.push(node.name().to_string()); - } - } - - // Build the full path of the current node - if path_stack.is_empty() || (path_stack.len() == 1 && path_stack[0] == "/") { - "/".to_string() - } else { - "/".to_string() + &path_stack.join("/") - } -} - -/// Determine node processing action -fn determine_node_action( - node: &Node, - node_path: &str, - passthrough_device_names: &[String], -) -> NodeAction { - if node.name() == "/" { - // Special handling for root node - NodeAction::RootNode - } else if node.name().starts_with("memory") { - // Skip memory nodes, will add them later - NodeAction::Skip - } else if node_path.starts_with("/cpus") { - NodeAction::CpuNode - } else if passthrough_device_names.contains(&node_path.to_string()) { - // Fully matched passthrough device node - NodeAction::IncludeAsPassthroughDevice - } - // Check if the node is a descendant of a passthrough device (by path inclusion and level validation) - else if is_descendant_of_passthrough_device(node_path, node.level(), passthrough_device_names) - { - NodeAction::IncludeAsChildNode - } - // Check if the node is an ancestor of a passthrough device (by path inclusion and level validation) - else if is_ancestor_of_passthrough_device(node_path, passthrough_device_names) { - NodeAction::IncludeAsAncestorNode - } else { - NodeAction::Skip - } -} - -/// Node processing action enumeration -enum NodeAction { - /// Skip node, not included in guest FDT - Skip, - /// Root node - RootNode, - /// CPU node - CpuNode, - /// Include node as passthrough device node - IncludeAsPassthroughDevice, - /// Include node as child node of passthrough device - IncludeAsChildNode, - /// Include node as ancestor node of passthrough device - IncludeAsAncestorNode, -} - -/// Handle node level changes to ensure correct FDT structure -fn handle_node_level_change( - fdt_writer: &mut FdtWriter, - node_stack: &mut Vec, - current_level: usize, - previous_level: usize, -) { - if current_level <= previous_level { - for _ in current_level..=previous_level { - if let Some(end_node) = node_stack.pop() { - fdt_writer.end_node(end_node).unwrap(); - } - } - } -} - -/// Determine if node is a descendant of passthrough device -/// When node path contains a path from passthrough_device_names and is longer than it, it is its descendant node -/// Also use node_level as validation condition -fn is_descendant_of_passthrough_device( - node_path: &str, - node_level: usize, - passthrough_device_names: &[String], -) -> bool { - for passthrough_path in passthrough_device_names { - // Check if the current node is a descendant of a passthrough device - if node_path.starts_with(passthrough_path) && node_path.len() > passthrough_path.len() { - // Ensure it is a true descendant path (separated by /) - if passthrough_path == "/" || node_path.chars().nth(passthrough_path.len()) == Some('/') - { - // Use level relationship for validation: the level of a descendant node should be higher than its parent - // Note: The level of the root node is 1, its direct child node level is 2, and so on - let expected_parent_level = passthrough_path.matches('/').count(); - let current_node_level = node_level; - - // If passthrough_path is the root node "/", then its child node level should be 2 - // Otherwise, the child node level should be higher than the parent node level - if (passthrough_path == "/" && current_node_level >= 2) - || (passthrough_path != "/" && current_node_level > expected_parent_level) - { - return true; - } - } - } - } - false -} - -/// Determine if node is an ancestor of passthrough device -fn is_ancestor_of_passthrough_device(node_path: &str, passthrough_device_names: &[String]) -> bool { - for passthrough_path in passthrough_device_names { - // Check if the current node is an ancestor of a passthrough device - if passthrough_path.starts_with(node_path) && passthrough_path.len() > node_path.len() { - // Ensure it is a true ancestor path (separated by /) - let next_char = passthrough_path.chars().nth(node_path.len()).unwrap_or(' '); - if next_char == '/' || node_path == "/" { - return true; - } - } - } - false -} - -/// Return the collection of all passthrough devices in the configuration file and newly added devices found -pub fn find_all_passthrough_devices(vm_cfg: &AxVMConfig, fdt: &Fdt) -> Vec { - let initial_device_count = vm_cfg.pass_through_devices().len(); - - // Pre-build node cache, store all nodes by path to improve lookup performance - let node_cache: BTreeMap> = build_optimized_node_cache(fdt); - - // Get the list of configured device names - let initial_device_names: Vec = vm_cfg - .pass_through_devices() - .iter() - .map(|dev| dev.name.clone()) - .collect(); - - // Phase 1: Discover descendant nodes of all passthrough devices in the configuration file - // Build a set of configured devices, using BTreeSet to improve lookup efficiency - let mut configured_device_names: BTreeSet = - initial_device_names.iter().cloned().collect(); - - // Used to store newly discovered related device names - let mut additional_device_names = Vec::new(); - - // Phase 1: Process initial devices and their descendant nodes - // Note: Directly use device paths instead of device names - for device_name in &initial_device_names { - // Get all descendant node paths for this device - let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_name); - trace!( - "Found {} descendant paths for {}", - descendant_paths.len(), - device_name - ); - - for descendant_path in descendant_paths { - if !configured_device_names.contains(&descendant_path) { - trace!("Found descendant device: {descendant_path}"); - configured_device_names.insert(descendant_path.clone()); - - additional_device_names.push(descendant_path.clone()); - } else { - trace!("Device already exists: {descendant_path}"); - } - } - } - - info!( - "Phase 1 completed: Found {} new descendant device names", - additional_device_names.len() - ); - - // Phase 2: Discover dependency nodes for all existing devices (including descendant devices) - let mut dependency_device_names = Vec::new(); - // Use a work queue of device names, including initial devices and descendant device names - let mut devices_to_process: Vec = configured_device_names.iter().cloned().collect(); - let mut processed_devices: BTreeSet = BTreeSet::new(); - - // Build phandle mapping table - let phandle_map = build_phandle_map(fdt); - - // Use work queue to recursively find all dependent devices - while let Some(device_node_path) = devices_to_process.pop() { - // Avoid processing the same device repeatedly - if processed_devices.contains(&device_node_path) { - continue; - } - processed_devices.insert(device_node_path.clone()); - - trace!("Analyzing dependencies for device: {device_node_path}"); - - // Find direct dependencies of the current device - let dependencies = find_device_dependencies(&device_node_path, &phandle_map, &node_cache); - trace!( - "Found {} dependencies: {:?}", - dependencies.len(), - dependencies - ); - for dep_node_name in dependencies { - // Check if dependency is already in configuration - if !configured_device_names.contains(&dep_node_name) { - trace!("Found new dependency device: {dep_node_name}"); - dependency_device_names.push(dep_node_name.clone()); - - // Add dependency device name to work queue to further find its dependencies - devices_to_process.push(dep_node_name.clone()); - configured_device_names.insert(dep_node_name.clone()); - } - } - } - - info!( - "Phase 2 completed: Found {} new dependency device names", - dependency_device_names.len() - ); - - // Phase 3: Find all excluded devices and remove them from the list - // Convert Vec> to Vec - let excluded_device_path: Vec = vm_cfg - .excluded_devices() - .iter() - .flatten() - .cloned() - .collect(); - let mut all_excludes_devices = excluded_device_path.clone(); - let mut process_excludeds: BTreeSet = excluded_device_path.iter().cloned().collect(); - - for device_path in &excluded_device_path { - // Get all descendant node paths for this device - let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_path); - info!( - "Found {} descendant paths for {}", - descendant_paths.len(), - device_path - ); - - for descendant_path in descendant_paths { - if !process_excludeds.contains(&descendant_path) { - trace!("Found descendant device: {descendant_path}"); - process_excludeds.insert(descendant_path.clone()); - - all_excludes_devices.push(descendant_path.clone()); - } else { - trace!("Device already exists: {descendant_path}"); - } - } - } - info!("Found excluded devices: {all_excludes_devices:?}"); - - // Merge all device name lists - let mut all_device_names = initial_device_names.clone(); - all_device_names.extend(additional_device_names); - all_device_names.extend(dependency_device_names); - - // Remove excluded devices from the final list - if !all_excludes_devices.is_empty() { - info!( - "Removing {} excluded devices from the list", - all_excludes_devices.len() - ); - let excluded_set: BTreeSet = all_excludes_devices.into_iter().collect(); - - // Filter out excluded devices - all_device_names.retain(|device_name| { - let should_keep = !excluded_set.contains(device_name); - if !should_keep { - info!("Excluding device: {device_name}"); - } - should_keep - }); - } - - // Phase 4: remove root node from the list - all_device_names.retain(|device_name| device_name != "/"); - - let final_device_count = all_device_names.len(); - info!( - "Passthrough devices analysis completed. Total devices: {} (added: {})", - final_device_count, - final_device_count - initial_device_count - ); - - // Print final device list - for (i, device_name) in all_device_names.iter().enumerate() { - trace!("Final passthrough device[{i}]: {device_name}"); - } - - all_device_names -} - -/// Build a simplified node cache table, traverse all nodes once and group by full path -/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name -pub fn build_optimized_node_cache<'a>(fdt: &'a Fdt) -> BTreeMap> { - let mut node_cache: BTreeMap> = BTreeMap::new(); - - let all_nodes = fdt.all_nodes(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = build_node_path(&all_nodes, index); - if let Some(existing_nodes) = node_cache.get(&node_path) - && !existing_nodes.is_empty() - { - error!( - "Duplicate node path found: {} for node '{}' at level {}, existing node: '{}'", - node_path, - node.name(), - node.level(), - existing_nodes[0].name() - ); - } - - trace!( - "Adding node to cache: {} (level: {}, index: {})", - node_path, - node.level(), - index - ); - node_cache.entry(node_path).or_default().push(node.clone()); - } - - debug!( - "Built simplified node cache with {} unique device paths", - node_cache.len() - ); - node_cache -} - -/// Build a mapping table from phandle to node information, optimized version using fdt-parser convenience methods -/// Use full path instead of node name -/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name -fn build_phandle_map(fdt: &Fdt) -> BTreeMap)> { - let mut phandle_map = BTreeMap::new(); - - let all_nodes = fdt.all_nodes(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = build_node_path(&all_nodes, index); - - // Collect node properties - let mut phandle = None; - let mut cells_map = BTreeMap::new(); - for prop in node.properties() { - match prop.name { - "phandle" | "linux,phandle" => { - phandle = Some(prop.u32().unwrap()); - } - "#address-cells" - | "#size-cells" - | "#clock-cells" - | "#reset-cells" - | "#gpio-cells" - | "#interrupt-cells" - | "#power-domain-cells" - | "#thermal-sensor-cells" - | "#phy-cells" - | "#dma-cells" - | "#sound-dai-cells" - | "#mbox-cells" - | "#pwm-cells" - | "#iommu-cells" => { - cells_map.insert(prop.name.to_string(), prop.u32().unwrap()); - } - _ => {} - } - } - - // If phandle is found, store it together with the node's full path - if let Some(ph) = phandle { - phandle_map.insert(ph, (node_path, cells_map)); - } - } - phandle_map -} - -/// Parse properties containing phandle references intelligently based on #*-cells properties -/// Supports multiple formats: -/// - Single phandle: -/// - phandle+specifier: -/// - Multiple phandle references: -fn parse_phandle_property_with_cells( - prop_data: &[u8], - prop_name: &str, - phandle_map: &BTreeMap)>, -) -> Vec<(u32, Vec)> { - let mut results = Vec::new(); - - debug!( - "Parsing property '{}' with cells info, data length: {} bytes", - prop_name, - prop_data.len() - ); - - if prop_data.is_empty() || prop_data.len() % 4 != 0 { - warn!( - "Property '{}' data length ({} bytes) is invalid", - prop_name, - prop_data.len() - ); - return results; - } - - let u32_values: Vec = prop_data - .chunks(4) - .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) - .collect(); - - let mut i = 0; - while i < u32_values.len() { - let potential_phandle = u32_values[i]; - - // Check if it's a valid phandle - if let Some((device_name, cells_info)) = phandle_map.get(&potential_phandle) { - // Determine the number of cells required based on property name - let cells_count = get_cells_count_for_property(prop_name, cells_info); - trace!( - "Property '{prop_name}' requires {cells_count} cells for device '{device_name}'" - ); - - // Check if there's enough data - if i + cells_count < u32_values.len() { - let specifiers: Vec = u32_values[i + 1..=i + cells_count].to_vec(); - debug!( - "Parsed phandle reference: phandle={potential_phandle:#x}, specifiers={specifiers:?}" - ); - results.push((potential_phandle, specifiers)); - i += cells_count + 1; // Skip phandle and all specifiers - } else { - warn!( - "Property:{} not enough data for phandle {:#x}, expected {} cells but only {} values remaining", - prop_name, - potential_phandle, - cells_count, - u32_values.len() - i - 1 - ); - break; - } - } else { - // If not a valid phandle, skip this value - i += 1; - } - } - - results -} - -/// Determine the required number of cells based on property name and target node's cells information -fn get_cells_count_for_property(prop_name: &str, cells_info: &BTreeMap) -> usize { - let cells_property = match prop_name { - "clocks" | "assigned-clocks" => "#clock-cells", - "resets" => "#reset-cells", - "power-domains" => "#power-domain-cells", - "phys" => "#phy-cells", - "interrupts" | "interrupts-extended" => "#interrupt-cells", - "gpios" => "#gpio-cells", - _ if prop_name.ends_with("-gpios") || prop_name.ends_with("-gpio") => "#gpio-cells", - "dmas" => "#dma-cells", - "thermal-sensors" => "#thermal-sensor-cells", - "sound-dai" => "#sound-dai-cells", - "mboxes" => "#mbox-cells", - "pwms" => "#pwm-cells", - _ => { - debug!("Unknown property '{prop_name}', defaulting to 0 cell"); - return 0; - } - }; - - cells_info.get(cells_property).copied().unwrap_or(0) as usize -} - -/// Generic phandle property parsing function -/// Parse phandle references according to cells information with correct block size -/// Support single phandle and multiple phandle+specifier formats -/// Return full path instead of node name -fn parse_phandle_property( - prop_data: &[u8], - prop_name: &str, - phandle_map: &BTreeMap)>, -) -> Vec { - let mut dependencies = Vec::new(); - - let phandle_refs = parse_phandle_property_with_cells(prop_data, prop_name, phandle_map); - - for (phandle, specifiers) in phandle_refs { - if let Some((device_path, _cells_info)) = phandle_map.get(&phandle) { - let spec_info = if !specifiers.is_empty() { - format!(" (specifiers: {specifiers:?})") - } else { - String::new() - }; - debug!( - "Found {prop_name} dependency: phandle={phandle:#x}, device={device_path}{spec_info}" - ); - dependencies.push(device_path.clone()); - } - } - - dependencies -} - -/// Device property classifier - used to identify properties that require special handling -struct DevicePropertyClassifier; - -impl DevicePropertyClassifier { - /// Phandle properties that require special handling - includes all properties that need dependency resolution - const PHANDLE_PROPERTIES: &'static [&'static str] = &[ - "clocks", - "power-domains", - "phys", - "resets", - "dmas", - "thermal-sensors", - "mboxes", - "assigned-clocks", - "interrupt-parent", - "phy-handle", - "msi-parent", - "memory-region", - "syscon", - "regmap", - "iommus", - "interconnects", - "nvmem-cells", - "sound-dai", - "pinctrl-0", - "pinctrl-1", - "pinctrl-2", - "pinctrl-3", - "pinctrl-4", - ]; - - /// Determine if it's a phandle property that requires handling - fn is_phandle_property(prop_name: &str) -> bool { - Self::PHANDLE_PROPERTIES.contains(&prop_name) - || prop_name.ends_with("-supply") - || prop_name == "gpios" - || prop_name.ends_with("-gpios") - || prop_name.ends_with("-gpio") - || (prop_name.contains("cells") && !prop_name.starts_with("#") && prop_name.len() >= 4) - } -} - -/// Find device dependencies -fn find_device_dependencies( - device_node_path: &str, - phandle_map: &BTreeMap)>, - node_cache: &BTreeMap>, // Add node_cache parameter -) -> Vec { - let mut dependencies = Vec::new(); - - // Directly find nodes from node_cache, avoiding traversing all nodes - if let Some(nodes) = node_cache.get(device_node_path) { - // Traverse all properties of nodes to find dependencies - for node in nodes { - for prop in node.properties() { - // Determine if it's a phandle property that needs to be processed - if DevicePropertyClassifier::is_phandle_property(prop.name) { - let mut prop_deps = - parse_phandle_property(prop.raw_value(), prop.name, phandle_map); - dependencies.append(&mut prop_deps); - } - } - } - } - - dependencies -} - -/// Get all descendant nodes based on parent node path (including child nodes, grandchild nodes, etc.) -/// Find all descendant nodes by looking up nodes with parent node path as prefix in node_cache -fn get_descendant_nodes_by_path<'a>( - node_cache: &'a BTreeMap>, - parent_path: &str, -) -> Vec { - let mut descendant_paths = Vec::new(); - - // Special handling if parent path is root path - let search_prefix = if parent_path == "/" { - "/".to_string() - } else { - parent_path.to_string() + "/" - }; - - // Traverse node_cache, find all nodes with parent path as prefix - for path in node_cache.keys() { - // Check if path has parent path as prefix (and is not the parent path itself) - if path.starts_with(&search_prefix) && path.len() > search_prefix.len() { - // This is a descendant node path, add to results - descendant_paths.push(path.clone()); - } - } - - descendant_paths -} - -fn print_fdt(fdt: &Fdt) { - debug!("FDT Structure:"); - for node in fdt.all_nodes() { - let indent = " ".repeat(node.level().saturating_sub(1)); - debug!("{}Node: {}", indent, node.name()); - for prop in node.properties() { - debug!( - "{} Property: {} = {:?}", - indent, - prop.name, - prop.raw_value() - ); - } - } -} diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index 8afd84c..5d9f820 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -1,35 +1,26 @@ use alloc::vec::Vec; +use fdt_edit::{Fdt, Status}; -pub(crate) fn fdt_edit() -> Option { +pub(crate) fn fdt_edit() -> Option { let addr = axhal::get_bootarg(); if addr == 0 { return None; } - let fdt = unsafe { fdt_edit::Fdt::from_ptr(addr as *mut u8).ok()? }; - Some(fdt) -} - -pub(crate) fn fdt() -> Option { - let addr = axhal::get_bootarg(); - if addr == 0 { - return None; - } - let fdt = unsafe { fdt_parser::Fdt::from_ptr(addr as *mut u8).ok()? }; + let fdt = unsafe { Fdt::from_ptr(addr as *mut u8).ok()? }; Some(fdt) } pub fn cpu_list() -> Option> { - let fdt = fdt()?; + let fdt = fdt_edit()?; - let nodes = fdt.find_nodes("/cpus/cpu"); - let cpus = nodes - .into_iter() + let cpus = fdt + .find_by_path("/cpus/cpu") .filter(|node| node.name().contains("cpu@")) - .filter(|node| !matches!(node.status(), Some(fdt_parser::Status::Disabled))) + .filter(|node| !matches!(node.status(), Some(Status::Disabled))) .map(|node| { let reg = node - .reg() - .unwrap_or_else(|_| panic!("cpu {} reg not found", node.name()))[0]; + .regs() + .unwrap_or_else(|| panic!("cpu {} reg not found", node.name()))[0]; reg.address as usize }) .collect(); From 9925b659b6310bb18f3701cbdcb5ed44752f8769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 15 Dec 2025 13:38:36 +0800 Subject: [PATCH 43/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E5=88=9D=E5=A7=8B=E5=8C=96=E5=92=8C=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E7=A9=BA=E9=97=B4=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E5=86=85=E5=AD=98=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 + src/arch/aarch64/vm/init.rs | 195 ++++++++++++++++++++++++++ src/arch/aarch64/{vm.rs => vm/mod.rs} | 186 +----------------------- src/vm/addrspace.rs | 22 +++ src/vm/data.rs | 8 +- src/vm/mod.rs | 1 + 6 files changed, 231 insertions(+), 183 deletions(-) create mode 100644 src/arch/aarch64/vm/init.rs rename src/arch/aarch64/{vm.rs => vm/mod.rs} (58%) create mode 100644 src/vm/addrspace.rs diff --git a/Cargo.toml b/Cargo.toml index 43e2fcc..0ec5460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ log = "0.4" spin = "0.10" thiserror = {version = "2", default-features = false} timer_list = "0.1" +ranges-ext.workspace = true # System independent crates provided by ArceOS. axerrno = "0.1.0" @@ -31,6 +32,7 @@ percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true vm-fdt.workspace = true + # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" # axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} diff --git a/src/arch/aarch64/vm/init.rs b/src/arch/aarch64/vm/init.rs new file mode 100644 index 0000000..5902efa --- /dev/null +++ b/src/arch/aarch64/vm/init.rs @@ -0,0 +1,195 @@ +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::{ + os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, + string::String, + sync::Arc, + vec::Vec, +}; + +use arm_vcpu::Aarch64VCpuSetupConfig; + +use crate::{ + GuestPhysAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, + VmStatusStoppingOps, + arch::{VmStatusRunning, cpu::VCpu}, + config::{AxVMConfig, MemoryKind}, + vm::{MappingFlags, VmId}, +}; + +pub struct VmInit { + pub id: VmId, + pub name: String, + pt_levels: usize, + stop_requested: AtomicBool, + run_data: Option, +} + +impl VmInit { + /// Creates a new VM with the given configuration + pub fn new(config: &AxVMConfig) -> anyhow::Result { + let vm = Self { + id: config.id().into(), + name: config.name(), + pt_levels: 4, + stop_requested: AtomicBool::new(false), + run_data: None, + }; + Ok(vm) + } + + /// Initializes the VM, creating vCPUs and setting up memory + pub fn init(&mut self, config: AxVMConfig) -> anyhow::Result<()> { + debug!("Initializing VM {} ({})", self.id, self.name); + + // Create vCPUs + let mut vcpus = Vec::new(); + + let dtb_addr = GuestPhysAddr::from_usize(0); + + match config.cpu_num { + crate::config::CpuNumType::Alloc(num) => { + for _ in 0..num { + let vcpu = VCpu::new(None, dtb_addr)?; + debug!("Created vCPU with {:?}", vcpu.id); + vcpus.push(vcpu); + } + } + crate::config::CpuNumType::Fixed(ref ids) => { + for id in ids { + let vcpu = VCpu::new(Some(*id), dtb_addr)?; + debug!("Created vCPU with {:?}", vcpu.id); + vcpus.push(vcpu); + } + } + } + + let vcpu_count = vcpus.len(); + + for vcpu in &vcpus { + let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); + if max_levels < self.pt_levels { + self.pt_levels = max_levels; + } + } + + debug!( + "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", + self.id, self.name, vcpu_count, self.pt_levels + ); + + let mut run_data = VmStatusRunning { + vcpus, + data: VmData::new(self.pt_levels)?, + dtb_addr: GuestPhysAddr::from_usize(0), + vcpu_running_count: Arc::new(AtomicUsize::new(0)), + }; + + debug!("Mapping memory regions for VM {} ({})", self.id, self.name); + for memory_cfg in &config.memory_regions { + use crate::vm::MappingFlags; + let m = run_data.data.new_memory( + memory_cfg, + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::USER, + ); + run_data.data.add_memory(m); + } + + run_data.data.load_kernel_image(&config)?; + run_data.make_dtb(&config)?; + + let kernel_entry = run_data.data.kernel_entry(); + let gpt_root = run_data.data.gpt_root(); + + // Setup vCPUs + for vcpu in &mut run_data.vcpus { + vcpu.vcpu.set_entry(kernel_entry).unwrap(); + vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); + + let setup_config = Aarch64VCpuSetupConfig { + passthrough_interrupt: config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + passthrough_timer: config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + }; + + vcpu.vcpu + .setup(setup_config) + .map_err(|e| anyhow::anyhow!("Failed to setup vCPU : {e:?}"))?; + + // Set EPT root + vcpu.vcpu + .set_ept_root(gpt_root) + .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; + + run_data.vcpu_running_count.fetch_add(1, Ordering::SeqCst); + } + + self.run_data = Some(run_data); + + Ok(()) + } +} + +impl VmStatusInitOps for VmInit { + type Running = VmStatusRunning; + + fn id(&self) -> VmId { + self.id + } + + fn name(&self) -> &str { + &self.name + } + + fn start(self) -> Result { + let mut data = self.run_data.unwrap(); + + let mut vcpus = vec![]; + + vcpus.append(&mut data.vcpus); + let mut vcpu_handles = vec![]; + let vm_id = self.id; + + for mut vcpu in vcpus.into_iter() { + let vcpu_id = vcpu.id; + let vcpu_running_count = data.vcpu_running_count.clone(); + let bind_id = vcpu.binded_cpu_id(); + let handle = std::thread::Builder::new() + .name(format!("{vm_id}-{vcpu_id}")) + .stack_size(TASK_STACK_SIZE) + .spawn(move || { + assert!( + set_current_affinity(AxCpuMask::one_shot(bind_id.raw())), + "Initialize CPU affinity failed!" + ); + match vcpu.run() { + Ok(()) => { + info!("vCPU {} of VM {} exited normally", vcpu_id, vm_id); + } + Err(e) => { + error!( + "vCPU {} of VM {} exited with error: {:?}", + vcpu_id, vm_id, e + ); + } + } + vcpu_running_count.fetch_sub(1, Ordering::SeqCst); + vcpu + }) + .unwrap(); + + vcpu_handles.push(handle); + } + + info!( + "VM {} ({}) with {} cpus booted successfully.", + self.id, + self.name, + vcpu_handles.len() + ); + Ok(data) + } +} diff --git a/src/arch/aarch64/vm.rs b/src/arch/aarch64/vm/mod.rs similarity index 58% rename from src/arch/aarch64/vm.rs rename to src/arch/aarch64/vm/mod.rs index a0060ba..e238eb2 100644 --- a/src/arch/aarch64/vm.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -9,6 +9,8 @@ use arm_vcpu::Aarch64VCpuSetupConfig; use fdt_edit::{Node, NodeRef, Property, RegInfo}; use memory_addr::{MemoryAddr, align_down_4k, align_up_4k}; +mod init; + use crate::{ GuestPhysAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, VmStatusStoppingOps, @@ -17,191 +19,11 @@ use crate::{ vm::{MappingFlags, VmId}, }; +pub use init::VmInit; + const VM_ASPACE_BASE: usize = 0x0; const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; -/// AArch64 Virtual Machine implementation -pub struct VmInit { - pub id: VmId, - pub name: String, - pt_levels: usize, - stop_requested: AtomicBool, - exit_code: AtomicUsize, - run_data: Option, -} - -impl VmInit { - /// Creates a new VM with the given configuration - pub fn new(config: &AxVMConfig) -> anyhow::Result { - let vm = Self { - id: config.id().into(), - name: config.name(), - pt_levels: 4, - stop_requested: AtomicBool::new(false), - exit_code: AtomicUsize::new(0), - run_data: None, - }; - Ok(vm) - } - - /// Initializes the VM, creating vCPUs and setting up memory - pub fn init(&mut self, config: AxVMConfig) -> anyhow::Result<()> { - debug!("Initializing VM {} ({})", self.id, self.name); - - // Create vCPUs - let mut vcpus = Vec::new(); - - let dtb_addr = GuestPhysAddr::from_usize(0); - - match config.cpu_num { - crate::config::CpuNumType::Alloc(num) => { - for _ in 0..num { - let vcpu = VCpu::new(None, dtb_addr)?; - debug!("Created vCPU with {:?}", vcpu.id); - vcpus.push(vcpu); - } - } - crate::config::CpuNumType::Fixed(ref ids) => { - for id in ids { - let vcpu = VCpu::new(Some(*id), dtb_addr)?; - debug!("Created vCPU with {:?}", vcpu.id); - vcpus.push(vcpu); - } - } - } - - let vcpu_count = vcpus.len(); - - for vcpu in &vcpus { - let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); - if max_levels < self.pt_levels { - self.pt_levels = max_levels; - } - } - - debug!( - "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", - self.id, self.name, vcpu_count, self.pt_levels - ); - - let mut run_data = VmStatusRunning { - vcpus, - data: VmData::new(self.pt_levels)?, - dtb_addr: GuestPhysAddr::from_usize(0), - vcpu_running_count: Arc::new(AtomicUsize::new(0)), - }; - - debug!("Mapping memory regions for VM {} ({})", self.id, self.name); - for memory_cfg in &config.memory_regions { - use crate::vm::MappingFlags; - let m = run_data.data.new_memory( - memory_cfg, - MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::EXECUTE - | MappingFlags::USER, - ); - run_data.data.add_memory(m); - } - - run_data.data.load_kernel_image(&config)?; - - run_data.make_dtb(&config)?; - - let kernel_entry = run_data.data.kernel_entry(); - let gpt_root = run_data.data.gpt_root(); - - // Setup vCPUs - for vcpu in &mut run_data.vcpus { - vcpu.vcpu.set_entry(kernel_entry).unwrap(); - vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); - - let setup_config = Aarch64VCpuSetupConfig { - passthrough_interrupt: config.interrupt_mode() - == axvmconfig::VMInterruptMode::Passthrough, - passthrough_timer: config.interrupt_mode() - == axvmconfig::VMInterruptMode::Passthrough, - }; - - vcpu.vcpu - .setup(setup_config) - .map_err(|e| anyhow::anyhow!("Failed to setup vCPU : {e:?}"))?; - - // Set EPT root - vcpu.vcpu - .set_ept_root(gpt_root) - .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; - - run_data.vcpu_running_count.fetch_add(1, Ordering::SeqCst); - } - - self.run_data = Some(run_data); - - Ok(()) - } -} - -impl VmStatusInitOps for VmInit { - type Running = VmStatusRunning; - - fn id(&self) -> VmId { - self.id - } - - fn name(&self) -> &str { - &self.name - } - - fn start(self) -> Result { - let mut data = self.run_data.unwrap(); - - let mut vcpus = vec![]; - - vcpus.append(&mut data.vcpus); - let mut vcpu_handles = vec![]; - let vm_id = self.id; - - for mut vcpu in vcpus.into_iter() { - let vcpu_id = vcpu.id; - let vcpu_running_count = data.vcpu_running_count.clone(); - let bind_id = vcpu.binded_cpu_id(); - let handle = std::thread::Builder::new() - .name(format!("{vm_id}-{vcpu_id}")) - .stack_size(TASK_STACK_SIZE) - .spawn(move || { - assert!( - set_current_affinity(AxCpuMask::one_shot(bind_id.raw())), - "Initialize CPU affinity failed!" - ); - match vcpu.run() { - Ok(()) => { - info!("vCPU {} of VM {} exited normally", vcpu_id, vm_id); - } - Err(e) => { - error!( - "vCPU {} of VM {} exited with error: {:?}", - vcpu_id, vm_id, e - ); - } - } - vcpu_running_count.fetch_sub(1, Ordering::SeqCst); - vcpu - }) - .unwrap(); - - vcpu_handles.push(handle); - } - - info!( - "VM {} ({}) with {} cpus booted successfully.", - self.id, - self.name, - vcpu_handles.len() - ); - Ok(data) - } -} - impl VmStatusRunningOps for VmStatusRunning { type Stopping = VmStatusStopping; diff --git a/src/vm/addrspace.rs b/src/vm/addrspace.rs new file mode 100644 index 0000000..747fbcc --- /dev/null +++ b/src/vm/addrspace.rs @@ -0,0 +1,22 @@ +use core::alloc::Layout; + +type AddrSpaceRaw = axaddrspace::AddrSpace; + +pub struct VmAddrSpace { + pub(crate) addrspace: AddrSpaceRaw, +} + +#[derive(Debug, Clone)] +pub struct VmRegion { + pub gpa: GuestPhysAddr, + pub hva: HostVirtAddr, + pub layout: Layout, + pub kind: VmRegionKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VmRegionKind { + Passthrough, +} + +impl VmRegion {} diff --git a/src/vm/data.rs b/src/vm/data.rs index c1883e9..eeafe21 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -1,4 +1,4 @@ -use core::alloc::Layout; +use core::{alloc::Layout, any}; use std::{ sync::{Arc, Mutex}, vec::Vec, @@ -171,6 +171,10 @@ impl VmData { .map(|m| (m.gpa(), m.size())) .collect() } + + pub fn map_passthrough_regions(&self) -> anyhow::Result<()> { + Ok(()) + } } #[derive(Default)] @@ -181,6 +185,8 @@ struct SharedData { kernel_entry: GuestPhysAddr, } +impl SharedData {} + pub struct GuestMemory { gpa: GuestPhysAddr, hva: HostVirtAddr, diff --git a/src/vm/mod.rs b/src/vm/mod.rs index ebef17f..be480c3 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -8,6 +8,7 @@ use crate::{AxVMConfig, arch::VmInit}; mod data; mod machine; +// mod addrspace; pub(crate) use data::*; use machine::*; From 47d5d054ebde986868219ea2b4aa4831735888a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 15 Dec 2025 14:11:32 +0800 Subject: [PATCH 44/74] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E5=88=9D=E5=A7=8B=E5=8C=96=E5=92=8C=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E7=A9=BA=E9=97=B4=E7=AE=A1=E7=90=86=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20vCPU=20=E5=88=9B=E5=BB=BA=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=92=8C=E5=86=85=E5=AD=98=E6=98=A0=E5=B0=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/init.rs | 88 ++++++++++++++++++++----------------- src/arch/aarch64/vm/mod.rs | 11 ++++- src/vm/addrspace.rs | 39 +++++++++++++--- src/vm/data.rs | 29 ++++++++---- src/vm/mod.rs | 2 +- 5 files changed, 111 insertions(+), 58 deletions(-) diff --git a/src/arch/aarch64/vm/init.rs b/src/arch/aarch64/vm/init.rs index 5902efa..9e15f0e 100644 --- a/src/arch/aarch64/vm/init.rs +++ b/src/arch/aarch64/vm/init.rs @@ -16,6 +16,11 @@ use crate::{ vm::{MappingFlags, VmId}, }; +const VM_ASPACE_BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0); +const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; +const VM_ASPACE_END: GuestPhysAddr = + GuestPhysAddr::from_usize(VM_ASPACE_BASE.as_usize() + VM_ASPACE_SIZE); + pub struct VmInit { pub id: VmId, pub name: String, @@ -41,48 +46,12 @@ impl VmInit { pub fn init(&mut self, config: AxVMConfig) -> anyhow::Result<()> { debug!("Initializing VM {} ({})", self.id, self.name); - // Create vCPUs - let mut vcpus = Vec::new(); - - let dtb_addr = GuestPhysAddr::from_usize(0); - - match config.cpu_num { - crate::config::CpuNumType::Alloc(num) => { - for _ in 0..num { - let vcpu = VCpu::new(None, dtb_addr)?; - debug!("Created vCPU with {:?}", vcpu.id); - vcpus.push(vcpu); - } - } - crate::config::CpuNumType::Fixed(ref ids) => { - for id in ids { - let vcpu = VCpu::new(Some(*id), dtb_addr)?; - debug!("Created vCPU with {:?}", vcpu.id); - vcpus.push(vcpu); - } - } - } - - let vcpu_count = vcpus.len(); + let vcpus = self.new_vcpus(&config)?; - for vcpu in &vcpus { - let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); - if max_levels < self.pt_levels { - self.pt_levels = max_levels; - } - } - - debug!( - "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", - self.id, self.name, vcpu_count, self.pt_levels - ); - - let mut run_data = VmStatusRunning { + let mut run_data = VmStatusRunning::new( + VmData::new(self.pt_levels, VM_ASPACE_BASE..VM_ASPACE_END)?, vcpus, - data: VmData::new(self.pt_levels)?, - dtb_addr: GuestPhysAddr::from_usize(0), - vcpu_running_count: Arc::new(AtomicUsize::new(0)), - }; + ); debug!("Mapping memory regions for VM {} ({})", self.id, self.name); for memory_cfg in &config.memory_regions { @@ -131,6 +100,45 @@ impl VmInit { Ok(()) } + + fn new_vcpus(&mut self, config: &AxVMConfig) -> anyhow::Result> { + // Create vCPUs + let mut vcpus = Vec::new(); + + let dtb_addr = GuestPhysAddr::from_usize(0); + + match config.cpu_num { + crate::config::CpuNumType::Alloc(num) => { + for _ in 0..num { + let vcpu = VCpu::new(None, dtb_addr)?; + debug!("Created vCPU with {:?}", vcpu.id); + vcpus.push(vcpu); + } + } + crate::config::CpuNumType::Fixed(ref ids) => { + for id in ids { + let vcpu = VCpu::new(Some(*id), dtb_addr)?; + debug!("Created vCPU with {:?}", vcpu.id); + vcpus.push(vcpu); + } + } + } + + let vcpu_count = vcpus.len(); + + for vcpu in &vcpus { + let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); + if max_levels < self.pt_levels { + self.pt_levels = max_levels; + } + } + + debug!( + "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", + self.id, self.name, vcpu_count, self.pt_levels + ); + Ok(vcpus) + } } impl VmStatusInitOps for VmInit { diff --git a/src/arch/aarch64/vm/mod.rs b/src/arch/aarch64/vm/mod.rs index e238eb2..81547bd 100644 --- a/src/arch/aarch64/vm/mod.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -12,7 +12,7 @@ use memory_addr::{MemoryAddr, align_down_4k, align_up_4k}; mod init; use crate::{ - GuestPhysAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, + GuestPhysAddr, RunError, TASK_STACK_SIZE, Vm, VmData, VmStatusInitOps, VmStatusRunningOps, VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, @@ -56,6 +56,15 @@ pub struct VmStatusRunning { } impl VmStatusRunning { + pub(crate) fn new(data: VmData, vcpus: Vec) -> Self { + Self { + vcpus, + data, + dtb_addr: GuestPhysAddr::from_usize(0), + vcpu_running_count: Arc::new(AtomicUsize::new(0)), + } + } + fn make_dtb(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { let flags = MappingFlags::READ | MappingFlags::WRITE | MappingFlags::WRITE | MappingFlags::USER; diff --git a/src/vm/addrspace.rs b/src/vm/addrspace.rs index 747fbcc..7e7bb15 100644 --- a/src/vm/addrspace.rs +++ b/src/vm/addrspace.rs @@ -1,22 +1,47 @@ use core::alloc::Layout; -type AddrSpaceRaw = axaddrspace::AddrSpace; +use ranges_ext::RangeInfo; -pub struct VmAddrSpace { - pub(crate) addrspace: AddrSpaceRaw, -} +use crate::GuestPhysAddr; + +pub(crate) type AddrSpace = axaddrspace::AddrSpace; +pub(crate) type VmRegionMap = ranges_ext::RangeSetAlloc; #[derive(Debug, Clone)] pub struct VmRegion { pub gpa: GuestPhysAddr, - pub hva: HostVirtAddr, - pub layout: Layout, + pub size: usize, pub kind: VmRegionKind, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VmRegionKind { Passthrough, + Memory, } -impl VmRegion {} +impl RangeInfo for VmRegion { + type Kind = VmRegionKind; + + type Type = GuestPhysAddr; + + fn range(&self) -> core::ops::Range { + self.gpa..GuestPhysAddr::from_usize(self.gpa.as_usize() + self.size) + } + + fn kind(&self) -> &Self::Kind { + &self.kind + } + + fn overwritable(&self) -> bool { + matches!(self.kind, VmRegionKind::Passthrough) + } + + fn clone_with_range(&self, range: core::ops::Range) -> Self { + VmRegion { + gpa: range.start, + size: range.end.as_usize() - range.start.as_usize(), + kind: self.kind, + } + } +} diff --git a/src/vm/data.rs b/src/vm/data.rs index eeafe21..a108c74 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -1,4 +1,4 @@ -use core::{alloc::Layout, any}; +use core::{alloc::Layout, ops::Range}; use std::{ sync::{Arc, Mutex}, vec::Vec, @@ -7,18 +7,17 @@ use std::{ pub use axaddrspace::MappingFlags; use memory_addr::MemoryAddr; -use crate::vhal::ArchHal; use crate::{ AxVMConfig, GuestPhysAddr, HostPhysAddr, HostVirtAddr, config::MemoryKind, vhal::{phys_to_virt, virt_to_phys}, + vm::addrspace::{VmRegion, VmRegionKind}, }; +use crate::{vhal::ArchHal, vm::addrspace::VmRegionMap}; -const VM_ASPACE_BASE: usize = 0x0; -const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; const ALIGN: usize = 1024 * 1024 * 2; -type AddrSpace = axaddrspace::AddrSpace; +use super::addrspace::AddrSpace; #[derive(Clone)] pub struct VmData { @@ -27,17 +26,28 @@ pub struct VmData { } impl VmData { - pub fn new(gpt_levels: usize) -> anyhow::Result { + pub fn new(gpt_levels: usize, vm_addr_space: Range) -> anyhow::Result { + let mut memory_map = VmRegionMap::new(Vec::new()); + let vm_space_size = vm_addr_space.end.as_usize() - vm_addr_space.start.as_usize(); + memory_map.add(VmRegion { + gpa: vm_addr_space.start, + size: vm_space_size, + kind: VmRegionKind::Passthrough, + }); + // Create address space for the VM let address_space = AddrSpace::new_empty( gpt_levels, - axaddrspace::GuestPhysAddr::from(VM_ASPACE_BASE), - VM_ASPACE_SIZE, + vm_addr_space.start.as_usize().into(), + vm_space_size, ) .map_err(|e| anyhow!("Failed to create address space: {e:?}"))?; Ok(Self { addrspace: Arc::new(Mutex::new(address_space)), - shared: Arc::new(Mutex::new(SharedData::default())), + shared: Arc::new(Mutex::new(SharedData { + memory_map, + ..Default::default() + })), }) } @@ -183,6 +193,7 @@ struct SharedData { reserved_memories: Vec, kernel_region_index: usize, kernel_entry: GuestPhysAddr, + memory_map: VmRegionMap, } impl SharedData {} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index be480c3..d4cbfc7 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -8,7 +8,7 @@ use crate::{AxVMConfig, arch::VmInit}; mod data; mod machine; -// mod addrspace; +mod addrspace; pub(crate) use data::*; use machine::*; From f92d4ebf5e9c5d6c9c1026b2d2528ebc6574ac38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 15 Dec 2025 14:29:55 +0800 Subject: [PATCH 45/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=98=A0=E5=B0=84=E5=8C=BA=E5=9F=9F=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E7=9B=B4=E9=80=9A=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/init.rs | 2 ++ src/arch/aarch64/vm/mod.rs | 30 ++++++++++++++--------------- src/vm/data.rs | 38 +++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/arch/aarch64/vm/init.rs b/src/arch/aarch64/vm/init.rs index 9e15f0e..d8101fd 100644 --- a/src/arch/aarch64/vm/init.rs +++ b/src/arch/aarch64/vm/init.rs @@ -69,6 +69,8 @@ impl VmInit { run_data.data.load_kernel_image(&config)?; run_data.make_dtb(&config)?; + run_data.data.map_passthrough_regions()?; + let kernel_entry = run_data.data.kernel_entry(); let gpt_root = run_data.data.gpt_root(); diff --git a/src/arch/aarch64/vm/mod.rs b/src/arch/aarch64/vm/mod.rs index 81547bd..4c778d2 100644 --- a/src/arch/aarch64/vm/mod.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -191,21 +191,21 @@ impl VmStatusRunning { }, ); - for (gpa, len, name) in &pt_dev_region { - self.data - .addrspace - .lock() - .map_linear( - (*gpa).into(), - (*gpa).into(), - *len, - MappingFlags::DEVICE - | MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::USER, - ) - .map_err(|e| anyhow!("`{name}` map [{:#x}, {:#x}) fail:\n {e}", *gpa, len))?; - } + // for (gpa, len, name) in &pt_dev_region { + // self.data + // .addrspace + // .lock() + // .map_linear( + // (*gpa).into(), + // (*gpa).into(), + // *len, + // MappingFlags::DEVICE + // | MappingFlags::READ + // | MappingFlags::WRITE + // | MappingFlags::USER, + // ) + // .map_err(|e| anyhow!("`{name}` map [{:#x}, {:#x}) fail:\n {e}", *gpa, len))?; + // } let mut guest_mem = self.data.memories().into_iter().next().unwrap(); let mut dtb_start = diff --git a/src/vm/data.rs b/src/vm/data.rs index a108c74..b1b8902 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -60,6 +60,12 @@ impl VmData { self.shared.lock().reserved_memories.push(r); } + pub(crate) fn memory_map_add_region(&self, region: VmRegion) -> anyhow::Result<()> { + let mut s = self.shared.lock(); + s.memory_map.add(region).unwrap(); + Ok(()) + } + pub fn new_memory(&self, kind: &MemoryKind, flags: MappingFlags) -> GuestMemory { let _gpa; let _size; @@ -103,6 +109,13 @@ impl VmData { } } + self.memory_map_add_region(VmRegion { + gpa: _gpa, + size: _size, + kind: VmRegionKind::Memory, + }) + .unwrap(); + GuestMemory { gpa: _gpa, hva, @@ -183,6 +196,31 @@ impl VmData { } pub fn map_passthrough_regions(&self) -> anyhow::Result<()> { + let s = self.shared.lock(); + let mut g = self.addrspace.lock(); + for region in s + .memory_map + .iter() + .filter(|m| m.kind == VmRegionKind::Passthrough) + { + g.map_linear( + region.gpa.as_usize().into(), + region.gpa.as_usize().into(), + region.size.align_up_4k(), + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::USER, + ) + .map_err(|e| { + anyhow!( + "Failed to map passthrough region: [{:?}, {:?})\n {e:?}", + region.gpa, + region.gpa + region.size + ) + })?; + } + Ok(()) } } From 8902a286bcda286053093086038b3037cd58e352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 15 Dec 2025 16:57:27 +0800 Subject: [PATCH 46/74] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E7=9A=84=204-level-ept=20=E7=89=B9=E6=80=A7=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20CPU=20=E5=88=86=E9=85=8D=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=E8=AE=BE=E5=A4=87=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E6=A0=87=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 - src/arch/aarch64/vm/mod.rs | 94 ++++++++------------------------------ src/vhal/cpu.rs | 2 +- src/vm/data.rs | 1 + 4 files changed, 21 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0ec5460..b695f17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ name = "axvm" version = "0.1.0" [features] -4-level-ept = [] default = ["vmx"] vmx = [] # Note: 4-level-ept support is now provided through dynamic page table selection in axaddrspace diff --git a/src/arch/aarch64/vm/mod.rs b/src/arch/aarch64/vm/mod.rs index 4c778d2..bf2a405 100644 --- a/src/arch/aarch64/vm/mod.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -16,6 +16,7 @@ use crate::{ VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, + vhal::cpu::CpuHardId, vm::{MappingFlags, VmId}, }; @@ -100,6 +101,24 @@ impl VmStatusRunning { config.name() ); let mut fdt = crate::fdt::fdt_edit().expect("Need fdt"); + + let mut rm_nodes = vec![]; + let vcpu_hard_ls = self.vcpus.iter().map(|v| v.id).collect::>(); + for cpu in fdt.find_by_path("/cpus/cpu") { + if let Some(id) = cpu.regs() { + let id = CpuHardId::new(id[0].address as usize); + if vcpu_hard_ls.contains(&id) { + continue; + } + } + + rm_nodes.push(cpu.path()); + } + + for path in rm_nodes { + fdt.remove_node(&path).unwrap(); + } + let nodes = fdt .find_by_path("/memory") .into_iter() @@ -109,46 +128,6 @@ impl VmStatusRunning { let _ = fdt.remove_node(&path); } - let mut pt_dev_region = vec![]; - - for node in fdt.all_nodes() { - if matches!(node.status(), Some(fdt_edit::Status::Disabled)) { - continue; - } - let name = node.name().to_string(); - - if let Some(regs) = node.regs() { - for reg in regs { - if let Some(size) = reg.size - && size > 0 - { - // Align the base address and length to 4K boundaries. - pt_dev_region.push(( - align_down_4k(reg.address as _), - align_up_4k(size as _), - name.clone(), - )); - } - } - } - - if let NodeRef::Pci(pci) = &node - && let Some(ranges) = pci.ranges() - { - for range in ranges { - if range.size > 0 { - // Align the base address and length to 4K boundaries. - pt_dev_region.push(( - align_down_4k(range.cpu_address as _), - align_up_4k(range.size as _), - name.clone(), - )); - } - } - } - } - pt_dev_region.sort_by_key(|(gpa, ..)| *gpa); - let root_address_cells = fdt.root().address_cells().unwrap_or(2); let root_size_cells = fdt.root().size_cells().unwrap_or(2); @@ -172,41 +151,6 @@ impl VmStatusRunning { let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); debug!("Generated DTB:\n{f}"); - // Merge overlapping regions. - let pt_dev_region = pt_dev_region.into_iter().fold( - Vec::<(usize, usize, String)>::new(), - |mut acc, (gpa, len, name)| { - if let Some(last) = acc.last_mut() { - let last_name = last.2.clone(); - if last.0 + last.1 >= gpa { - // Merge with the last region. - last.1 = (last.0 + last.1).max(gpa + len) - last.0; - } else { - acc.push((gpa, len, last_name)); - } - } else { - acc.push((gpa, len, name)); - } - acc - }, - ); - - // for (gpa, len, name) in &pt_dev_region { - // self.data - // .addrspace - // .lock() - // .map_linear( - // (*gpa).into(), - // (*gpa).into(), - // *len, - // MappingFlags::DEVICE - // | MappingFlags::READ - // | MappingFlags::WRITE - // | MappingFlags::USER, - // ) - // .map_err(|e| anyhow!("`{name}` map [{:#x}, {:#x}) fail:\n {e}", *gpa, len))?; - // } - let mut guest_mem = self.data.memories().into_iter().next().unwrap(); let mut dtb_start = (guest_mem.0.as_usize() + guest_mem.1.min(512 * 1024 * 1024)) - dtb_data.len(); diff --git a/src/vhal/cpu.rs b/src/vhal/cpu.rs index fb44e90..87816a0 100644 --- a/src/vhal/cpu.rs +++ b/src/vhal/cpu.rs @@ -23,7 +23,7 @@ impl HCpuExclusive { match id { Some(id) => { // Try to allocate the specific ID - let raw = a.alloc_contiguous(Some(id.raw()), 1, 1)?; + let raw = a.alloc_contiguous(Some(id.raw()), 1, 0)?; Some(HCpuExclusive(CpuId::new(raw))) } None => { diff --git a/src/vm/data.rs b/src/vm/data.rs index b1b8902..5303b2c 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -210,6 +210,7 @@ impl VmData { MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE + | MappingFlags::DEVICE | MappingFlags::USER, ) .map_err(|e| { From f4eea00fc46278fa1d57a25f5968ee3f10ade102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 18 Dec 2025 13:50:41 +0800 Subject: [PATCH 47/74] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=90=AF=E5=8A=A8=E5=8F=82=E6=95=B0=E7=9A=84=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E8=B0=83=E7=94=A8=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E8=8E=B7=E5=8F=96=E8=AE=BE=E5=A4=87=E6=A0=91=E5=9C=B0?= =?UTF-8?q?=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fdt/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index 5d9f820..daecd7f 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use fdt_edit::{Fdt, Status}; pub(crate) fn fdt_edit() -> Option { - let addr = axhal::get_bootarg(); + let addr = axhal::dtb::get_bootarg(); if addr == 0 { return None; } From 8b64d8ac3c7945e8921523ee04c05b29b4ef0a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 18 Dec 2025 15:20:18 +0800 Subject: [PATCH 48/74] fmt code --- src/arch/aarch64/hal.rs | 47 +++++++++++++++++++++++++++++++++++++ src/arch/aarch64/mod.rs | 47 ++----------------------------------- src/arch/aarch64/vm/init.rs | 6 ++--- src/arch/aarch64/vm/mod.rs | 8 ++----- src/lib.rs | 3 --- src/vm/data.rs | 2 +- 6 files changed, 54 insertions(+), 59 deletions(-) create mode 100644 src/arch/aarch64/hal.rs diff --git a/src/arch/aarch64/hal.rs b/src/arch/aarch64/hal.rs new file mode 100644 index 0000000..4c7c316 --- /dev/null +++ b/src/arch/aarch64/hal.rs @@ -0,0 +1,47 @@ +use alloc::vec::Vec; + +use aarch64_cpu::registers::*; +use aarch64_cpu_ext::cache::{CacheOp, dcache_range}; + +use crate::fdt; +use crate::vhal::{ + ArchHal, + cpu::{CpuHardId, CpuId}, +}; + +use super::cpu::{HCpu, VCpuHal}; + +pub struct Hal; + +impl ArchHal for Hal { + fn current_cpu_init(id: CpuId) -> anyhow::Result { + info!("Enabling virtualization on cpu {id}"); + let mut cpu = HCpu::new(id); + cpu.init()?; + info!("{cpu}"); + Ok(cpu) + } + + fn init() -> anyhow::Result<()> { + arm_vcpu::init_hal(&VCpuHal); + + Ok(()) + } + + fn cpu_list() -> Vec { + fdt::cpu_list() + .unwrap() + .into_iter() + .map(CpuHardId::new) + .collect() + } + + fn cpu_hard_id() -> CpuHardId { + let mpidr = MPIDR_EL1.get() as usize; + CpuHardId::new(mpidr) + } + + fn cache_flush(vaddr: arm_vcpu::HostVirtAddr, size: usize) { + dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); + } +} diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index e6767d7..58b5757 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,54 +1,11 @@ -use aarch64_cpu::registers::MPIDR_EL1; use aarch64_cpu_ext::cache::{CacheOp, dcache_range}; -use crate::alloc::vec::Vec; -use crate::fdt; -use crate::vhal::{ - ArchHal, - cpu::{CpuHardId, CpuId}, -}; - -use aarch64_cpu::registers::Readable; - pub mod cpu; +mod hal; mod vm; pub use cpu::HCpu; +pub use hal::Hal; pub use vm::*; type AddrSpace = axaddrspace::AddrSpace; - -pub struct Hal; - -impl ArchHal for Hal { - fn current_cpu_init(id: CpuId) -> anyhow::Result { - info!("Enabling virtualization on cpu {id}"); - let mut cpu = HCpu::new(id); - cpu.init()?; - info!("{cpu}"); - Ok(cpu) - } - - fn init() -> anyhow::Result<()> { - arm_vcpu::init_hal(&cpu::VCpuHal); - - Ok(()) - } - - fn cpu_list() -> Vec { - fdt::cpu_list() - .unwrap() - .into_iter() - .map(CpuHardId::new) - .collect() - } - - fn cpu_hard_id() -> CpuHardId { - let mpidr = MPIDR_EL1.get() as usize; - CpuHardId::new(mpidr) - } - - fn cache_flush(vaddr: arm_vcpu::HostVirtAddr, size: usize) { - dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); - } -} diff --git a/src/arch/aarch64/vm/init.rs b/src/arch/aarch64/vm/init.rs index d8101fd..bf16d68 100644 --- a/src/arch/aarch64/vm/init.rs +++ b/src/arch/aarch64/vm/init.rs @@ -2,17 +2,15 @@ use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{ os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, string::String, - sync::Arc, vec::Vec, }; use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, RunError, TASK_STACK_SIZE, VmData, VmStatusInitOps, VmStatusRunningOps, - VmStatusStoppingOps, + GuestPhysAddr, TASK_STACK_SIZE, VmData, VmStatusInitOps, arch::{VmStatusRunning, cpu::VCpu}, - config::{AxVMConfig, MemoryKind}, + config::AxVMConfig, vm::{MappingFlags, VmId}, }; diff --git a/src/arch/aarch64/vm/mod.rs b/src/arch/aarch64/vm/mod.rs index bf2a405..49cdf85 100644 --- a/src/arch/aarch64/vm/mod.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -1,13 +1,9 @@ use alloc::{string::String, sync::Arc, vec::Vec}; -use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::{ - os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, - string::ToString, -}; +use core::sync::atomic::{AtomicUsize, Ordering}; use arm_vcpu::Aarch64VCpuSetupConfig; use fdt_edit::{Node, NodeRef, Property, RegInfo}; -use memory_addr::{MemoryAddr, align_down_4k, align_up_4k}; +use memory_addr::MemoryAddr; mod init; diff --git a/src/lib.rs b/src/lib.rs index ad72f13..180cc3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,5 @@ #![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. //! diff --git a/src/vm/data.rs b/src/vm/data.rs index 5303b2c..42350cc 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -33,7 +33,7 @@ impl VmData { gpa: vm_addr_space.start, size: vm_space_size, kind: VmRegionKind::Passthrough, - }); + })?; // Create address space for the VM let address_space = AddrSpace::new_empty( From aff70e1621de9f7924e71196d315b0a15ed9623f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 18 Dec 2025 15:37:01 +0800 Subject: [PATCH 49/74] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E7=9A=84=20vcpu=20=E6=A8=A1=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E7=AE=80=E5=8C=96=20VM=20=E9=85=8D=E7=BD=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.rs | 115 ++++---------------------------------------------- src/lib.rs | 1 - src/vcpu.rs | 31 -------------- 3 files changed, 9 insertions(+), 138 deletions(-) delete mode 100644 src/vcpu.rs diff --git a/src/config.rs b/src/config.rs index aeebb26..eed9812 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,15 +13,6 @@ pub use axvmconfig::{ use crate::vhal::cpu::CpuId; -// /// 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, -// pub setup_config: as AxArchVCpu>::SetupConfig, -// } - /// A part of `AxVMConfig`, which represents a `VCpu`. #[derive(Clone, Copy, Debug, Default)] pub struct AxVCpuConfig { @@ -68,13 +59,7 @@ pub struct AxVMConfig { pub name: String, pub cpu_num: CpuNumType, pub image_config: VMImagesConfig, - pub emu_devices: Vec, - pub pass_through_devices: Vec, - pub excluded_devices: Vec>, - pub pass_through_addresses: Vec, pub memory_regions: Vec, - // TODO: improve interrupt passthrough - pub spi_list: Vec, pub interrupt_mode: VMInterruptMode, } @@ -84,44 +69,21 @@ pub enum CpuNumType { Fixed(Vec), } +impl CpuNumType { + pub fn num(&self) -> usize { + match self { + CpuNumType::Alloc(num) => *num, + CpuNumType::Fixed(ids) => ids.len(), + } + } +} + impl Default for CpuNumType { fn default() -> Self { CpuNumType::Alloc(1) } } -// impl From for AxVMConfig { -// fn from(cfg: AxVMCrateConfig) -> Self { -// Self { -// id: cfg.base.id, -// name: cfg.base.name, -// vm_type: VMType::from(cfg.base.vm_type), -// 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), -// }, -// image_config: VMImagesConfig { -// kernel_load_gpa: GuestPhysAddr::from(cfg.kernel.kernel_load_addr), -// bios_load_gpa: cfg.kernel.bios_load_addr.map(GuestPhysAddr::from), -// 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, -// 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, -// } -// } -// } - impl AxVMConfig { /// Returns VM id. pub fn id(&self) -> usize { @@ -138,65 +100,6 @@ impl AxVMConfig { &self.image_config } - pub fn excluded_devices(&self) -> &Vec> { - &self.excluded_devices - } - - 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 { - &self.emu_devices - } - - /// Returns configurations related to VM passthrough devices. - pub fn pass_through_devices(&self) -> &Vec { - &self.pass_through_devices - } - - /// Adds a new passthrough device to the VM configuration. - pub fn add_pass_through_device(&mut self, device: PassThroughDeviceConfig) { - 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); - } - - /// Returns the list of passthrough SPIs. - pub fn pass_through_spis(&self) -> &Vec { - &self.spi_list - } - /// Returns the interrupt mode of the VM. pub fn interrupt_mode(&self) -> VMInterruptMode { self.interrupt_mode diff --git a/src/lib.rs b/src/lib.rs index 180cc3e..9355d6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,6 @@ const TASK_STACK_SIZE: usize = 0x40000; // 256 KB pub(crate) mod arch; mod fdt; -mod vcpu; mod vm; pub mod config; diff --git a/src/vcpu.rs b/src/vcpu.rs deleted file mode 100644 index 95d970a..0000000 --- a/src/vcpu.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Architecture dependent vcpu implementations. - -// cfg_if::cfg_if! { -// if #[cfg(target_arch = "x86_64")] { -// pub use x86_vcpu::VmxArchVCpu as AxArchVCpuImpl; -// pub use x86_vcpu::VmxArchPerCpuState as AxVMArchPerCpuImpl; -// pub use x86_vcpu::has_hardware_support; -// pub type AxVCpuCreateConfig = (); - -// // Note: -// // According to the requirements of `x86_vcpu`, -// // users of the `x86_vcpu` crate need to implement the `PhysFrameIf` trait for it with the help of `crate_interface`. -// // -// // Since in our hypervisor architecture, `axvm` is not responsible for OS-related resource management, -// // we leave the `PhysFrameIf` implementation to `vmm_app`. -// } else if #[cfg(target_arch = "riscv64")] { -// pub use riscv_vcpu::RISCVVCpu as AxArchVCpuImpl; -// pub use riscv_vcpu::RISCVPerCpu as AxVMArchPerCpuImpl; -// pub use riscv_vcpu::RISCVVCpuCreateConfig as AxVCpuCreateConfig; -// pub use riscv_vcpu::has_hardware_support; -// } else if #[cfg(target_arch = "aarch64")] { -// pub use arm_vcpu::Aarch64VCpu as AxArchVCpuImpl; -// pub use arm_vcpu::Aarch64PerCpu as AxVMArchPerCpuImpl; -// -// pub use arm_vcpu::Aarch64VCpuCreateConfig as AxVCpuCreateConfig; -// pub use arm_vcpu::Aarch64VCpuSetupConfig as AxVCpuSetupConfig; -// pub use arm_vcpu::has_hardware_support; - -// pub use arm_vgic::vtimer::get_sysreg_device; -// } -// } From 70bd7a6dc442022f5f45e890afd616d3f1a073aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 10:54:00 +0800 Subject: [PATCH 50/74] update --- src/arch/aarch64/cpu.rs | 68 ++++---- src/arch/aarch64/vm/init.rs | 50 +++--- src/arch/aarch64/vm/mod.rs | 38 ++--- src/arch/aarch64/vm/unint.rs | 138 ++++++++++++++++ src/config.rs | 4 +- src/lib.rs | 1 + src/vcpu/mod.rs | 43 +++++ src/vm/data.rs | 53 ++++-- src/vm/data2.rs | 192 ++++++++++++++++++++++ src/vm/define.rs | 69 ++++++++ src/vm/machine.rs | 307 ++++++----------------------------- src/vm/mod.rs | 132 +++------------ 12 files changed, 619 insertions(+), 476 deletions(-) create mode 100644 src/arch/aarch64/vm/unint.rs create mode 100644 src/vcpu/mod.rs create mode 100644 src/vm/data2.rs create mode 100644 src/vm/define.rs diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index f9f4263..d3d744e 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -1,13 +1,17 @@ -use core::{fmt::Display, sync::atomic::AtomicBool}; +use core::{fmt::Display, ops::Deref, sync::atomic::AtomicBool}; use std::sync::Arc; use aarch64_cpu::registers::*; use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; use axvm_types::addr::*; -use crate::vhal::{ - ArchCpuData, - cpu::{CpuHardId, CpuId, HCpuExclusive}, +use crate::{ + data2::VmDataWeak, + vcpu::VCpuCommon, + vhal::{ + ArchCpuData, + cpu::{CpuHardId, CpuId, HCpuExclusive}, + }, }; pub struct HCpu { @@ -97,53 +101,34 @@ impl VCpuHandle { } pub struct VCpu { - pub id: CpuHardId, pub vcpu: arm_vcpu::Aarch64VCpu, - hcpu: HCpuExclusive, - handle: VCpuHandle, + common: VCpuCommon, } impl VCpu { - pub fn new(host_cpuid: Option, dtb_addr: GuestPhysAddr) -> anyhow::Result { - let hcpu_exclusive = HCpuExclusive::try_new(host_cpuid) - .ok_or_else(|| anyhow!("Failed to allocate cpu with id `{host_cpuid:?}`"))?; + pub fn new( + host_cpuid: Option, + dtb_addr: GuestPhysAddr, + vm: VmDataWeak, + ) -> anyhow::Result { + let common = VCpuCommon::new_exclusive(host_cpuid, vm)?; - let hard_id = hcpu_exclusive.hard_id(); + let hard_id = common.hard_id(); let vcpu = arm_vcpu::Aarch64VCpu::new(Aarch64VCpuCreateConfig { mpidr_el1: hard_id.raw() as u64, dtb_addr: dtb_addr.as_usize(), }) .unwrap(); - Ok(VCpu { - id: hard_id, - vcpu, - hcpu: hcpu_exclusive, - handle: VCpuHandle::new(), - }) - } - - pub fn handle(&self) -> VCpuHandle { - self.handle.clone() - } - - pub fn with_hcpu(&self, f: F) -> R - where - F: FnOnce(&HCpu) -> R, - { - self.hcpu.with_cpu(f) - } - - pub fn binded_cpu_id(&self) -> CpuId { - self.hcpu.cpu_id() + Ok(VCpu { vcpu, common }) } pub fn run(&mut self) -> anyhow::Result<()> { - info!("Starting vCPU {}", self.id); + info!("Starting vCPU {}", self.id()); - while self.handle.is_active() { + while self.is_active() { let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; - debug!("vCPU {} exited with reason: {:?}", self.id, exit_reason); + debug!("vCPU {} exited with reason: {:?}", self.id(), exit_reason); match exit_reason { arm_vcpu::AxVCpuExitReason::Hypercall { nr, args } => todo!(), arm_vcpu::AxVCpuExitReason::MmioRead { @@ -163,7 +148,10 @@ impl VCpu { arg, } => todo!(), arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), - arm_vcpu::AxVCpuExitReason::SystemDown => todo!(), + arm_vcpu::AxVCpuExitReason::SystemDown => { + info!("vCPU {} requested system shutdown", self.common.bind_id); + self.shutdown()?; + } arm_vcpu::AxVCpuExitReason::Nothing => {} arm_vcpu::AxVCpuExitReason::SendIPI { target_cpu, @@ -179,3 +167,11 @@ impl VCpu { Ok(()) } } + +impl Deref for VCpu { + type Target = VCpuCommon; + + fn deref(&self) -> &Self::Target { + &self.common + } +} diff --git a/src/arch/aarch64/vm/init.rs b/src/arch/aarch64/vm/init.rs index bf16d68..1a10138 100644 --- a/src/arch/aarch64/vm/init.rs +++ b/src/arch/aarch64/vm/init.rs @@ -8,9 +8,10 @@ use std::{ use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, TASK_STACK_SIZE, VmData, VmStatusInitOps, + GuestPhysAddr, TASK_STACK_SIZE, VmRunCommonData, VmStatusInitOps, arch::{VmStatusRunning, cpu::VCpu}, config::AxVMConfig, + data2::VmDataWeak, vm::{MappingFlags, VmId}, }; @@ -19,26 +20,27 @@ const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; const VM_ASPACE_END: GuestPhysAddr = GuestPhysAddr::from_usize(VM_ASPACE_BASE.as_usize() + VM_ASPACE_SIZE); -pub struct VmInit { +pub struct VmMachineInited { pub id: VmId, pub name: String, - pt_levels: usize, - stop_requested: AtomicBool, - run_data: Option, + // pt_levels: usize, + // stop_requested: AtomicBool, + pub run_data: VmStatusRunning, } -impl VmInit { +impl VmMachineInited { /// Creates a new VM with the given configuration - pub fn new(config: &AxVMConfig) -> anyhow::Result { - let vm = Self { - id: config.id().into(), - name: config.name(), - pt_levels: 4, - stop_requested: AtomicBool::new(false), - run_data: None, - }; - Ok(vm) - } + // pub fn new(config: &AxVMConfig) -> anyhow::Result { + // let vm = Self { + // id: config.id().into(), + // name: config.name().into(), + // pt_levels: 4, + // stop_requested: AtomicBool::new(false), + // run_data: None, + // }; + // Ok(vm) + // } + /// Initializes the VM, creating vCPUs and setting up memory pub fn init(&mut self, config: AxVMConfig) -> anyhow::Result<()> { @@ -47,14 +49,14 @@ impl VmInit { let vcpus = self.new_vcpus(&config)?; let mut run_data = VmStatusRunning::new( - VmData::new(self.pt_levels, VM_ASPACE_BASE..VM_ASPACE_END)?, + VmRunCommonData::new(self.pt_levels, VM_ASPACE_BASE..VM_ASPACE_END)?, vcpus, ); debug!("Mapping memory regions for VM {} ({})", self.id, self.name); for memory_cfg in &config.memory_regions { use crate::vm::MappingFlags; - let m = run_data.data.new_memory( + let m = run_data.data.try_use()?.new_memory( memory_cfg, MappingFlags::READ | MappingFlags::WRITE @@ -64,13 +66,13 @@ impl VmInit { run_data.data.add_memory(m); } - run_data.data.load_kernel_image(&config)?; + run_data.data.try_use()?.load_kernel_image(&config)?; run_data.make_dtb(&config)?; - run_data.data.map_passthrough_regions()?; + run_data.data.try_use()?.map_passthrough_regions()?; - let kernel_entry = run_data.data.kernel_entry(); - let gpt_root = run_data.data.gpt_root(); + let kernel_entry = run_data.data.try_use()?.kernel_entry(); + let gpt_root = run_data.data.try_use()?.gpt_root(); // Setup vCPUs for vcpu in &mut run_data.vcpus { @@ -141,7 +143,7 @@ impl VmInit { } } -impl VmStatusInitOps for VmInit { +impl VmStatusInitOps for VmMachineInited { type Running = VmStatusRunning; fn id(&self) -> VmId { @@ -152,7 +154,7 @@ impl VmStatusInitOps for VmInit { &self.name } - fn start(self) -> Result { + fn start(self, vmdata: VmDataWeak) -> Result { let mut data = self.run_data.unwrap(); let mut vcpus = vec![]; diff --git a/src/arch/aarch64/vm/mod.rs b/src/arch/aarch64/vm/mod.rs index 49cdf85..c8f3e2d 100644 --- a/src/arch/aarch64/vm/mod.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -6,17 +6,20 @@ use fdt_edit::{Node, NodeRef, Property, RegInfo}; use memory_addr::MemoryAddr; mod init; +mod unint; use crate::{ - GuestPhysAddr, RunError, TASK_STACK_SIZE, Vm, VmData, VmStatusInitOps, VmStatusRunningOps, - VmStatusStoppingOps, + GuestPhysAddr, RunError, TASK_STACK_SIZE, Vm, VmDataWeak, VmRunCommonData, VmStatusInitOps, + VmStatusRunningOps, VmStatusStoppingOps, arch::cpu::VCpu, config::{AxVMConfig, MemoryKind}, + data2::VmDataWeak, vhal::cpu::CpuHardId, vm::{MappingFlags, VmId}, }; -pub use init::VmInit; +pub(crate) use init::*; +pub(crate) use unint::*; const VM_ASPACE_BASE: usize = 0x0; const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; @@ -24,19 +27,8 @@ const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; impl VmStatusRunningOps for VmStatusRunning { type Stopping = VmStatusStopping; - fn stop(self) -> Result - where - Self: Sized, - { - Ok(VmStatusStopping {}) - } - - fn do_work(&mut self) -> Result<(), RunError> { - if self.vcpu_running_count.load(Ordering::SeqCst) == 0 { - Err(RunError::Exit) - } else { - Ok(()) - } + fn stop(self) -> Self::Stopping { + Self::Stopping {} } } @@ -47,13 +39,13 @@ impl VmStatusStoppingOps for VmStatusStopping {} /// Data needed when VM is running pub struct VmStatusRunning { vcpus: Vec, - data: VmData, + data: VmDataWeak, dtb_addr: GuestPhysAddr, vcpu_running_count: Arc, } impl VmStatusRunning { - pub(crate) fn new(data: VmData, vcpus: Vec) -> Self { + pub(crate) fn new(data: VmDataWeak, vcpus: Vec) -> Self { Self { vcpus, data, @@ -84,12 +76,12 @@ impl VmStatusRunning { } }; - let mut guest_mem = self.data.new_memory(&kind, flags); + let mut guest_mem = self.data.try_use()?.new_memory(&kind, flags); self.dtb_addr = guest_mem.gpa(); guest_mem.copy_from_slice(0, &dtb_cfg.data); - self.data.add_reserved_memory(guest_mem); + self.data.try_use()?.add_reserved_memory(guest_mem); } else { debug!( "No dtb provided, generating new dtb for {} ({})", @@ -127,7 +119,7 @@ impl VmStatusRunning { let root_address_cells = fdt.root().address_cells().unwrap_or(2); let root_size_cells = fdt.root().size_cells().unwrap_or(2); - for (i, m) in self.data.memories().iter().enumerate() { + for (i, m) in self.data.try_use()?.memories().iter().enumerate() { let mut node = Node::new(&format!("memory@{i}")); let mut prop = Property::new("device_type", vec![]); prop.set_string("memory"); @@ -147,7 +139,7 @@ impl VmStatusRunning { let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); debug!("Generated DTB:\n{f}"); - let mut guest_mem = self.data.memories().into_iter().next().unwrap(); + let mut guest_mem = self.data.try_use()?.memories().into_iter().next().unwrap(); let mut dtb_start = (guest_mem.0.as_usize() + guest_mem.1.min(512 * 1024 * 1024)) - dtb_data.len(); dtb_start = dtb_start.align_down_4k(); @@ -170,6 +162,8 @@ impl VmStatusRunning { fn copy_to_guest(&mut self, gpa: GuestPhysAddr, data: &[u8]) { let parts = self .data + .try_use() + .unwrap() .addrspace .lock() .translated_byte_buffer(gpa.as_usize().into(), data.len()) diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs new file mode 100644 index 0000000..9fb2106 --- /dev/null +++ b/src/arch/aarch64/vm/unint.rs @@ -0,0 +1,138 @@ +use core::sync::atomic::Ordering; + +use alloc::vec::Vec; + +use crate::{ + AxVMConfig, GuestPhysAddr, VmMachineUninitOps, VmRunCommonData, + arch::{VmMachineInited, VmStatusRunning, cpu::VCpu}, + config::CpuNumType, + data2::VmDataWeak, + vm::MappingFlags, +}; + +const VM_ASPACE_BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0); +const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; +const VM_ASPACE_END: GuestPhysAddr = + GuestPhysAddr::from_usize(VM_ASPACE_BASE.as_usize() + VM_ASPACE_SIZE); + +pub struct VmMachineUninit { + config: AxVMConfig, + pt_levels: usize, +} + +impl VmMachineUninitOps for VmMachineUninit { + type Inited = VmMachineInited; + + fn new(config: AxVMConfig) -> Self { + Self { + config, + pt_levels: 4, + } + } + + fn init(self, vmdata: VmDataWeak) -> Result + where + Self: Sized, + { + debug!("Initializing VM {} ({})", self.config.id, self.config.name); + let cpus = self.new_vcpus(&vmdata)?; + let mut run_data = VmStatusRunning::new( + VmRunCommonData::new(self.pt_levels, VM_ASPACE_BASE..VM_ASPACE_END)?, + vcpus, + ); + + debug!( + "Mapping memory regions for VM {} ({})", + self.config.id, self.config.name + ); + for memory_cfg in &self.config.memory_regions { + let m = run_data.data.try_use()?.new_memory( + memory_cfg, + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::USER, + ); + run_data.data.add_memory(m); + } + + run_data.data.try_use()?.load_kernel_image(&self.config)?; + run_data.make_dtb(&self.config)?; + + run_data.data.try_use()?.map_passthrough_regions()?; + + let kernel_entry = run_data.data.try_use()?.kernel_entry(); + let gpt_root = run_data.data.try_use()?.gpt_root(); + + // Setup vCPUs + for vcpu in &mut run_data.vcpus { + vcpu.vcpu.set_entry(kernel_entry).unwrap(); + vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); + + let setup_config = Aarch64VCpuSetupConfig { + passthrough_interrupt: self.config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + passthrough_timer: self.config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + }; + + vcpu.vcpu + .setup(setup_config) + .map_err(|e| anyhow::anyhow!("Failed to setup vCPU : {e:?}"))?; + + // Set EPT root + vcpu.vcpu + .set_ept_root(gpt_root) + .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; + + run_data.vcpu_running_count.fetch_add(1, Ordering::SeqCst); + } + + Ok(VmMachineInited { + id: self.config.id, + name: self.config.name, + run_data, + }) + } +} + +impl VmMachineUninit { + fn new_vcpus(&mut self, vm: &VmDataWeak) -> anyhow::Result> { + // Create vCPUs + let mut vcpus = vec![]; + + let dtb_addr = GuestPhysAddr::from_usize(0); + + match self.config.cpu_num { + CpuNumType::Alloc(num) => { + for _ in 0..num { + let vcpu = VCpu::new(None, dtb_addr, vm.clone())?; + debug!("Created vCPU with {:?}", vcpu.bind_id()); + vcpus.push(vcpu); + } + } + CpuNumType::Fixed(ref ids) => { + for id in ids { + let vcpu = VCpu::new(Some(*id), dtb_addr, vm.clone())?; + debug!("Created vCPU with {:?}", vcpu.bind_id()); + vcpus.push(vcpu); + } + } + } + + let vcpu_count = vcpus.len(); + + for vcpu in &vcpus { + let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); + if max_levels < self.pt_levels { + self.pt_levels = max_levels; + } + } + + debug!( + "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", + self.config.id, self.config.name, vcpu_count, self.pt_levels + ); + Ok(vcpus) + } +} diff --git a/src/config.rs b/src/config.rs index eed9812..ac728a0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -91,8 +91,8 @@ impl AxVMConfig { } /// Returns VM name. - pub fn name(&self) -> String { - self.name.clone() + pub fn name(&self) -> &str { + &self.name } /// Returns configurations related to VM image load addresses. diff --git a/src/lib.rs b/src/lib.rs index 9355d6b..d4a9b06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ pub(crate) mod arch; mod fdt; mod vm; +mod vcpu; pub mod config; pub mod vhal; diff --git a/src/vcpu/mod.rs b/src/vcpu/mod.rs new file mode 100644 index 0000000..2dfcafa --- /dev/null +++ b/src/vcpu/mod.rs @@ -0,0 +1,43 @@ +use crate::{ + CpuId, + data2::{VmData, VmDataWeak}, + vhal::cpu::{CpuHardId, HCpuExclusive}, +}; + +#[derive(Debug)] +pub struct VCpuCommon { + pub(crate) hcpu: HCpuExclusive, + vm: VmDataWeak, +} + +impl VCpuCommon { + pub fn new_exclusive(bind: Option, vm: VmDataWeak) -> anyhow::Result { + let hcpu_exclusive = HCpuExclusive::try_new(bind) + .ok_or_else(|| anyhow!("Failed to allocate cpu with id `{bind:?}`"))?; + Ok(VCpuCommon { hcpu, vm }) + } + + pub fn bind_id(&self) -> CpuId { + self.hcpu.id() + } + + pub fn hard_id(&self) -> CpuHardId { + self.hcpu.hard_id() + } + + #[inline] + pub fn is_active(&self) -> bool { + self.vm.is_active() + } + + pub fn with_hcpu(&self, f: F) -> R + where + F: FnOnce(&HCpu) -> R, + { + self.hcpu.with_cpu(f) + } + + pub fn vm(&self) -> anyhow::Result { + self.vm.try_upgrade() + } +} diff --git a/src/vm/data.rs b/src/vm/data.rs index 42350cc..c9edd3c 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -1,6 +1,6 @@ use core::{alloc::Layout, ops::Range}; use std::{ - sync::{Arc, Mutex}, + sync::{Arc, Mutex, Weak}, vec::Vec, }; @@ -19,14 +19,18 @@ const ALIGN: usize = 1024 * 1024 * 2; use super::addrspace::AddrSpace; -#[derive(Clone)] -pub struct VmData { - shared: Arc>, - pub(crate) addrspace: Arc>, +pub type VmDataArc = Arc; + +pub struct VmRunCommonData { + shared: Mutex, + pub(crate) addrspace: Mutex, } -impl VmData { - pub fn new(gpt_levels: usize, vm_addr_space: Range) -> anyhow::Result { +impl VmRunCommonData { + pub fn new( + gpt_levels: usize, + vm_addr_space: Range, + ) -> anyhow::Result> { let mut memory_map = VmRegionMap::new(Vec::new()); let vm_space_size = vm_addr_space.end.as_usize() - vm_addr_space.start.as_usize(); memory_map.add(VmRegion { @@ -42,13 +46,13 @@ impl VmData { vm_space_size, ) .map_err(|e| anyhow!("Failed to create address space: {e:?}"))?; - Ok(Self { - addrspace: Arc::new(Mutex::new(address_space)), - shared: Arc::new(Mutex::new(SharedData { + Ok(Arc::new(Self { + addrspace: Mutex::new(address_space), + shared: Mutex::new(SharedData { memory_map, ..Default::default() - })), - }) + }), + })) } pub fn add_memory(&self, m: GuestMemory) { @@ -66,7 +70,7 @@ impl VmData { Ok(()) } - pub fn new_memory(&self, kind: &MemoryKind, flags: MappingFlags) -> GuestMemory { + pub fn new_memory(self: &Arc, kind: &MemoryKind, flags: MappingFlags) -> GuestMemory { let _gpa; let _size; let mut hva = HostVirtAddr::from(0); @@ -121,10 +125,14 @@ impl VmData { hva, size: _size, kind: kind.clone(), - owner: self.clone(), + owner: self.weak(), } } + pub fn weak(self: &Arc) -> VmDataWeak { + Arc::downgrade(self).into() + } + pub fn load_kernel_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { let mut idx = 0; let image_cfg = config.image_config(); @@ -242,13 +250,15 @@ pub struct GuestMemory { hva: HostVirtAddr, size: usize, kind: MemoryKind, - owner: VmData, + owner: VmDataWeak, } impl GuestMemory { pub fn copy_from_slice(&mut self, offset: usize, data: &[u8]) { assert!(data.len() <= self.size - offset); - let g = self.owner.addrspace.lock(); + let owner = self.owner.try_use().unwrap(); + + let g = owner.addrspace.lock(); let hva = g .translated_byte_buffer(self.gpa.as_usize().into(), self.size) .expect("Failed to translate kernel image load address"); @@ -282,8 +292,10 @@ impl GuestMemory { } pub fn to_vec(&self) -> Vec { + let owner = self.owner.try_use().unwrap(); + let mut result = vec![]; - let g = self.owner.addrspace.lock(); + let g = owner.addrspace.lock(); let hva = g .translated_byte_buffer(self.gpa.as_usize().into(), self.size) .expect("Failed to translate memory region"); @@ -297,7 +309,12 @@ impl GuestMemory { impl Drop for GuestMemory { fn drop(&mut self) { - let mut g = self.owner.addrspace.lock(); + let owner = match self.owner.inner.upgrade() { + Some(o) => o, + None => return, + }; + + let mut g = owner.addrspace.lock(); match &self.kind { MemoryKind::Identical { .. } => { unsafe { diff --git a/src/vm/data2.rs b/src/vm/data2.rs new file mode 100644 index 0000000..3354776 --- /dev/null +++ b/src/vm/data2.rs @@ -0,0 +1,192 @@ +use core::ops::Deref; +use std::sync::{Arc, Weak}; + +use spin::RwLock; + +use crate::{ + AxVMConfig, RunError, VmMachineUninitOps, VmStatusInitOps, VmStatusRunningOps, + arch::{VmMachineInited, VmMachineUninit}, + config::AxVCpuConfig, + vm::machine::{AtomicState, VMStatus, VmMachineState}, +}; + +pub(crate) struct VmDataInner { + pub id: VmId, + pub name: String, + pub machine: RwLock, + pub status: AtomicState, + error: RwLock>, +} + +impl VmDataInner { + pub fn new(config: AxVMConfig) -> Self { + Self { + id: config.id.into(), + name: config.name.clone(), + machine: RwLock::new(VmMachineState::Uninit(config)), + status: AtomicState::new(VMStatus::Uninit), + error: RwLock::new(None), + } + } + + pub fn stop(&self) -> anyhow::Result<()> { + let mut status_guard = self.machine.write(); + match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + VmMachineState::Running(running) => { + let stopping = running.stop(); + *status_guard = VmMachineState::Stopping(stopping); + Ok(()) + } + other => { + *status_guard = other; + Err(anyhow::anyhow!("VM is not in Running state")) + } + } + } + + pub fn wait(&self) -> anyhow::Result<()> { + loop { + { + let status_guard = self.machine.read(); + if let VmMachineState::Stopped = &*status_guard { + break; + } + } + std::thread::yield_now(); + } + } + + #[inline] + pub fn status(&self) -> VMStatus { + self.status.load() + } + + #[inline] + pub fn is_active(&self) -> bool { + let status = self.status(); + status < VMStatus::Stopping + } + + pub(crate) fn set_err(&self, err: RunError) { + let mut guard = self.error.write(); + *guard = Some(err); + } + + pub(crate) fn run_result(&self) -> Result<(), RunError> { + let mut guard = self.error.write(); + let res = guard.clone(); + match res { + Some(err) => match err { + RunError::Exit => Ok(()), + RunError::ExitWithError(error) => Err(error), + }, + None => Ok(()), + } + } +} + +pub struct VmData { + inner: Arc, +} + +impl VmData { + pub fn new(config: AxVMConfig) -> anyhow::Result { + Ok(Self { + inner: Arc::new(VmDataInner::new(config)), + }) + } + + pub fn id(&self) -> VmId { + self.inner.id + } + + pub fn name(&self) -> &str { + &self.inner.name + } + + pub fn init(&self) -> anyhow::Result<()> { + let mut status_guard = self.machine.write(); + match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + VmMachineState::Uninit(config) => { + let vm_init = VmMachineUninit::new(config); + let init = vm_init.init(self.downgrade())?; + *status_guard = VmMachineState::Inited(init); + self.status.store(VMStatus::Inited); + Ok(()) + } + other => { + *status_guard = other; + Err(anyhow::anyhow!("VM is not in Uninit state")) + } + } + } + + pub fn start(&self) -> anyhow::Result<()> { + let data = self.downgrade(); + let mut status_guard = self.machine.write(); + match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + VmMachineState::Inited(init) => match init.start(data) { + Ok(running) => { + *status_guard = VmMachineState::Running(running); + self.status.store(VMStatus::Running); + Ok(()) + } + Err((e, init)) => { + *status_guard = VmMachineState::Inited(init); + Err(e) + } + }, + other => { + *status_guard = other; + Err(anyhow::anyhow!("VM is not in Init state")) + } + } + } + + pub fn downgrade(&self) -> VmDataWeak { + VmDataWeak { + inner: Arc::downgrade(&self.inner), + } + } +} + +impl From> for VmData { + fn from(inner: Arc) -> Self { + Self { inner } + } +} + +impl Deref for VmData { + type Target = VmDataInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[derive(Clone)] +pub struct VmDataWeak { + inner: Weak, +} + +impl VmDataWeak { + pub fn upgrade(&self) -> Option { + Some(self.inner.upgrade()?.into()) + } + + pub fn try_upgrade(&self) -> anyhow::Result { + let res = self + .upgrade() + .ok_or_else(|| anyhow::anyhow!("VM data has been dropped"))?; + Ok(res) + } + + #[inline] + pub fn is_active(&self) -> bool { + if let Some(inner) = self.upgrade() { + inner.is_active() + } else { + false + } + } +} diff --git a/src/vm/define.rs b/src/vm/define.rs new file mode 100644 index 0000000..00e878f --- /dev/null +++ b/src/vm/define.rs @@ -0,0 +1,69 @@ +use core::fmt; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VmId(usize); + +impl VmId { + pub fn new_fixed(id: usize) -> Self { + VmId(id) + } + + pub fn new() -> Self { + use core::sync::atomic::{AtomicUsize, Ordering}; + static VM_ID_COUNTER: AtomicUsize = AtomicUsize::new(1); + let id = VM_ID_COUNTER.fetch_add(1, Ordering::Relaxed); + VmId(id) + } +} + +impl Default for VmId { + fn default() -> Self { + VmId::new() + } +} + +// Implement Display for VmId +impl fmt::Display for VmId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for VmId { + fn from(value: usize) -> Self { + VmId(value) + } +} + +impl From for usize { + fn from(value: VmId) -> Self { + value.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + Idle, + Running, + ShuttingDown, + PoweredOff, +} + +#[derive(thiserror::Error, Debug)] +pub enum RunError { + #[error("VM exited normally")] + Exit, + #[error("VM exited with error: {0}")] + ExitWithError(#[from] anyhow::Error), +} + +impl Clone for RunError { + fn clone(&self) -> Self { + match self { + RunError::Exit => RunError::Exit, + RunError::ExitWithError(err) => { + RunError::ExitWithError(anyhow::anyhow!(format!("{err}"))) + } + } + } +} diff --git a/src/vm/machine.rs b/src/vm/machine.rs index ff9ac0d..1f2fa88 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -5,16 +5,36 @@ use alloc::{ }; use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use spin::Mutex; -use std::thread::{self}; +use std::thread; use crate::{ - RunError, Status, VmId, VmStatusInitOps, VmStatusRunningOps, - arch::{VmInit, VmStatusRunning, VmStatusStopping}, + AxVMConfig, RunError, Status, VmId, VmStatusInitOps, VmStatusRunningOps, + arch::{VmMachineInited, VmStatusRunning, VmStatusStopping}, }; -/// Default interval in milliseconds for polling the VM status from -/// the background machine thread. -const STATE_POLL_INTERVAL_MS: u64 = 20; +pub trait VmMachineUninitOps { + type Inited: VmStatusInitOps; + fn new(config: AxVMConfig) -> Self; + fn init(self, vmdata: VmDataWeak) -> Result + where + Self: Sized; +} + +pub trait VmStatusInitOps { + type Running: VmStatusRunningOps; + fn id(&self) -> VmId; + fn name(&self) -> &str; + fn start(self, vmdata: VmDataWeak) -> Result + where + Self: Sized; +} + +pub trait VmStatusRunningOps { + type Stopping: VmStatusStoppingOps; + fn stop(self) -> Self::Stopping; +} + +pub trait VmStatusStoppingOps {} /// A lightweight container that stores the identifier and human readable name /// for a VM instance. Shared between the public [`Vm`] object and the @@ -25,244 +45,18 @@ pub struct VmCommon { pub name: String, } -#[derive(Clone)] -struct CommandResponder { - inner: Arc, -} - -struct CommandResponderInner { - ready: AtomicBool, - worker_alive: Arc, - result: Mutex>>, -} - -impl CommandResponder { - fn new(worker_alive: &Arc) -> Self { - Self { - inner: Arc::new(CommandResponderInner { - ready: AtomicBool::new(false), - worker_alive: worker_alive.clone(), - result: Mutex::new(None), - }), - } - } - - fn complete(&self, result: anyhow::Result<()>) { - *self.inner.result.lock() = Some(result); - self.inner.ready.store(true, Ordering::Release); - } - - fn wait(self) -> anyhow::Result<()> { - loop { - if self.inner.ready.load(Ordering::Acquire) { - return self.inner.result.lock().take().unwrap_or_else(|| Ok(())); - } - if !self.inner.worker_alive.load(Ordering::Acquire) { - return Err(anyhow::anyhow!( - "vm worker stopped before completing command" - )); - } - thread::yield_now(); - } - } -} - -enum MachineCommand { - Start { responder: CommandResponder }, - Shutdown { responder: CommandResponder }, -} - -pub struct CommandMailbox { - queue: Mutex>, -} - -impl CommandMailbox { - pub fn new() -> Self { - Self { - queue: Mutex::new(VecDeque::new()), - } - } - - pub fn push(&self, cmd: MachineCommand) { - self.queue.lock().push_back(cmd); - } - - pub fn pop(&self) -> Option { - self.queue.lock().pop_front() - } -} - -#[derive(Clone)] -pub struct VmHandle { - pub common: VmCommon, - state: Arc, - commands: Arc, - worker_alive: Arc, -} - -impl VmHandle { - fn new(vm: &VmInit) -> Self { - Self { - common: VmCommon { - id: vm.id(), - name: vm.name().to_string(), - }, - state: Arc::new(AtomicState::new(VMStatus::Loaded)), - commands: Arc::new(CommandMailbox::new()), - worker_alive: Arc::new(AtomicBool::new(true)), - } - } - - pub fn status(&self) -> VMStatus { - self.state.load() - } - - pub fn start(&self) -> anyhow::Result<()> { - let responder = CommandResponder::new(&self.worker_alive); - self.send_command(MachineCommand::Start { - responder: responder.clone(), - })?; - responder.wait() - } - - pub fn shutdown(&self) -> anyhow::Result<()> { - let responder = CommandResponder::new(&self.worker_alive); - self.send_command(MachineCommand::Shutdown { - responder: responder.clone(), - })?; - responder.wait() - } - - fn send_command(&self, cmd: MachineCommand) -> anyhow::Result<()> { - if !self.worker_alive.load(Ordering::Acquire) { - return Err(anyhow::anyhow!("vm worker already stopped")); - } - self.commands.push(cmd); - Ok(()) - } -} - -enum VmMachineState { - Init(VmInit), +pub enum VmMachineState { + Uninit(AxVMConfig), + Inited(VmMachineInited), Running(VmStatusRunning), + Switching, Stopping(VmStatusStopping), Stopped, } impl VmMachineState { - fn do_work(&mut self) -> Result<(), RunError> { - match self { - VmMachineState::Running(running_vm) => running_vm.do_work()?, - _ => {} - } - Ok(()) - } -} - -/// State machine that owns a VM implementation (`V`) and executes commands in -/// a dedicated worker thread. The public side can enqueue commands and read -/// status without blocking the main control thread. -pub struct VmMachine { - handle: VmHandle, - vm: Option, -} - -impl VmMachine { - pub(crate) fn new(vm: VmInit) -> anyhow::Result { - let handle = VmHandle::new(&vm); - Ok(Self { - handle, - vm: Some(VmMachineState::Init(vm)), - }) - } - - pub(crate) fn id(&self) -> VmId { - self.handle.common.id - } - - pub(crate) fn name(&self) -> &str { - self.handle.common.name.as_str() - } - - pub(crate) fn status(&self) -> VMStatus { - self.handle.state.load() - } - - pub fn handle(&self) -> VmHandle { - self.handle.clone() - } - - fn is_active(&self) -> bool { - self.status() < VMStatus::Stopping - } - - pub fn run(&mut self) -> Result<(), RunError> { - let res = self.run_loop(); - self.handle.state.store(VMStatus::Stopped); - res - } - - fn run_loop(&mut self) -> Result<(), RunError> { - while self.is_active() { - self.run_loop_once()?; - thread::yield_now(); - } - Ok(()) - } - - fn run_loop_once(&mut self) -> Result<(), RunError> { - if let Some(cmd) = self.handle.commands.pop() { - match cmd { - MachineCommand::Start { responder } => { - let result = match self.vm.take() { - Some(VmMachineState::Init(vm_init)) => match vm_init.start() { - Ok(running_vm) => { - self.vm = Some(VmMachineState::Running(running_vm)); - self.handle.state.store(VMStatus::Running); - Ok(()) - } - Err((e, vm_init)) => { - self.vm = Some(VmMachineState::Init(vm_init)); - Err(e) - } - }, - Some(state) => { - self.vm = Some(state); - Err(anyhow::anyhow!("VM is not in a startable state")) - } - None => panic!("VM state is missing"), - }; - responder.complete(result); - } - MachineCommand::Shutdown { responder } => { - let result = match self.vm.take() { - Some(VmMachineState::Running(running_vm)) => match running_vm.stop() { - Ok(stopping_vm) => { - self.vm = Some(VmMachineState::Stopping(stopping_vm)); - self.handle.state.store(VMStatus::Stopping); - Ok(()) - } - Err((e, running_vm)) => { - self.vm = Some(VmMachineState::Running(running_vm)); - Err(e) - } - }, - Some(state) => { - self.vm = Some(state); - Err(anyhow::anyhow!("VM is not in a stoppable state")) - } - None => panic!("VM state is missing"), - }; - responder.complete(result); - } - } - } else { - if let Some(vm_state) = &mut self.vm { - vm_state.do_work()?; - } - } - - Ok(()) + pub fn is_active(&self) -> bool { + !matches!(self, VmMachineState::Stopping(_) | VmMachineState::Stopped) } } @@ -272,10 +66,11 @@ impl VmMachine { pub(crate) struct AtomicState(AtomicU8); impl AtomicState { - pub fn new(state: VMStatus) -> Self { + pub const fn new(state: VMStatus) -> Self { Self(AtomicU8::new(state as u8)) } + #[inline] pub fn load(&self) -> VMStatus { VMStatus::from_u8(self.0.load(Ordering::Acquire)) } @@ -289,39 +84,29 @@ impl AtomicState { /// This is intentionally richer than the low-level `Status` that is returned /// by the architecture specific implementation so that the shell and /// management layers can express user-friendly states. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u8)] pub enum VMStatus { - Loading = 0, - Loaded = 1, - Running = 2, - Suspended = 3, - Stopping = 4, - Stopped = 5, -} - -impl Default for VMStatus { - fn default() -> Self { - VMStatus::Loading - } + #[default] + Uninit, + Switching, + Inited, + Running, + Suspended, + Stopping, + Stopped, } impl VMStatus { fn from_u8(raw: u8) -> Self { - match raw { - 0 => VMStatus::Loading, - 1 => VMStatus::Loaded, - 2 => VMStatus::Running, - 3 => VMStatus::Suspended, - 4 => VMStatus::Stopping, - _ => VMStatus::Stopped, - } + unsafe { core::mem::transmute(raw) } } } impl From for VMStatus { fn from(status: Status) -> Self { match status { - Status::Idle => VMStatus::Loaded, + Status::Idle => VMStatus::Inited, Status::Running => VMStatus::Running, Status::ShuttingDown => VMStatus::Stopping, Status::PoweredOff => VMStatus::Stopped, diff --git a/src/vm/mod.rs b/src/vm/mod.rs index d4cbfc7..8926fd6 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,151 +1,57 @@ use core::fmt; use alloc::sync::Arc; -use spin::Mutex; +use spin::{Mutex, RwLock}; use std::thread; -use crate::{AxVMConfig, arch::VmInit}; +use crate::{AxVMConfig, arch::VmMachineInited, vm::data2::VmDataWeak}; +mod addrspace; mod data; +pub(crate) mod data2; +mod define; mod machine; -mod addrspace; -pub(crate) use data::*; -use machine::*; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct VmId(usize); - -impl VmId { - pub fn new_fixed(id: usize) -> Self { - VmId(id) - } - - pub fn new() -> Self { - use core::sync::atomic::{AtomicUsize, Ordering}; - static VM_ID_COUNTER: AtomicUsize = AtomicUsize::new(1); - let id = VM_ID_COUNTER.fetch_add(1, Ordering::Relaxed); - VmId(id) - } -} -impl Default for VmId { - fn default() -> Self { - VmId::new() - } -} - -// Implement Display for VmId -impl fmt::Display for VmId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for VmId { - fn from(value: usize) -> Self { - VmId(value) - } -} - -impl From for usize { - fn from(value: VmId) -> Self { - value.0 - } -} - -pub trait VmStatusInitOps { - type Running: VmStatusRunningOps; - fn id(&self) -> VmId; - fn name(&self) -> &str; - fn start(self) -> Result - where - Self: Sized; -} - -#[derive(thiserror::Error, Debug)] -pub enum RunError { - #[error("VM exited normally")] - Exit, - #[error("VM exited with error: {0}")] - ExitWithError(#[from] anyhow::Error), -} - -pub trait VmStatusRunningOps { - type Stopping: VmStatusStoppingOps; - fn do_work(&mut self) -> Result<(), RunError>; - fn stop(self) -> Result - where - Self: Sized; -} - -pub trait VmStatusStoppingOps {} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Status { - Idle, - Running, - ShuttingDown, - PoweredOff, -} +pub(crate) use data::*; +pub use define::*; +pub(crate) use machine::*; pub struct Vm { - handle: VmHandle, - res: Arc>>>, + data: data2::VmData, } impl Vm { pub fn new(config: AxVMConfig) -> anyhow::Result { - let mut arch_vm = VmInit::new(&config)?; - arch_vm.init(config)?; - let mut machine = VmMachine::new(arch_vm)?; - let handle = machine.handle(); - let res = Arc::new(Mutex::new(None)); - let res_arc = res.clone(); - - thread::Builder::new() - .name(format!("{}-main", handle.common.id.0)) - .spawn(move || { - let res = machine.run(); - let mut guard = res_arc.lock(); - guard.replace(res); - }) - .map_err(|e| anyhow::anyhow!("Failed to spawn VM thread: {:?}", e))?; - - Ok(Vm { handle, res }) + let data = data2::VmData::new(&config)?; + data.init()?; + Ok(Self { data }) } pub fn id(&self) -> VmId { - self.handle.common.id + self.data.id() } pub fn name(&self) -> &str { - &self.handle.common.name + self.data.name() } pub fn boot(&self) -> anyhow::Result<()> { - self.handle.start() + self.data.start() } pub fn shutdown(&self) -> anyhow::Result<()> { - self.handle.shutdown() + self.data.stop() } + #[inline] pub fn status(&self) -> VMStatus { - self.handle.status() + self.data.status() } pub fn wait(&self) -> Result<(), RunError> { while !matches!(self.status(), VMStatus::Stopped) { thread::sleep(std::time::Duration::from_millis(50)); } - let guard = self.res.lock(); - let res = guard.as_ref().unwrap(); - match res { - Ok(()) => Ok(()), - Err(e) => match e { - RunError::Exit => Ok(()), - RunError::ExitWithError(err) => Err(RunError::ExitWithError(anyhow!("{err}"))), - }, - } + self.data.run_result() } } From 7ac833455d003c6946b9feb115b468fdc9d7b644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 13:42:31 +0800 Subject: [PATCH 51/74] Refactor VM data management and address space handling - Introduced `VmData` and `VmDataWeak` structures to encapsulate VM state and configuration. - Removed the obsolete `data2.rs` file and migrated relevant functionality to `data.rs`. - Enhanced `VmAddrSpace` to manage memory regions more effectively, including methods for adding and initializing memory. - Updated `FdtBuilder` to streamline CPU and memory setup in the device tree. - Refactored `VCpuCommon` to utilize the new `HCpu` architecture. - Improved error handling and state management in VM lifecycle operations. - Added new traits for VM machine states to facilitate better state transitions and management. --- src/arch/aarch64/cpu.rs | 17 +- src/arch/aarch64/vm/init.rs | 177 +---------- src/arch/aarch64/vm/mod.rs | 175 +---------- src/arch/aarch64/vm/running.rs | 26 ++ src/arch/aarch64/vm/unint.rs | 146 +++++---- src/fdt/mod.rs | 80 ++++- src/vcpu/mod.rs | 5 +- src/vhal/cpu.rs | 1 + src/vm/addrspace.rs | 348 +++++++++++++++++++- src/vm/data.rs | 437 ++++++++++---------------- src/vm/data2.rs | 192 ----------- src/vm/{machine.rs => machine/mod.rs} | 31 +- src/vm/mod.rs | 18 +- 13 files changed, 740 insertions(+), 913 deletions(-) create mode 100644 src/arch/aarch64/vm/running.rs delete mode 100644 src/vm/data2.rs rename src/vm/{machine.rs => machine/mod.rs} (80%) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index d3d744e..c0528c9 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -6,7 +6,7 @@ use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; use axvm_types::addr::*; use crate::{ - data2::VmDataWeak, + data::VmDataWeak, vcpu::VCpuCommon, vhal::{ ArchCpuData, @@ -19,6 +19,7 @@ pub struct HCpu { pub hard_id: CpuHardId, vpercpu: Aarch64PerCpu, max_guest_page_table_levels: usize, + pub pa_range: core::ops::Range, } impl HCpu { @@ -33,12 +34,14 @@ impl HCpu { hard_id: CpuHardId::new(hard_id), vpercpu, max_guest_page_table_levels: 0, + pa_range: 0..0, } } pub fn init(&mut self) -> anyhow::Result<()> { self.vpercpu.hardware_enable(); self.max_guest_page_table_levels = self.vpercpu.max_guest_page_table_levels(); + self.pa_range = self.vpercpu.pa_range(); Ok(()) } @@ -124,11 +127,15 @@ impl VCpu { } pub fn run(&mut self) -> anyhow::Result<()> { - info!("Starting vCPU {}", self.id()); + info!("Starting vCPU {}", self.bind_id()); while self.is_active() { let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; - debug!("vCPU {} exited with reason: {:?}", self.id(), exit_reason); + debug!( + "vCPU {} exited with reason: {:?}", + self.bind_id(), + exit_reason + ); match exit_reason { arm_vcpu::AxVCpuExitReason::Hypercall { nr, args } => todo!(), arm_vcpu::AxVCpuExitReason::MmioRead { @@ -149,8 +156,8 @@ impl VCpu { } => todo!(), arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), arm_vcpu::AxVCpuExitReason::SystemDown => { - info!("vCPU {} requested system shutdown", self.common.bind_id); - self.shutdown()?; + info!("vCPU {} requested system shutdown", self.bind_id()); + self.vm()?.stop()?; } arm_vcpu::AxVCpuExitReason::Nothing => {} arm_vcpu::AxVCpuExitReason::SendIPI { diff --git a/src/arch/aarch64/vm/init.rs b/src/arch/aarch64/vm/init.rs index 1a10138..27b5cae 100644 --- a/src/arch/aarch64/vm/init.rs +++ b/src/arch/aarch64/vm/init.rs @@ -8,11 +8,11 @@ use std::{ use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, TASK_STACK_SIZE, VmRunCommonData, VmStatusInitOps, - arch::{VmStatusRunning, cpu::VCpu}, + GuestPhysAddr, TASK_STACK_SIZE, VmAddrSpace, VmMachineInitedOps, + arch::{VmMachineRunning, cpu::VCpu}, config::AxVMConfig, - data2::VmDataWeak, - vm::{MappingFlags, VmId}, + data::VmDataWeak, + vm::VmId, }; const VM_ASPACE_BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0); @@ -23,128 +23,14 @@ const VM_ASPACE_END: GuestPhysAddr = pub struct VmMachineInited { pub id: VmId, pub name: String, - // pt_levels: usize, - // stop_requested: AtomicBool, - pub run_data: VmStatusRunning, + pub vcpus: Vec, + pub vmspace: VmAddrSpace, } -impl VmMachineInited { - /// Creates a new VM with the given configuration - // pub fn new(config: &AxVMConfig) -> anyhow::Result { - // let vm = Self { - // id: config.id().into(), - // name: config.name().into(), - // pt_levels: 4, - // stop_requested: AtomicBool::new(false), - // run_data: None, - // }; - // Ok(vm) - // } - +impl VmMachineInited {} - /// Initializes the VM, creating vCPUs and setting up memory - pub fn init(&mut self, config: AxVMConfig) -> anyhow::Result<()> { - debug!("Initializing VM {} ({})", self.id, self.name); - - let vcpus = self.new_vcpus(&config)?; - - let mut run_data = VmStatusRunning::new( - VmRunCommonData::new(self.pt_levels, VM_ASPACE_BASE..VM_ASPACE_END)?, - vcpus, - ); - - debug!("Mapping memory regions for VM {} ({})", self.id, self.name); - for memory_cfg in &config.memory_regions { - use crate::vm::MappingFlags; - let m = run_data.data.try_use()?.new_memory( - memory_cfg, - MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::EXECUTE - | MappingFlags::USER, - ); - run_data.data.add_memory(m); - } - - run_data.data.try_use()?.load_kernel_image(&config)?; - run_data.make_dtb(&config)?; - - run_data.data.try_use()?.map_passthrough_regions()?; - - let kernel_entry = run_data.data.try_use()?.kernel_entry(); - let gpt_root = run_data.data.try_use()?.gpt_root(); - - // Setup vCPUs - for vcpu in &mut run_data.vcpus { - vcpu.vcpu.set_entry(kernel_entry).unwrap(); - vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); - - let setup_config = Aarch64VCpuSetupConfig { - passthrough_interrupt: config.interrupt_mode() - == axvmconfig::VMInterruptMode::Passthrough, - passthrough_timer: config.interrupt_mode() - == axvmconfig::VMInterruptMode::Passthrough, - }; - - vcpu.vcpu - .setup(setup_config) - .map_err(|e| anyhow::anyhow!("Failed to setup vCPU : {e:?}"))?; - - // Set EPT root - vcpu.vcpu - .set_ept_root(gpt_root) - .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; - - run_data.vcpu_running_count.fetch_add(1, Ordering::SeqCst); - } - - self.run_data = Some(run_data); - - Ok(()) - } - - fn new_vcpus(&mut self, config: &AxVMConfig) -> anyhow::Result> { - // Create vCPUs - let mut vcpus = Vec::new(); - - let dtb_addr = GuestPhysAddr::from_usize(0); - - match config.cpu_num { - crate::config::CpuNumType::Alloc(num) => { - for _ in 0..num { - let vcpu = VCpu::new(None, dtb_addr)?; - debug!("Created vCPU with {:?}", vcpu.id); - vcpus.push(vcpu); - } - } - crate::config::CpuNumType::Fixed(ref ids) => { - for id in ids { - let vcpu = VCpu::new(Some(*id), dtb_addr)?; - debug!("Created vCPU with {:?}", vcpu.id); - vcpus.push(vcpu); - } - } - } - - let vcpu_count = vcpus.len(); - - for vcpu in &vcpus { - let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); - if max_levels < self.pt_levels { - self.pt_levels = max_levels; - } - } - - debug!( - "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", - self.id, self.name, vcpu_count, self.pt_levels - ); - Ok(vcpus) - } -} - -impl VmStatusInitOps for VmMachineInited { - type Running = VmStatusRunning; +impl VmMachineInitedOps for VmMachineInited { + type Running = VmMachineRunning; fn id(&self) -> VmId { self.id @@ -155,51 +41,14 @@ impl VmStatusInitOps for VmMachineInited { } fn start(self, vmdata: VmDataWeak) -> Result { - let mut data = self.run_data.unwrap(); - - let mut vcpus = vec![]; - - vcpus.append(&mut data.vcpus); - let mut vcpu_handles = vec![]; - let vm_id = self.id; - - for mut vcpu in vcpus.into_iter() { - let vcpu_id = vcpu.id; - let vcpu_running_count = data.vcpu_running_count.clone(); - let bind_id = vcpu.binded_cpu_id(); - let handle = std::thread::Builder::new() - .name(format!("{vm_id}-{vcpu_id}")) - .stack_size(TASK_STACK_SIZE) - .spawn(move || { - assert!( - set_current_affinity(AxCpuMask::one_shot(bind_id.raw())), - "Initialize CPU affinity failed!" - ); - match vcpu.run() { - Ok(()) => { - info!("vCPU {} of VM {} exited normally", vcpu_id, vm_id); - } - Err(e) => { - error!( - "vCPU {} of VM {} exited with error: {:?}", - vcpu_id, vm_id, e - ); - } - } - vcpu_running_count.fetch_sub(1, Ordering::SeqCst); - vcpu - }) - .unwrap(); - - vcpu_handles.push(handle); - } - + debug!("Starting VM {} ({})", self.id, self.name); + let running = VmMachineRunning::new(); info!( "VM {} ({}) with {} cpus booted successfully.", self.id, self.name, - vcpu_handles.len() + self.vcpus.len() ); - Ok(data) + Ok(running) } } diff --git a/src/arch/aarch64/vm/mod.rs b/src/arch/aarch64/vm/mod.rs index c8f3e2d..710c5b6 100644 --- a/src/arch/aarch64/vm/mod.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -1,182 +1,15 @@ -use alloc::{string::String, sync::Arc, vec::Vec}; -use core::sync::atomic::{AtomicUsize, Ordering}; +use alloc::string::String; -use arm_vcpu::Aarch64VCpuSetupConfig; -use fdt_edit::{Node, NodeRef, Property, RegInfo}; -use memory_addr::MemoryAddr; +use crate::GuestPhysAddr; mod init; +mod running; mod unint; -use crate::{ - GuestPhysAddr, RunError, TASK_STACK_SIZE, Vm, VmDataWeak, VmRunCommonData, VmStatusInitOps, - VmStatusRunningOps, VmStatusStoppingOps, - arch::cpu::VCpu, - config::{AxVMConfig, MemoryKind}, - data2::VmDataWeak, - vhal::cpu::CpuHardId, - vm::{MappingFlags, VmId}, -}; - pub(crate) use init::*; +pub(crate) use running::*; pub(crate) use unint::*; -const VM_ASPACE_BASE: usize = 0x0; -const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; - -impl VmStatusRunningOps for VmStatusRunning { - type Stopping = VmStatusStopping; - - fn stop(self) -> Self::Stopping { - Self::Stopping {} - } -} - -pub struct VmStatusStopping {} - -impl VmStatusStoppingOps for VmStatusStopping {} - -/// Data needed when VM is running -pub struct VmStatusRunning { - vcpus: Vec, - data: VmDataWeak, - dtb_addr: GuestPhysAddr, - vcpu_running_count: Arc, -} - -impl VmStatusRunning { - pub(crate) fn new(data: VmDataWeak, vcpus: Vec) -> Self { - Self { - vcpus, - data, - dtb_addr: GuestPhysAddr::from_usize(0), - vcpu_running_count: Arc::new(AtomicUsize::new(0)), - } - } - - fn make_dtb(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { - let flags = - MappingFlags::READ | MappingFlags::WRITE | MappingFlags::WRITE | MappingFlags::USER; - - if let Some(dtb_cfg) = &config.image_config().dtb { - debug!( - "Loading DTB image into GPA @{:#x} for VM {} ({})", - dtb_cfg.gpa.unwrap_or(0.into()).as_usize(), - config.id(), - config.name() - ); - let kind = if let Some(gpa) = dtb_cfg.gpa { - MemoryKind::Vmem { - gpa: gpa.into(), - size: dtb_cfg.data.len(), - } - } else { - MemoryKind::Identical { - size: dtb_cfg.data.len(), - } - }; - - let mut guest_mem = self.data.try_use()?.new_memory(&kind, flags); - - self.dtb_addr = guest_mem.gpa(); - - guest_mem.copy_from_slice(0, &dtb_cfg.data); - self.data.try_use()?.add_reserved_memory(guest_mem); - } else { - debug!( - "No dtb provided, generating new dtb for {} ({})", - config.id(), - config.name() - ); - let mut fdt = crate::fdt::fdt_edit().expect("Need fdt"); - - let mut rm_nodes = vec![]; - let vcpu_hard_ls = self.vcpus.iter().map(|v| v.id).collect::>(); - for cpu in fdt.find_by_path("/cpus/cpu") { - if let Some(id) = cpu.regs() { - let id = CpuHardId::new(id[0].address as usize); - if vcpu_hard_ls.contains(&id) { - continue; - } - } - - rm_nodes.push(cpu.path()); - } - - for path in rm_nodes { - fdt.remove_node(&path).unwrap(); - } - - let nodes = fdt - .find_by_path("/memory") - .into_iter() - .map(|o| o.path()) - .collect::>(); - for path in nodes { - let _ = fdt.remove_node(&path); - } - - let root_address_cells = fdt.root().address_cells().unwrap_or(2); - let root_size_cells = fdt.root().size_cells().unwrap_or(2); - - for (i, m) in self.data.try_use()?.memories().iter().enumerate() { - let mut node = Node::new(&format!("memory@{i}")); - let mut prop = Property::new("device_type", vec![]); - prop.set_string("memory"); - node.add_property(prop); - fdt.root_mut().add_child(node); - let mut node = fdt - .get_by_path_mut(&format!("/memory@{i}")) - .expect("must has node"); - node.set_regs(&[RegInfo { - address: m.0.as_usize() as u64, - size: Some(m.1 as u64), - }]); - } - - let dtb_data = fdt.encode(); - - let f = fdt_edit::Fdt::from_bytes(&dtb_data).unwrap(); - debug!("Generated DTB:\n{f}"); - - let mut guest_mem = self.data.try_use()?.memories().into_iter().next().unwrap(); - let mut dtb_start = - (guest_mem.0.as_usize() + guest_mem.1.min(512 * 1024 * 1024)) - dtb_data.len(); - dtb_start = dtb_start.align_down_4k(); - - self.dtb_addr = GuestPhysAddr::from(dtb_start); - debug!( - "Loading generated DTB into GPA @{:#x} for VM {} ({})", - dtb_start, - config.id(), - config.name() - ); - self.copy_to_guest(self.dtb_addr, &dtb_data); - } - - Ok(()) - } - - fn handle_node_regs(dev_vec: &mut [DevMapConfig], node: &NodeRef<'_>) {} - - fn copy_to_guest(&mut self, gpa: GuestPhysAddr, data: &[u8]) { - let parts = self - .data - .try_use() - .unwrap() - .addrspace - .lock() - .translated_byte_buffer(gpa.as_usize().into(), data.len()) - .unwrap(); - let mut offset = 0; - for part in parts { - let len = part.len().min(data.len() - offset); - part.copy_from_slice(&data[offset..offset + len]); - offset += len; - } - } -} - /// Information about a device in the VM #[derive(Debug, Clone)] pub struct DeviceInfo {} diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs new file mode 100644 index 0000000..8907f7f --- /dev/null +++ b/src/arch/aarch64/vm/running.rs @@ -0,0 +1,26 @@ +use fdt_edit::NodeRef; + +use crate::{VmMachineRunningOps, VmMachineStoppingOps, arch::vm::DevMapConfig}; + +/// Data needed when VM is running +pub struct VmMachineRunning {} + +impl VmMachineRunning { + pub(crate) fn new() -> Self { + Self {} + } + + fn handle_node_regs(dev_vec: &mut [DevMapConfig], node: &NodeRef<'_>) {} +} + +impl VmMachineRunningOps for VmMachineRunning { + type Stopping = VmStatusStopping; + + fn stop(self) -> Self::Stopping { + Self::Stopping {} + } +} + +pub struct VmStatusStopping {} + +impl VmMachineStoppingOps for VmStatusStopping {} diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index 9fb2106..d6fda15 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -1,13 +1,14 @@ -use core::sync::atomic::Ordering; +use core::{ops::Deref, sync::atomic::Ordering}; use alloc::vec::Vec; +use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - AxVMConfig, GuestPhysAddr, VmMachineUninitOps, VmRunCommonData, - arch::{VmMachineInited, VmStatusRunning, cpu::VCpu}, + AxVMConfig, GuestPhysAddr, VmAddrSpace, VmMachineUninitOps, + arch::{VmMachineInited, cpu::VCpu}, config::CpuNumType, - data2::VmDataWeak, - vm::MappingFlags, + data::VmDataWeak, + fdt::FdtBuilder, }; const VM_ASPACE_BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0); @@ -18,6 +19,7 @@ const VM_ASPACE_END: GuestPhysAddr = pub struct VmMachineUninit { config: AxVMConfig, pt_levels: usize, + pa_max: usize, } impl VmMachineUninitOps for VmMachineUninit { @@ -27,72 +29,18 @@ impl VmMachineUninitOps for VmMachineUninit { Self { config, pt_levels: 4, + pa_max: usize::MAX, } } - fn init(self, vmdata: VmDataWeak) -> Result + fn init(mut self, vmdata: VmDataWeak) -> Result where Self: Sized, { - debug!("Initializing VM {} ({})", self.config.id, self.config.name); - let cpus = self.new_vcpus(&vmdata)?; - let mut run_data = VmStatusRunning::new( - VmRunCommonData::new(self.pt_levels, VM_ASPACE_BASE..VM_ASPACE_END)?, - vcpus, - ); - - debug!( - "Mapping memory regions for VM {} ({})", - self.config.id, self.config.name - ); - for memory_cfg in &self.config.memory_regions { - let m = run_data.data.try_use()?.new_memory( - memory_cfg, - MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::EXECUTE - | MappingFlags::USER, - ); - run_data.data.add_memory(m); + match self.init_raw(vmdata) { + Ok(inited) => Ok(inited), + Err(e) => Err((e, self)), } - - run_data.data.try_use()?.load_kernel_image(&self.config)?; - run_data.make_dtb(&self.config)?; - - run_data.data.try_use()?.map_passthrough_regions()?; - - let kernel_entry = run_data.data.try_use()?.kernel_entry(); - let gpt_root = run_data.data.try_use()?.gpt_root(); - - // Setup vCPUs - for vcpu in &mut run_data.vcpus { - vcpu.vcpu.set_entry(kernel_entry).unwrap(); - vcpu.vcpu.set_dtb_addr(run_data.dtb_addr).unwrap(); - - let setup_config = Aarch64VCpuSetupConfig { - passthrough_interrupt: self.config.interrupt_mode() - == axvmconfig::VMInterruptMode::Passthrough, - passthrough_timer: self.config.interrupt_mode() - == axvmconfig::VMInterruptMode::Passthrough, - }; - - vcpu.vcpu - .setup(setup_config) - .map_err(|e| anyhow::anyhow!("Failed to setup vCPU : {e:?}"))?; - - // Set EPT root - vcpu.vcpu - .set_ept_root(gpt_root) - .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; - - run_data.vcpu_running_count.fetch_add(1, Ordering::SeqCst); - } - - Ok(VmMachineInited { - id: self.config.id, - name: self.config.name, - run_data, - }) } } @@ -123,16 +71,80 @@ impl VmMachineUninit { let vcpu_count = vcpus.len(); for vcpu in &vcpus { - let max_levels = vcpu.with_hcpu(|cpu| cpu.max_guest_page_table_levels()); + let (max_levels, max_pa) = + vcpu.with_hcpu(|cpu| (cpu.max_guest_page_table_levels(), cpu.pa_range.end)); if max_levels < self.pt_levels { self.pt_levels = max_levels; } + if max_pa < self.pa_max { + self.pa_max = max_pa; + } } debug!( - "VM {} ({}) vCPU count: {}, Max Guest Page Table Levels: {}", - self.config.id, self.config.name, vcpu_count, self.pt_levels + "VM {} ({}) vCPU count: {}, \n Max Guest Page Table Levels: {}\n Max PA: {:#x}", + self.config.id, self.config.name, vcpu_count, self.pt_levels, self.pa_max ); Ok(vcpus) } + + fn init_raw(&mut self, vmdata: VmDataWeak) -> anyhow::Result { + debug!("Initializing VM {} ({})", self.config.id, self.config.name); + let mut cpus = self.new_vcpus(&vmdata)?; + + let mut vmspace = VmAddrSpace::new( + self.pt_levels, + GuestPhysAddr::from_usize(0)..self.pa_max.into(), + )?; + + debug!( + "Mapping memory regions for VM {} ({})", + self.config.id, self.config.name + ); + for memory_cfg in &self.config.memory_regions { + let m = vmspace.new_memory(memory_cfg); + } + + vmspace.load_kernel_image(&self.config)?; + let mut fdt = FdtBuilder::new()?; + fdt.setup_cpus(cpus.iter().map(|c| c.deref()))?; + fdt.setup_memory(vmspace.memories().iter())?; + let dtb_data = fdt.build()?; + + let dtb_addr = vmspace.load_dtb(&dtb_data)?; + + vmspace.map_passthrough_regions()?; + + let kernel_entry = vmspace.kernel_entry(); + let gpt_root = vmspace.gpt_root(); + + // Setup vCPUs + for vcpu in &mut cpus { + vcpu.vcpu.set_entry(kernel_entry).unwrap(); + vcpu.vcpu.set_dtb_addr(dtb_addr).unwrap(); + + let setup_config = Aarch64VCpuSetupConfig { + passthrough_interrupt: self.config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + passthrough_timer: self.config.interrupt_mode() + == axvmconfig::VMInterruptMode::Passthrough, + }; + + vcpu.vcpu + .setup(setup_config) + .map_err(|e| anyhow::anyhow!("Failed to setup vCPU : {e:?}"))?; + + // Set EPT root + vcpu.vcpu + .set_ept_root(gpt_root) + .map_err(|e| anyhow::anyhow!("Failed to set EPT root for vCPU : {e:?}"))?; + } + + Ok(VmMachineInited { + id: self.config.id.into(), + name: self.config.name.clone(), + vmspace, + vcpus: cpus, + }) + } } diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index daecd7f..ce05aed 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -1,5 +1,7 @@ use alloc::vec::Vec; -use fdt_edit::{Fdt, Status}; +use fdt_edit::{Fdt, FdtData, Node, Property, RegInfo, Status}; + +use crate::{GuestMemory, vcpu::VCpuCommon, vhal::cpu::CpuHardId}; pub(crate) fn fdt_edit() -> Option { let addr = axhal::dtb::get_bootarg(); @@ -26,3 +28,79 @@ pub fn cpu_list() -> Option> { .collect(); Some(cpus) } + +pub(crate) struct FdtBuilder { + fdt: Fdt, +} + +impl FdtBuilder { + pub fn new() -> anyhow::Result { + let fdt = fdt_edit().ok_or_else(|| anyhow::anyhow!("No FDT found"))?; + Ok(Self { fdt }) + } + + pub fn build(self) -> anyhow::Result { + let dtb_data = self.fdt.encode(); + Ok(dtb_data) + } + + pub fn setup_cpus<'a>( + &mut self, + vcpus: impl Iterator, + ) -> anyhow::Result<()> { + let mut rm_nodes = vec![]; + let vcpu_hard_ls = vcpus.map(|v: &VCpuCommon| v.hard_id()).collect::>(); + for cpu in self.fdt.find_by_path("/cpus/cpu") { + if let Some(id) = cpu.regs() { + let id = CpuHardId::new(id[0].address as usize); + if vcpu_hard_ls.contains(&id) { + continue; + } + } + + rm_nodes.push(cpu.path()); + } + + for path in rm_nodes { + self.fdt.remove_node(&path).unwrap(); + } + + Ok(()) + } + + pub fn setup_memory<'a>( + &mut self, + memories: impl Iterator, + ) -> anyhow::Result<()> { + let nodes = self + .fdt + .find_by_path("/memory") + .into_iter() + .map(|o| o.path()) + .collect::>(); + for path in nodes { + self.fdt.remove_node(&path).unwrap(); + } + + let root_address_cells = self.fdt.root().address_cells().unwrap_or(2); + let root_size_cells = self.fdt.root().size_cells().unwrap_or(2); + + for (i, m) in memories.enumerate() { + let mut node = Node::new(&format!("memory@{i}")); + let mut prop = Property::new("device_type", vec![]); + prop.set_string("memory"); + node.add_property(prop); + self.fdt.root_mut().add_child(node); + let mut node = self + .fdt + .get_by_path_mut(&format!("/memory@{i}")) + .expect("must has node"); + node.set_regs(&[RegInfo { + address: m.gpa().as_usize() as u64, + size: Some(m.size() as u64), + }]); + } + + Ok(()) + } +} diff --git a/src/vcpu/mod.rs b/src/vcpu/mod.rs index 2dfcafa..1dfc89e 100644 --- a/src/vcpu/mod.rs +++ b/src/vcpu/mod.rs @@ -1,6 +1,7 @@ use crate::{ CpuId, - data2::{VmData, VmDataWeak}, + arch::HCpu, + data::{VmData, VmDataWeak}, vhal::cpu::{CpuHardId, HCpuExclusive}, }; @@ -12,7 +13,7 @@ pub struct VCpuCommon { impl VCpuCommon { pub fn new_exclusive(bind: Option, vm: VmDataWeak) -> anyhow::Result { - let hcpu_exclusive = HCpuExclusive::try_new(bind) + let hcpu = HCpuExclusive::try_new(bind) .ok_or_else(|| anyhow!("Failed to allocate cpu with id `{bind:?}`"))?; Ok(VCpuCommon { hcpu, vm }) } diff --git a/src/vhal/cpu.rs b/src/vhal/cpu.rs index 87816a0..c30f799 100644 --- a/src/vhal/cpu.rs +++ b/src/vhal/cpu.rs @@ -11,6 +11,7 @@ use crate::{ pub(super) static PRE_CPU: PreCpuSet = PreCpuSet::new(); pub(super) static HCPU_ALLOC: Mutex = Mutex::new(BitAlloc4K::DEFAULT); +#[derive(Debug)] pub struct HCpuExclusive(CpuId); impl HCpuExclusive { diff --git a/src/vm/addrspace.rs b/src/vm/addrspace.rs index 7e7bb15..dfc8d87 100644 --- a/src/vm/addrspace.rs +++ b/src/vm/addrspace.rs @@ -1,12 +1,243 @@ -use core::alloc::Layout; +use alloc::vec::Vec; +use axaddrspace::MappingFlags; +use core::{ + alloc::Layout, + ops::{Deref, DerefMut, Range}, +}; +use memory_addr::MemoryAddr; +use std::sync::{Arc, Mutex}; use ranges_ext::RangeInfo; -use crate::GuestPhysAddr; +use crate::{ + AxVMConfig, GuestPhysAddr, HostPhysAddr, HostVirtAddr, + config::MemoryKind, + vhal::{ArchHal, phys_to_virt, virt_to_phys}, +}; + +const ALIGN: usize = 1024 * 1024 * 2; + +type AddrSpaceRaw = axaddrspace::AddrSpace; +type AddrSpaceSync = Arc>; -pub(crate) type AddrSpace = axaddrspace::AddrSpace; pub(crate) type VmRegionMap = ranges_ext::RangeSetAlloc; +pub struct VmAddrSpace { + pub aspace: AddrSpaceSync, + pub region_map: VmRegionMap, + kernel_entry: GuestPhysAddr, + kernel_memory_index: usize, + memories: Vec, +} + +impl VmAddrSpace { + pub fn new(gpt_levels: usize, vm_addr_space: Range) -> anyhow::Result { + let mut region_map = VmRegionMap::new(Vec::new()); + let vm_space_size = vm_addr_space.end.as_usize() - vm_addr_space.start.as_usize(); + region_map.add(VmRegion { + gpa: vm_addr_space.start, + size: vm_space_size, + kind: VmRegionKind::Passthrough, + })?; + // Create address space for the VM + let address_space = AddrSpaceRaw::new_empty( + gpt_levels, + vm_addr_space.start.as_usize().into(), + vm_space_size, + ) + .map_err(|e| anyhow!("Failed to create address space: {e:?}"))?; + + Ok(Self { + aspace: Arc::new(Mutex::new(address_space)), + region_map, + kernel_entry: GuestPhysAddr::from_usize(0), + kernel_memory_index: 0, + memories: vec![], + }) + } + + pub fn gpt_root(&self) -> HostPhysAddr { + let g = self.aspace.lock(); + g.page_table_root().as_usize().into() + } + + pub fn kernel_entry(&self) -> GuestPhysAddr { + self.kernel_entry + } + + pub fn new_memory(&mut self, kind: &MemoryKind) -> anyhow::Result<()> { + let _gpa; + let _size; + let _align = 0x1000; + let mut hva = HostVirtAddr::from(0); + let _payload; + let flags = + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE | MappingFlags::USER; + + match kind { + MemoryKind::Identical { size } => { + let array = Array::new(*size, ALIGN); + + hva = HostVirtAddr::from(array.as_mut_ptr() as usize); + _gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); + _size = *size; + _payload = Some(array); + let mut g = self.aspace.lock(); + g.map_linear( + _gpa.as_usize().into(), + hva.as_usize().into(), + _size.align_up_4k(), + flags, + ) + .unwrap(); + } + MemoryKind::Passthrough { hpa, size } => { + hva = phys_to_virt(*hpa); + _gpa = GuestPhysAddr::from_usize(hva.as_usize()); + _size = *size; + _payload = None; + let mut g = self.aspace.lock(); + g.map_linear( + _gpa.as_usize().into(), + hva.as_usize().into(), + _size.align_up_4k(), + flags, + ) + .unwrap(); + } + MemoryKind::Vmem { gpa, size } => { + _gpa = *gpa; + _size = *size; + _payload = None; + let mut g = self.aspace.lock(); + g.map_alloc(_gpa.as_usize().into(), _size.align_up_4k(), flags, true) + .unwrap(); + } + } + + self.memories.push(GuestMemory { + gpa: _gpa, + hva, + layout: Layout::from_size_align(_size, _align).unwrap(), + _payload, + aspace: self.aspace.clone(), + }); + + self.region_map.add(VmRegion { + gpa: _gpa, + size: _size, + kind: VmRegionKind::Memory, + })?; + + Ok(()) + } + + pub fn load_kernel_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { + let mut idx = 0; + let image_cfg = config.image_config(); + let gpa = if let Some(gpa) = image_cfg.kernel.gpa { + let mut found = false; + for (i, region) in self.memories.iter().enumerate() { + if (region.gpa..region.gpa + region.size()).contains(&gpa) { + idx = i; + found = true; + break; + } + } + if !found { + return Err(anyhow!( + "Kernel load GPA {:#x} not within any memory region", + gpa.as_usize() + )); + } + gpa + } else { + let mut gpa = None; + for (i, region) in self.memories.iter().enumerate() { + if region.size() >= image_cfg.kernel.data.len() { + gpa = Some(region.gpa + 2 * 1024 * 1024); + idx = i; + break; + } else { + continue; + } + } + gpa.ok_or(anyhow!("No suitable memory region found for kernel image"))? + }; + + debug!( + "Loading kernel image into GPA @{:#x} for VM {} ({})", + gpa.as_usize(), + config.id(), + config.name() + ); + let offset = gpa.as_usize() - self.memories[idx].gpa().as_usize(); + self.memories[idx].copy_from_slice(offset, &image_cfg.kernel.data); + self.kernel_memory_index = idx; + self.kernel_entry = gpa; + Ok(()) + } + + pub fn memories(&self) -> &[GuestMemory] { + &self.memories + } + + pub fn load_dtb(&mut self, data: &[u8]) -> anyhow::Result { + let guest_mem = self.memories().iter().next().unwrap(); + let mut dtb_start = + (guest_mem.gpa().as_usize() + guest_mem.size().min(512 * 1024 * 1024)) - data.len(); + dtb_start = dtb_start.align_down_4k(); + + let gpa = GuestPhysAddr::from(dtb_start); + debug!("Loading generated DTB into GPA @{:#x}", dtb_start,); + self.copy_to_guest(gpa, &data); + Ok(gpa) + } + + pub fn map_passthrough_regions(&self) -> anyhow::Result<()> { + let mut g = self.aspace.lock(); + for region in self + .region_map + .iter() + .filter(|m| m.kind == VmRegionKind::Passthrough) + { + g.map_linear( + region.gpa.as_usize().into(), + region.gpa.as_usize().into(), + region.size.align_up_4k(), + MappingFlags::READ + | MappingFlags::WRITE + | MappingFlags::EXECUTE + | MappingFlags::DEVICE + | MappingFlags::USER, + ) + .map_err(|e| { + anyhow!( + "Failed to map passthrough region: [{:?}, {:?})\n {e:?}", + region.gpa, + region.gpa + region.size + ) + })?; + } + + Ok(()) + } + + fn copy_to_guest(&mut self, gpa: GuestPhysAddr, data: &[u8]) { + let parts = self + .aspace + .lock() + .translated_byte_buffer(gpa.as_usize().into(), data.len()) + .unwrap(); + let mut offset = 0; + for part in parts { + let len = part.len().min(data.len() - offset); + part.copy_from_slice(&data[offset..offset + len]); + offset += len; + } + } +} + #[derive(Debug, Clone)] pub struct VmRegion { pub gpa: GuestPhysAddr, @@ -45,3 +276,114 @@ impl RangeInfo for VmRegion { } } } + +pub struct Array { + ptr: *mut u8, + layout: Layout, +} + +unsafe impl Send for Array {} +unsafe impl Sync for Array {} + +impl Array { + pub fn new(size: usize, align: usize) -> Self { + let layout = Layout::from_size_align(size, align).unwrap(); + let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) }; + Array { ptr, layout } + } + + pub fn as_mut_ptr(&self) -> *mut u8 { + self.ptr + } +} + +impl Deref for Array { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + unsafe { core::slice::from_raw_parts(self.ptr, self.layout.size()) } + } +} + +impl DerefMut for Array { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { core::slice::from_raw_parts_mut(self.ptr, self.layout.size()) } + } +} + +impl Drop for Array { + fn drop(&mut self) { + unsafe { + alloc::alloc::dealloc(self.ptr, self.layout); + } + } +} + +pub struct GuestMemory { + gpa: GuestPhysAddr, + hva: HostVirtAddr, + layout: Layout, + aspace: AddrSpaceSync, + _payload: Option, +} + +impl GuestMemory { + pub fn copy_from_slice(&mut self, offset: usize, data: &[u8]) { + assert!(data.len() <= self.size() - offset); + + let g = self.aspace.lock(); + let hva = g + .translated_byte_buffer(self.gpa.as_usize().into(), self.size()) + .expect("Failed to translate kernel image load address"); + let mut remain = data; + let mut skip = offset; + + for buff in hva { + if skip >= buff.len() { + skip -= buff.len(); + continue; + } + let buff = &mut buff[skip..]; + skip = 0; + + let copy_size = core::cmp::min(remain.len(), buff.len()); + buff[..copy_size].copy_from_slice(&remain[..copy_size]); + crate::arch::Hal::cache_flush(HostVirtAddr::from(buff.as_ptr() as usize), copy_size); + remain = &remain[copy_size..]; + if remain.is_empty() { + break; + } + } + } + + pub fn gpa(&self) -> GuestPhysAddr { + self.gpa + } + + pub fn size(&self) -> usize { + self.layout.size() + } + + pub fn to_vec(&self) -> Vec { + let mut result = vec![]; + let g = self.aspace.lock(); + let hva = g + .translated_byte_buffer(self.gpa.as_usize().into(), self.size()) + .expect("Failed to translate memory region"); + for buff in hva { + result.extend_from_slice(buff); + } + result.resize(self.size(), 0); + result + } +} + +impl Drop for GuestMemory { + fn drop(&mut self) { + let start = self.gpa.as_usize().align_down(self.layout.align()); + let size = self.size().align_up(self.layout.align()); + + let mut g = self.aspace.lock(); + g.unmap(start.into(), size).unwrap(); + } +} diff --git a/src/vm/data.rs b/src/vm/data.rs index c9edd3c..a40dea4 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -1,333 +1,212 @@ -use core::{alloc::Layout, ops::Range}; +use core::{ + fmt::{self, Debug}, + ops::Deref, +}; use std::{ - sync::{Arc, Mutex, Weak}, - vec::Vec, + string::String, + sync::{Arc, Weak}, }; -pub use axaddrspace::MappingFlags; -use memory_addr::MemoryAddr; +use spin::RwLock; use crate::{ - AxVMConfig, GuestPhysAddr, HostPhysAddr, HostVirtAddr, - config::MemoryKind, - vhal::{phys_to_virt, virt_to_phys}, - vm::addrspace::{VmRegion, VmRegionKind}, + AxVMConfig, RunError, VmId, VmMachineInitedOps, VmMachineRunningOps, VmMachineUninitOps, + arch::{VmMachineInited, VmMachineUninit}, + config::AxVCpuConfig, + vm::machine::{AtomicState, VMStatus, VmMachineState}, }; -use crate::{vhal::ArchHal, vm::addrspace::VmRegionMap}; - -const ALIGN: usize = 1024 * 1024 * 2; - -use super::addrspace::AddrSpace; - -pub type VmDataArc = Arc; -pub struct VmRunCommonData { - shared: Mutex, - pub(crate) addrspace: Mutex, +pub(crate) struct VmDataInner { + pub id: VmId, + pub name: String, + pub machine: RwLock, + pub status: AtomicState, + error: RwLock>, } -impl VmRunCommonData { - pub fn new( - gpt_levels: usize, - vm_addr_space: Range, - ) -> anyhow::Result> { - let mut memory_map = VmRegionMap::new(Vec::new()); - let vm_space_size = vm_addr_space.end.as_usize() - vm_addr_space.start.as_usize(); - memory_map.add(VmRegion { - gpa: vm_addr_space.start, - size: vm_space_size, - kind: VmRegionKind::Passthrough, - })?; - - // Create address space for the VM - let address_space = AddrSpace::new_empty( - gpt_levels, - vm_addr_space.start.as_usize().into(), - vm_space_size, - ) - .map_err(|e| anyhow!("Failed to create address space: {e:?}"))?; - Ok(Arc::new(Self { - addrspace: Mutex::new(address_space), - shared: Mutex::new(SharedData { - memory_map, - ..Default::default() - }), - })) +impl VmDataInner { + pub fn new(config: AxVMConfig) -> Self { + Self { + id: config.id.into(), + name: config.name.clone(), + machine: RwLock::new(VmMachineState::Uninit(VmMachineUninit::new(config))), + status: AtomicState::new(VMStatus::Uninit), + error: RwLock::new(None), + } } - pub fn add_memory(&self, m: GuestMemory) { - let mut s = self.shared.lock(); - s.memories.push(m); + pub fn stop(&self) -> anyhow::Result<()> { + let mut status_guard = self.machine.write(); + match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + VmMachineState::Running(running) => { + let stopping = running.stop(); + *status_guard = VmMachineState::Stopping(stopping); + Ok(()) + } + other => { + *status_guard = other; + Err(anyhow::anyhow!("VM is not in Running state")) + } + } } - pub fn add_reserved_memory(&self, r: GuestMemory) { - self.shared.lock().reserved_memories.push(r); + pub fn wait(&self) -> anyhow::Result<()> { + while self.is_active() { + std::thread::sleep(std::time::Duration::from_millis(50)); + } + self.run_result() } - pub(crate) fn memory_map_add_region(&self, region: VmRegion) -> anyhow::Result<()> { - let mut s = self.shared.lock(); - s.memory_map.add(region).unwrap(); - Ok(()) + #[inline] + pub fn status(&self) -> VMStatus { + self.status.load() } - pub fn new_memory(self: &Arc, kind: &MemoryKind, flags: MappingFlags) -> GuestMemory { - let _gpa; - let _size; - let mut hva = HostVirtAddr::from(0); - - match kind { - MemoryKind::Identical { size } => { - hva = HostVirtAddr::from(unsafe { - alloc::alloc::alloc(Layout::from_size_align_unchecked(*size, ALIGN)) - } as usize); - _gpa = GuestPhysAddr::from_usize(virt_to_phys(hva).as_usize()); - _size = *size; - let mut g = self.addrspace.lock(); - g.map_linear( - _gpa.as_usize().into(), - hva.as_usize().into(), - _size.align_up_4k(), - flags, - ) - .unwrap(); - } - MemoryKind::Passthrough { hpa, size } => { - hva = phys_to_virt(*hpa); - _gpa = GuestPhysAddr::from_usize(hva.as_usize()); - _size = *size; - let mut g = self.addrspace.lock(); - g.map_linear( - _gpa.as_usize().into(), - hva.as_usize().into(), - _size.align_up_4k(), - flags, - ) - .unwrap(); - } - MemoryKind::Vmem { gpa, size } => { - _gpa = *gpa; - _size = *size; - let mut g = self.addrspace.lock(); - g.map_alloc(_gpa.as_usize().into(), _size.align_up_4k(), flags, true) - .unwrap(); - } - } - - self.memory_map_add_region(VmRegion { - gpa: _gpa, - size: _size, - kind: VmRegionKind::Memory, - }) - .unwrap(); + #[inline] + pub fn is_active(&self) -> bool { + let status = self.status(); + status < VMStatus::Stopping + } - GuestMemory { - gpa: _gpa, - hva, - size: _size, - kind: kind.clone(), - owner: self.weak(), - } + pub(crate) fn set_err(&self, err: RunError) { + let mut guard = self.error.write(); + *guard = Some(err); } - pub fn weak(self: &Arc) -> VmDataWeak { - Arc::downgrade(self).into() + pub(crate) fn run_result(&self) -> anyhow::Result<()> { + let mut guard = self.error.write(); + let res = guard.clone(); + match res { + Some(err) => match err { + RunError::Exit => Ok(()), + RunError::ExitWithError(e) => Err(e), + }, + None => Ok(()), + } } +} - pub fn load_kernel_image(&mut self, config: &AxVMConfig) -> anyhow::Result<()> { - let mut idx = 0; - let image_cfg = config.image_config(); - let mut s = self.shared.lock(); - let gpa = if let Some(gpa) = image_cfg.kernel.gpa { - let mut found = false; - for (i, region) in s.memories.iter().enumerate() { - if (region.gpa..region.gpa + region.size).contains(&gpa) { - idx = i; - found = true; - break; - } - } - if !found { - return Err(anyhow!( - "Kernel load GPA {:#x} not within any memory region", - gpa.as_usize() - )); - } - gpa - } else { - let mut gpa = None; - for (i, region) in s.memories.iter().enumerate() { - if region.size >= image_cfg.kernel.data.len() { - gpa = Some(region.gpa + 2 * 1024 * 1024); - idx = i; - break; - } else { - continue; - } - } - gpa.ok_or(anyhow!("No suitable memory region found for kernel image"))? - }; +pub(crate) struct VmData { + inner: Arc, +} - debug!( - "Loading kernel image into GPA @{:#x} for VM {} ({})", - gpa.as_usize(), - config.id(), - config.name() - ); - let offset = gpa.as_usize() - s.memories[idx].gpa().as_usize(); - s.memories[idx].copy_from_slice(offset, &image_cfg.kernel.data); - s.kernel_region_index = idx; - s.kernel_entry = gpa; - Ok(()) +impl VmData { + pub fn new(config: AxVMConfig) -> anyhow::Result { + Ok(Self { + inner: Arc::new(VmDataInner::new(config)), + }) } - pub fn gpt_root(&self) -> HostPhysAddr { - let g = self.addrspace.lock(); - g.page_table_root().as_usize().into() + pub fn id(&self) -> VmId { + self.inner.id } - pub fn kernel_entry(&self) -> GuestPhysAddr { - let s = self.shared.lock(); - s.kernel_entry + pub fn name(&self) -> &str { + &self.inner.name } - pub fn memories(&self) -> Vec<(GuestPhysAddr, usize)> { - let s = self.shared.lock(); - s.memories.iter().map(|m| (m.gpa(), m.size())).collect() + pub fn init(&self) -> anyhow::Result<()> { + let mut status_guard = self.machine.write(); + match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + VmMachineState::Uninit(uninit) => { + let init = match uninit.init(self.downgrade()) { + Ok(inited) => inited, + Err((e, uninit)) => { + *status_guard = VmMachineState::Uninit(uninit); + return Err(e); + } + }; + *status_guard = VmMachineState::Inited(init); + self.status.store(VMStatus::Inited); + Ok(()) + } + other => { + *status_guard = other; + Err(anyhow::anyhow!("VM is not in Uninit state")) + } + } } - pub fn reserved_memories(&self) -> Vec<(GuestPhysAddr, usize)> { - let s = self.shared.lock(); - s.reserved_memories - .iter() - .map(|m| (m.gpa(), m.size())) - .collect() + pub fn start(&self) -> anyhow::Result<()> { + let data = self.downgrade(); + let mut status_guard = self.machine.write(); + match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + VmMachineState::Inited(init) => match init.start(data) { + Ok(running) => { + *status_guard = VmMachineState::Running(running); + self.status.store(VMStatus::Running); + Ok(()) + } + Err((e, init)) => { + *status_guard = VmMachineState::Inited(init); + Err(e) + } + }, + other => { + *status_guard = other; + Err(anyhow::anyhow!("VM is not in Init state")) + } + } } - pub fn map_passthrough_regions(&self) -> anyhow::Result<()> { - let s = self.shared.lock(); - let mut g = self.addrspace.lock(); - for region in s - .memory_map - .iter() - .filter(|m| m.kind == VmRegionKind::Passthrough) - { - g.map_linear( - region.gpa.as_usize().into(), - region.gpa.as_usize().into(), - region.size.align_up_4k(), - MappingFlags::READ - | MappingFlags::WRITE - | MappingFlags::EXECUTE - | MappingFlags::DEVICE - | MappingFlags::USER, - ) - .map_err(|e| { - anyhow!( - "Failed to map passthrough region: [{:?}, {:?})\n {e:?}", - region.gpa, - region.gpa + region.size - ) - })?; + pub fn downgrade(&self) -> VmDataWeak { + VmDataWeak { + inner: Arc::downgrade(&self.inner), } - - Ok(()) } } -#[derive(Default)] -struct SharedData { - memories: Vec, - reserved_memories: Vec, - kernel_region_index: usize, - kernel_entry: GuestPhysAddr, - memory_map: VmRegionMap, +impl From> for VmData { + fn from(inner: Arc) -> Self { + Self { inner } + } } -impl SharedData {} +impl Deref for VmData { + type Target = VmDataInner; -pub struct GuestMemory { - gpa: GuestPhysAddr, - hva: HostVirtAddr, - size: usize, - kind: MemoryKind, - owner: VmDataWeak, + fn deref(&self) -> &Self::Target { + &self.inner + } } -impl GuestMemory { - pub fn copy_from_slice(&mut self, offset: usize, data: &[u8]) { - assert!(data.len() <= self.size - offset); - let owner = self.owner.try_use().unwrap(); - - let g = owner.addrspace.lock(); - let hva = g - .translated_byte_buffer(self.gpa.as_usize().into(), self.size) - .expect("Failed to translate kernel image load address"); - let mut remain = data; - let mut skip = offset; - - for buff in hva { - if skip >= buff.len() { - skip -= buff.len(); - continue; - } - let buff = &mut buff[skip..]; - skip = 0; - - let copy_size = core::cmp::min(remain.len(), buff.len()); - buff[..copy_size].copy_from_slice(&remain[..copy_size]); - crate::arch::Hal::cache_flush(HostVirtAddr::from(buff.as_ptr() as usize), copy_size); - remain = &remain[copy_size..]; - if remain.is_empty() { - break; - } - } - } +#[derive(Clone)] +pub struct VmDataWeak { + inner: Weak, +} - pub fn gpa(&self) -> GuestPhysAddr { - self.gpa +impl VmDataWeak { + pub fn upgrade(&self) -> Option { + Some(self.inner.upgrade()?.into()) } - pub fn size(&self) -> usize { - self.size + pub fn try_upgrade(&self) -> anyhow::Result { + let res = self + .upgrade() + .ok_or_else(|| anyhow::anyhow!("VM data has been dropped"))?; + Ok(res) } - pub fn to_vec(&self) -> Vec { - let owner = self.owner.try_use().unwrap(); - - let mut result = vec![]; - let g = owner.addrspace.lock(); - let hva = g - .translated_byte_buffer(self.gpa.as_usize().into(), self.size) - .expect("Failed to translate memory region"); - for buff in hva { - result.extend_from_slice(buff); + #[inline] + pub fn is_active(&self) -> bool { + if let Some(inner) = self.upgrade() { + inner.is_active() + } else { + false } - result.resize(self.size, 0); - result } } -impl Drop for GuestMemory { - fn drop(&mut self) { - let owner = match self.owner.inner.upgrade() { - Some(o) => o, - None => return, - }; - - let mut g = owner.addrspace.lock(); - match &self.kind { - MemoryKind::Identical { .. } => { - unsafe { - alloc::alloc::dealloc( - HostVirtAddr::from(self.hva.as_usize()).as_mut_ptr(), - Layout::from_size_align(self.size, ALIGN).unwrap(), - ) - }; - } - _ => { - g.unmap(self.gpa.as_usize().into(), self.size.align_up_4k()) - .unwrap(); - } +impl Debug for VmDataWeak { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.upgrade() { + Some(data) => write!( + f, + "VmDataWeak {{ id: {}, name: {} }}", + data.id(), + data.name() + ), + None => write!(f, "VmDataWeak {{ dropped }}"), } } } diff --git a/src/vm/data2.rs b/src/vm/data2.rs deleted file mode 100644 index 3354776..0000000 --- a/src/vm/data2.rs +++ /dev/null @@ -1,192 +0,0 @@ -use core::ops::Deref; -use std::sync::{Arc, Weak}; - -use spin::RwLock; - -use crate::{ - AxVMConfig, RunError, VmMachineUninitOps, VmStatusInitOps, VmStatusRunningOps, - arch::{VmMachineInited, VmMachineUninit}, - config::AxVCpuConfig, - vm::machine::{AtomicState, VMStatus, VmMachineState}, -}; - -pub(crate) struct VmDataInner { - pub id: VmId, - pub name: String, - pub machine: RwLock, - pub status: AtomicState, - error: RwLock>, -} - -impl VmDataInner { - pub fn new(config: AxVMConfig) -> Self { - Self { - id: config.id.into(), - name: config.name.clone(), - machine: RwLock::new(VmMachineState::Uninit(config)), - status: AtomicState::new(VMStatus::Uninit), - error: RwLock::new(None), - } - } - - pub fn stop(&self) -> anyhow::Result<()> { - let mut status_guard = self.machine.write(); - match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { - VmMachineState::Running(running) => { - let stopping = running.stop(); - *status_guard = VmMachineState::Stopping(stopping); - Ok(()) - } - other => { - *status_guard = other; - Err(anyhow::anyhow!("VM is not in Running state")) - } - } - } - - pub fn wait(&self) -> anyhow::Result<()> { - loop { - { - let status_guard = self.machine.read(); - if let VmMachineState::Stopped = &*status_guard { - break; - } - } - std::thread::yield_now(); - } - } - - #[inline] - pub fn status(&self) -> VMStatus { - self.status.load() - } - - #[inline] - pub fn is_active(&self) -> bool { - let status = self.status(); - status < VMStatus::Stopping - } - - pub(crate) fn set_err(&self, err: RunError) { - let mut guard = self.error.write(); - *guard = Some(err); - } - - pub(crate) fn run_result(&self) -> Result<(), RunError> { - let mut guard = self.error.write(); - let res = guard.clone(); - match res { - Some(err) => match err { - RunError::Exit => Ok(()), - RunError::ExitWithError(error) => Err(error), - }, - None => Ok(()), - } - } -} - -pub struct VmData { - inner: Arc, -} - -impl VmData { - pub fn new(config: AxVMConfig) -> anyhow::Result { - Ok(Self { - inner: Arc::new(VmDataInner::new(config)), - }) - } - - pub fn id(&self) -> VmId { - self.inner.id - } - - pub fn name(&self) -> &str { - &self.inner.name - } - - pub fn init(&self) -> anyhow::Result<()> { - let mut status_guard = self.machine.write(); - match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { - VmMachineState::Uninit(config) => { - let vm_init = VmMachineUninit::new(config); - let init = vm_init.init(self.downgrade())?; - *status_guard = VmMachineState::Inited(init); - self.status.store(VMStatus::Inited); - Ok(()) - } - other => { - *status_guard = other; - Err(anyhow::anyhow!("VM is not in Uninit state")) - } - } - } - - pub fn start(&self) -> anyhow::Result<()> { - let data = self.downgrade(); - let mut status_guard = self.machine.write(); - match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { - VmMachineState::Inited(init) => match init.start(data) { - Ok(running) => { - *status_guard = VmMachineState::Running(running); - self.status.store(VMStatus::Running); - Ok(()) - } - Err((e, init)) => { - *status_guard = VmMachineState::Inited(init); - Err(e) - } - }, - other => { - *status_guard = other; - Err(anyhow::anyhow!("VM is not in Init state")) - } - } - } - - pub fn downgrade(&self) -> VmDataWeak { - VmDataWeak { - inner: Arc::downgrade(&self.inner), - } - } -} - -impl From> for VmData { - fn from(inner: Arc) -> Self { - Self { inner } - } -} - -impl Deref for VmData { - type Target = VmDataInner; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[derive(Clone)] -pub struct VmDataWeak { - inner: Weak, -} - -impl VmDataWeak { - pub fn upgrade(&self) -> Option { - Some(self.inner.upgrade()?.into()) - } - - pub fn try_upgrade(&self) -> anyhow::Result { - let res = self - .upgrade() - .ok_or_else(|| anyhow::anyhow!("VM data has been dropped"))?; - Ok(res) - } - - #[inline] - pub fn is_active(&self) -> bool { - if let Some(inner) = self.upgrade() { - inner.is_active() - } else { - false - } - } -} diff --git a/src/vm/machine.rs b/src/vm/machine/mod.rs similarity index 80% rename from src/vm/machine.rs rename to src/vm/machine/mod.rs index 1f2fa88..10bdb33 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine/mod.rs @@ -1,27 +1,22 @@ -use alloc::{ - collections::VecDeque, - string::{String, ToString}, - sync::Arc, -}; -use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; -use spin::Mutex; -use std::thread; +use alloc::string::String; +use core::sync::atomic::{AtomicU8, Ordering}; use crate::{ - AxVMConfig, RunError, Status, VmId, VmStatusInitOps, VmStatusRunningOps, - arch::{VmMachineInited, VmStatusRunning, VmStatusStopping}, + AxVMConfig, Status, VmId, + arch::{VmMachineInited, VmMachineRunning, VmMachineUninit, VmStatusStopping}, + data::VmDataWeak, }; pub trait VmMachineUninitOps { - type Inited: VmStatusInitOps; + type Inited: VmMachineInitedOps; fn new(config: AxVMConfig) -> Self; fn init(self, vmdata: VmDataWeak) -> Result where Self: Sized; } -pub trait VmStatusInitOps { - type Running: VmStatusRunningOps; +pub trait VmMachineInitedOps { + type Running: VmMachineRunningOps; fn id(&self) -> VmId; fn name(&self) -> &str; fn start(self, vmdata: VmDataWeak) -> Result @@ -29,12 +24,12 @@ pub trait VmStatusInitOps { Self: Sized; } -pub trait VmStatusRunningOps { - type Stopping: VmStatusStoppingOps; +pub trait VmMachineRunningOps { + type Stopping: VmMachineStoppingOps; fn stop(self) -> Self::Stopping; } -pub trait VmStatusStoppingOps {} +pub trait VmMachineStoppingOps {} /// A lightweight container that stores the identifier and human readable name /// for a VM instance. Shared between the public [`Vm`] object and the @@ -46,9 +41,9 @@ pub struct VmCommon { } pub enum VmMachineState { - Uninit(AxVMConfig), + Uninit(VmMachineUninit), Inited(VmMachineInited), - Running(VmStatusRunning), + Running(VmMachineRunning), Switching, Stopping(VmStatusStopping), Stopped, diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 8926fd6..94c59eb 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -4,25 +4,24 @@ use alloc::sync::Arc; use spin::{Mutex, RwLock}; use std::thread; -use crate::{AxVMConfig, arch::VmMachineInited, vm::data2::VmDataWeak}; +use crate::{AxVMConfig, arch::VmMachineInited, data::VmData, vm::data::VmDataWeak}; mod addrspace; -mod data; -pub(crate) mod data2; +pub(crate) mod data; mod define; mod machine; -pub(crate) use data::*; +pub(crate) use addrspace::*; pub use define::*; pub(crate) use machine::*; pub struct Vm { - data: data2::VmData, + data: VmData, } impl Vm { pub fn new(config: AxVMConfig) -> anyhow::Result { - let data = data2::VmData::new(&config)?; + let data = VmData::new(config)?; data.init()?; Ok(Self { data }) } @@ -48,10 +47,7 @@ impl Vm { self.data.status() } - pub fn wait(&self) -> Result<(), RunError> { - while !matches!(self.status(), VMStatus::Stopped) { - thread::sleep(std::time::Duration::from_millis(50)); - } - self.data.run_result() + pub fn wait(&self) -> anyhow::Result<()> { + self.data.wait() } } From faca4ac4301f03788c44b2d7a81577461f8892b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 14:29:57 +0800 Subject: [PATCH 52/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20VM=20?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=92=8C=E8=BF=90=E8=A1=8C=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=9D=97=EF=BC=8C=E4=BC=98=E5=8C=96=20CPU=20?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/{init.rs => inited.rs} | 20 ++--- src/arch/aarch64/vm/mod.rs | 4 +- src/arch/aarch64/vm/running.rs | 13 +-- src/arch/aarch64/vm/unint.rs | 7 +- src/vm/data.rs | 20 ++++- src/vm/machine/mod.rs | 8 +- src/vm/machine/running.rs | 94 ++++++++++++++++++++++ 7 files changed, 138 insertions(+), 28 deletions(-) rename src/arch/aarch64/vm/{init.rs => inited.rs} (76%) create mode 100644 src/vm/machine/running.rs diff --git a/src/arch/aarch64/vm/init.rs b/src/arch/aarch64/vm/inited.rs similarity index 76% rename from src/arch/aarch64/vm/init.rs rename to src/arch/aarch64/vm/inited.rs index 27b5cae..cc82915 100644 --- a/src/arch/aarch64/vm/init.rs +++ b/src/arch/aarch64/vm/inited.rs @@ -8,7 +8,7 @@ use std::{ use arm_vcpu::Aarch64VCpuSetupConfig; use crate::{ - GuestPhysAddr, TASK_STACK_SIZE, VmAddrSpace, VmMachineInitedOps, + GuestPhysAddr, TASK_STACK_SIZE, VmAddrSpace, VmMachineInitedOps, VmMachineRunningCommon, arch::{VmMachineRunning, cpu::VCpu}, config::AxVMConfig, data::VmDataWeak, @@ -40,15 +40,17 @@ impl VmMachineInitedOps for VmMachineInited { &self.name } - fn start(self, vmdata: VmDataWeak) -> Result { + fn start(self, vmdata: VmDataWeak) -> Result { debug!("Starting VM {} ({})", self.id, self.name); - let running = VmMachineRunning::new(); - info!( - "VM {} ({}) with {} cpus booted successfully.", - self.id, - self.name, - self.vcpus.len() - ); + let mut running = VmMachineRunning { + common: VmMachineRunningCommon::new(self.vmspace, self.vcpus, vmdata), + }; + + let main = running.common.take_cpu()?; + + running.common.run_cpu(main)?; + + info!("VM {} ({}) main cpu started.", self.id, self.name,); Ok(running) } } diff --git a/src/arch/aarch64/vm/mod.rs b/src/arch/aarch64/vm/mod.rs index 710c5b6..dff02d4 100644 --- a/src/arch/aarch64/vm/mod.rs +++ b/src/arch/aarch64/vm/mod.rs @@ -2,11 +2,11 @@ use alloc::string::String; use crate::GuestPhysAddr; -mod init; +mod inited; mod running; mod unint; -pub(crate) use init::*; +pub(crate) use inited::*; pub(crate) use running::*; pub(crate) use unint::*; diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index 8907f7f..7430ed1 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -1,15 +1,16 @@ use fdt_edit::NodeRef; -use crate::{VmMachineRunningOps, VmMachineStoppingOps, arch::vm::DevMapConfig}; +use crate::{ + VmAddrSpace, VmMachineRunningCommon, VmMachineRunningOps, VmMachineStoppingOps, + arch::vm::DevMapConfig, +}; /// Data needed when VM is running -pub struct VmMachineRunning {} +pub struct VmMachineRunning { + pub(super) common: VmMachineRunningCommon, +} impl VmMachineRunning { - pub(crate) fn new() -> Self { - Self {} - } - fn handle_node_regs(dev_vec: &mut [DevMapConfig], node: &NodeRef<'_>) {} } diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index d6fda15..0d6089f 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -33,14 +33,11 @@ impl VmMachineUninitOps for VmMachineUninit { } } - fn init(mut self, vmdata: VmDataWeak) -> Result + fn init(mut self, vmdata: VmDataWeak) -> Result where Self: Sized, { - match self.init_raw(vmdata) { - Ok(inited) => Ok(inited), - Err(e) => Err((e, self)), - } + self.init_raw(vmdata) } } diff --git a/src/vm/data.rs b/src/vm/data.rs index a40dea4..6cfa256 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -111,8 +111,10 @@ impl VmData { VmMachineState::Uninit(uninit) => { let init = match uninit.init(self.downgrade()) { Ok(inited) => inited, - Err((e, uninit)) => { - *status_guard = VmMachineState::Uninit(uninit); + Err(e) => { + self.set_err(RunError::ExitWithError(anyhow!("{e}"))); + *status_guard = VmMachineState::Stopped; + self.status.store(VMStatus::Stopped); return Err(e); } }; @@ -137,8 +139,10 @@ impl VmData { self.status.store(VMStatus::Running); Ok(()) } - Err((e, init)) => { - *status_guard = VmMachineState::Inited(init); + Err(e) => { + self.set_err(RunError::ExitWithError(anyhow!("{e}"))); + *status_guard = VmMachineState::Stopped; + self.status.store(VMStatus::Stopped); Err(e) } }, @@ -195,6 +199,14 @@ impl VmDataWeak { false } } + + pub(crate) fn set_stopped(&self) { + if let Some(inner) = self.upgrade() { + let mut status_guard = inner.machine.write(); + *status_guard = VmMachineState::Stopped; + inner.status.store(VMStatus::Stopped); + } + } } impl Debug for VmDataWeak { diff --git a/src/vm/machine/mod.rs b/src/vm/machine/mod.rs index 10bdb33..19c6794 100644 --- a/src/vm/machine/mod.rs +++ b/src/vm/machine/mod.rs @@ -7,10 +7,14 @@ use crate::{ data::VmDataWeak, }; +mod running; + +pub(crate) use running::*; + pub trait VmMachineUninitOps { type Inited: VmMachineInitedOps; fn new(config: AxVMConfig) -> Self; - fn init(self, vmdata: VmDataWeak) -> Result + fn init(self, vmdata: VmDataWeak) -> Result where Self: Sized; } @@ -19,7 +23,7 @@ pub trait VmMachineInitedOps { type Running: VmMachineRunningOps; fn id(&self) -> VmId; fn name(&self) -> &str; - fn start(self, vmdata: VmDataWeak) -> Result + fn start(self, vmdata: VmDataWeak) -> Result where Self: Sized; } diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs new file mode 100644 index 0000000..92fc163 --- /dev/null +++ b/src/vm/machine/running.rs @@ -0,0 +1,94 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + collections::btree_map::BTreeMap, + os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, + sync::Arc, +}; + +use alloc::vec::Vec; + +use crate::{ + RunError, TASK_STACK_SIZE, VmAddrSpace, arch::cpu::VCpu, data::VmDataWeak, vhal::cpu::CpuHardId, +}; + +pub struct VmMachineRunningCommon { + pub cpus: BTreeMap, + pub vmspace: VmAddrSpace, + pub vm: VmDataWeak, + running_cpu_count: Arc, +} + +impl VmMachineRunningCommon { + pub fn new(vmspace: VmAddrSpace, vcpu: Vec, vm: VmDataWeak) -> Self { + let mut cpus = BTreeMap::new(); + for cpu in vcpu.into_iter() { + cpus.insert(cpu.hard_id(), cpu); + } + + VmMachineRunningCommon { + vmspace, + cpus, + vm, + running_cpu_count: Arc::new(AtomicUsize::new(0)), + } + } + + pub fn take_cpu(&mut self) -> anyhow::Result { + let next = self + .cpus + .keys() + .next() + .cloned() + .ok_or_else(|| anyhow!("No CPUs available"))?; + let cpu = self.cpus.remove(&next).unwrap(); + Ok(cpu) + } + + pub fn run_cpu(&mut self, mut cpu: VCpu) -> anyhow::Result<()> { + let waiter = self.new_waiter(); + + let bind_id = cpu.bind_id(); + std::thread::Builder::new() + .name(format!("init-cpu-{}", bind_id)) + .stack_size(TASK_STACK_SIZE) + .spawn(move || { + // Initialize cpu affinity here. + assert!( + set_current_affinity(AxCpuMask::one_shot(bind_id.raw())), + "Initialize CPU affinity failed!" + ); + info!("Starting VCpu {} on {}", cpu.hard_id(), bind_id); + let res = cpu.run(); + if let Err(e) = res { + if let Some(vm) = waiter.vm.upgrade() { + vm.set_err(RunError::ExitWithError(e)); + } + } + waiter.running_cpu_count.fetch_sub(1, Ordering::SeqCst); + if waiter.running_cpu_count.load(Ordering::SeqCst) == 0 { + waiter.vm.set_stopped(); + } + }) + .map_err(|e| anyhow!("{e:?}"))?; + + Ok(()) + } + + fn new_waiter(&self) -> Waiter { + let running_cpu_count = self.running_cpu_count.clone(); + running_cpu_count.fetch_add(1, Ordering::SeqCst); + Waiter { + running_cpu_count, + vm: self.vm.clone(), + } + } + + pub fn vmspace(&self) -> &VmAddrSpace { + &self.vmspace + } +} + +struct Waiter { + running_cpu_count: Arc, + vm: VmDataWeak, +} From 2c2cf9e3e595b0b8925e53fcf48c0a557dee5f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 14:51:43 +0800 Subject: [PATCH 53/74] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20VCpu=20?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E8=99=9A=E6=8B=9F=20CPU=20=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 15 +++++++++++++-- src/vcpu/mod.rs | 8 +++++++- src/vm/data.rs | 19 ++++++++++++++++++- src/vm/machine/running.rs | 7 ++++--- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index c0528c9..28d72ae 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -6,8 +6,9 @@ use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; use axvm_types::addr::*; use crate::{ + RunError, data::VmDataWeak, - vcpu::VCpuCommon, + vcpu::{VCpuCommon, VCpuOp}, vhal::{ ArchCpuData, cpu::{CpuHardId, CpuId, HCpuExclusive}, @@ -125,8 +126,18 @@ impl VCpu { .unwrap(); Ok(VCpu { vcpu, common }) } +} + +impl VCpuOp for VCpu { + fn bind_id(&self) -> CpuId { + self.common.bind_id() + } + + fn hard_id(&self) -> CpuHardId { + self.common.hard_id() + } - pub fn run(&mut self) -> anyhow::Result<()> { + fn run(&mut self) -> Result<(), RunError> { info!("Starting vCPU {}", self.bind_id()); while self.is_active() { diff --git a/src/vcpu/mod.rs b/src/vcpu/mod.rs index 1dfc89e..b71e744 100644 --- a/src/vcpu/mod.rs +++ b/src/vcpu/mod.rs @@ -1,10 +1,16 @@ use crate::{ - CpuId, + CpuId, RunError, arch::HCpu, data::{VmData, VmDataWeak}, vhal::cpu::{CpuHardId, HCpuExclusive}, }; +pub trait VCpuOp: Send + 'static{ + fn bind_id(&self) -> CpuId; + fn hard_id(&self) -> CpuHardId; + fn run(&mut self) -> Result<(), RunError>; +} + #[derive(Debug)] pub struct VCpuCommon { pub(crate) hcpu: HCpuExclusive, diff --git a/src/vm/data.rs b/src/vm/data.rs index 6cfa256..894d60c 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -11,7 +11,7 @@ use spin::RwLock; use crate::{ AxVMConfig, RunError, VmId, VmMachineInitedOps, VmMachineRunningOps, VmMachineUninitOps, - arch::{VmMachineInited, VmMachineUninit}, + arch::{VmMachineInited, VmMachineRunning, VmMachineUninit}, config::AxVCpuConfig, vm::machine::{AtomicState, VMStatus, VmMachineState}, }; @@ -207,6 +207,23 @@ impl VmDataWeak { inner.status.store(VMStatus::Stopped); } } + + pub(crate) fn with_machine_running(&self, f: F) -> Result + where + F: FnOnce(&VmMachineRunning) -> R, + { + let vmdata = self.try_upgrade()?; + let status = vmdata.machine.read(); + let running = match &*status { + VmMachineState::Running(running) => running, + _ => { + return Err(RunError::ExitWithError(anyhow!( + "VM is not in Running state" + ))); + } + }; + Ok(f(running)) + } } impl Debug for VmDataWeak { diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs index 92fc163..5832385 100644 --- a/src/vm/machine/running.rs +++ b/src/vm/machine/running.rs @@ -8,7 +8,8 @@ use std::{ use alloc::vec::Vec; use crate::{ - RunError, TASK_STACK_SIZE, VmAddrSpace, arch::cpu::VCpu, data::VmDataWeak, vhal::cpu::CpuHardId, + RunError, TASK_STACK_SIZE, VmAddrSpace, arch::cpu::VCpu, data::VmDataWeak, vcpu::VCpuOp, + vhal::cpu::CpuHardId, }; pub struct VmMachineRunningCommon { @@ -44,7 +45,7 @@ impl VmMachineRunningCommon { Ok(cpu) } - pub fn run_cpu(&mut self, mut cpu: VCpu) -> anyhow::Result<()> { + pub fn run_cpu(&mut self, mut cpu: C) -> anyhow::Result<()> { let waiter = self.new_waiter(); let bind_id = cpu.bind_id(); @@ -61,7 +62,7 @@ impl VmMachineRunningCommon { let res = cpu.run(); if let Err(e) = res { if let Some(vm) = waiter.vm.upgrade() { - vm.set_err(RunError::ExitWithError(e)); + vm.set_err(e); } } waiter.running_cpu_count.fetch_sub(1, Ordering::SeqCst); From c1586519e6c2098f5bafc2de6e58e5d79a5a540a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 15:04:25 +0800 Subject: [PATCH 54/74] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20VmMachineRun?= =?UTF-8?q?ning=20=E5=92=8C=20VmStatusStopping=20=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=20VM=20=E5=81=9C=E6=AD=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/running.rs | 8 ++++++-- src/vm/data.rs | 3 ++- src/vm/machine/running.rs | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index 7430ed1..10f3f4a 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -18,10 +18,14 @@ impl VmMachineRunningOps for VmMachineRunning { type Stopping = VmStatusStopping; fn stop(self) -> Self::Stopping { - Self::Stopping {} + Self::Stopping { + vmspace: self.common.vmspace, + } } } -pub struct VmStatusStopping {} +pub struct VmStatusStopping { + vmspace: VmAddrSpace, +} impl VmMachineStoppingOps for VmStatusStopping {} diff --git a/src/vm/data.rs b/src/vm/data.rs index 894d60c..c35ec14 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -41,6 +41,7 @@ impl VmDataInner { VmMachineState::Running(running) => { let stopping = running.stop(); *status_guard = VmMachineState::Stopping(stopping); + self.status.store(VMStatus::Stopping); Ok(()) } other => { @@ -51,7 +52,7 @@ impl VmDataInner { } pub fn wait(&self) -> anyhow::Result<()> { - while self.is_active() { + while !matches!(self.status(), VMStatus::Stopped) { std::thread::sleep(std::time::Duration::from_millis(50)); } self.run_result() diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs index 5832385..4b2edaa 100644 --- a/src/vm/machine/running.rs +++ b/src/vm/machine/running.rs @@ -67,6 +67,7 @@ impl VmMachineRunningCommon { } waiter.running_cpu_count.fetch_sub(1, Ordering::SeqCst); if waiter.running_cpu_count.load(Ordering::SeqCst) == 0 { + info!("All vCPUs have exited, VM set stopped."); waiter.vm.set_stopped(); } }) From f3c369d0df089787d9d38053e188bfad7892f0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 15:42:13 +0800 Subject: [PATCH 55/74] =?UTF-8?q?refactor:=20=E6=B8=85=E7=90=86=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=E5=AF=BC=E5=85=A5=E5=92=8C=E7=BB=93?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 27 ++------------------------- src/arch/aarch64/mod.rs | 4 ---- src/arch/aarch64/vm/inited.rs | 12 ++---------- src/arch/aarch64/vm/unint.rs | 4 ++-- src/fdt/mod.rs | 3 --- src/vhal/cpu.rs | 2 +- src/vhal/mod.rs | 1 - src/vm/data.rs | 5 ++--- src/vm/machine/mod.rs | 2 ++ src/vm/mod.rs | 8 +------- 10 files changed, 12 insertions(+), 56 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 28d72ae..0749847 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -1,5 +1,4 @@ -use core::{fmt::Display, ops::Deref, sync::atomic::AtomicBool}; -use std::sync::Arc; +use core::{fmt::Display, ops::Deref}; use aarch64_cpu::registers::*; use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; @@ -11,7 +10,7 @@ use crate::{ vcpu::{VCpuCommon, VCpuOp}, vhal::{ ArchCpuData, - cpu::{CpuHardId, CpuId, HCpuExclusive}, + cpu::{CpuHardId, CpuId}, }, }; @@ -82,28 +81,6 @@ impl arm_vcpu::CpuHal for VCpuHal { } } -#[derive(Clone)] -pub struct VCpuHandle { - is_active: Arc, -} - -impl VCpuHandle { - pub fn new() -> Self { - VCpuHandle { - is_active: Arc::new(AtomicBool::new(true)), - } - } - - pub fn stop(&self) { - self.is_active - .store(false, core::sync::atomic::Ordering::Release); - } - - pub fn is_active(&self) -> bool { - self.is_active.load(core::sync::atomic::Ordering::Acquire) - } -} - pub struct VCpu { pub vcpu: arm_vcpu::Aarch64VCpu, common: VCpuCommon, diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 58b5757..a3c06f0 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,5 +1,3 @@ -use aarch64_cpu_ext::cache::{CacheOp, dcache_range}; - pub mod cpu; mod hal; mod vm; @@ -7,5 +5,3 @@ mod vm; pub use cpu::HCpu; pub use hal::Hal; pub use vm::*; - -type AddrSpace = axaddrspace::AddrSpace; diff --git a/src/arch/aarch64/vm/inited.rs b/src/arch/aarch64/vm/inited.rs index cc82915..a9cfdf1 100644 --- a/src/arch/aarch64/vm/inited.rs +++ b/src/arch/aarch64/vm/inited.rs @@ -1,16 +1,8 @@ -use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::{ - os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, - string::String, - vec::Vec, -}; - -use arm_vcpu::Aarch64VCpuSetupConfig; +use std::{string::String, vec::Vec}; use crate::{ - GuestPhysAddr, TASK_STACK_SIZE, VmAddrSpace, VmMachineInitedOps, VmMachineRunningCommon, + GuestPhysAddr, VmAddrSpace, VmMachineInitedOps, VmMachineRunningCommon, arch::{VmMachineRunning, cpu::VCpu}, - config::AxVMConfig, data::VmDataWeak, vm::VmId, }; diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index 0d6089f..c1315ee 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -1,4 +1,4 @@ -use core::{ops::Deref, sync::atomic::Ordering}; +use core::ops::Deref; use alloc::vec::Vec; use arm_vcpu::Aarch64VCpuSetupConfig; @@ -99,7 +99,7 @@ impl VmMachineUninit { self.config.id, self.config.name ); for memory_cfg in &self.config.memory_regions { - let m = vmspace.new_memory(memory_cfg); + vmspace.new_memory(memory_cfg)?; } vmspace.load_kernel_image(&self.config)?; diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index ce05aed..d4258ae 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -82,9 +82,6 @@ impl FdtBuilder { self.fdt.remove_node(&path).unwrap(); } - let root_address_cells = self.fdt.root().address_cells().unwrap_or(2); - let root_size_cells = self.fdt.root().size_cells().unwrap_or(2); - for (i, m) in memories.enumerate() { let mut node = Node::new(&format!("memory@{i}")); let mut prop = Property::new("device_type", vec![]); diff --git a/src/vhal/cpu.rs b/src/vhal/cpu.rs index c30f799..a68a933 100644 --- a/src/vhal/cpu.rs +++ b/src/vhal/cpu.rs @@ -39,7 +39,7 @@ impl HCpuExclusive { where F: FnOnce(&HCpu) -> R, { - for (id, cpu) in PRE_CPU.iter() { + for (_id, cpu) in PRE_CPU.iter() { if cpu.id == self.0 { return f(cpu); } diff --git a/src/vhal/mod.rs b/src/vhal/mod.rs index f6f1bd8..a2be9a7 100644 --- a/src/vhal/mod.rs +++ b/src/vhal/mod.rs @@ -5,7 +5,6 @@ use axstd::{ }; use bitmap_allocator::BitAlloc; use core::sync::atomic::{AtomicUsize, Ordering}; -use spin::Mutex; use crate::{ HostPhysAddr, HostVirtAddr, TASK_STACK_SIZE, diff --git a/src/vm/data.rs b/src/vm/data.rs index c35ec14..28c71a9 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -11,8 +11,7 @@ use spin::RwLock; use crate::{ AxVMConfig, RunError, VmId, VmMachineInitedOps, VmMachineRunningOps, VmMachineUninitOps, - arch::{VmMachineInited, VmMachineRunning, VmMachineUninit}, - config::AxVCpuConfig, + arch::{VmMachineRunning, VmMachineUninit}, vm::machine::{AtomicState, VMStatus, VmMachineState}, }; @@ -75,7 +74,7 @@ impl VmDataInner { } pub(crate) fn run_result(&self) -> anyhow::Result<()> { - let mut guard = self.error.write(); + let guard = self.error.read(); let res = guard.clone(); match res { Some(err) => match err { diff --git a/src/vm/machine/mod.rs b/src/vm/machine/mod.rs index 19c6794..1b6ed24 100644 --- a/src/vm/machine/mod.rs +++ b/src/vm/machine/mod.rs @@ -19,6 +19,7 @@ pub trait VmMachineUninitOps { Self: Sized; } +#[allow(unused)] pub trait VmMachineInitedOps { type Running: VmMachineRunningOps; fn id(&self) -> VmId; @@ -49,6 +50,7 @@ pub enum VmMachineState { Inited(VmMachineInited), Running(VmMachineRunning), Switching, + #[allow(unused)] Stopping(VmStatusStopping), Stopped, } diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 94c59eb..3281d27 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,10 +1,4 @@ -use core::fmt; - -use alloc::sync::Arc; -use spin::{Mutex, RwLock}; -use std::thread; - -use crate::{AxVMConfig, arch::VmMachineInited, data::VmData, vm::data::VmDataWeak}; +use crate::{AxVMConfig, data::VmData}; mod addrspace; pub(crate) mod data; From d1cf7b05c4d977570047295f5043fb69b337fe2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 15:45:04 +0800 Subject: [PATCH 56/74] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=E5=B8=B8=E9=87=8F=E5=92=8C=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/inited.rs | 5 ----- src/arch/aarch64/vm/running.rs | 4 ++-- src/arch/aarch64/vm/unint.rs | 5 ----- src/vm/addrspace.rs | 24 ++++++++++++------------ src/vm/machine/mod.rs | 11 ++--------- 5 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/arch/aarch64/vm/inited.rs b/src/arch/aarch64/vm/inited.rs index a9cfdf1..08e8843 100644 --- a/src/arch/aarch64/vm/inited.rs +++ b/src/arch/aarch64/vm/inited.rs @@ -7,11 +7,6 @@ use crate::{ vm::VmId, }; -const VM_ASPACE_BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0); -const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; -const VM_ASPACE_END: GuestPhysAddr = - GuestPhysAddr::from_usize(VM_ASPACE_BASE.as_usize() + VM_ASPACE_SIZE); - pub struct VmMachineInited { pub id: VmId, pub name: String, diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index 10f3f4a..fbd0749 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -19,13 +19,13 @@ impl VmMachineRunningOps for VmMachineRunning { fn stop(self) -> Self::Stopping { Self::Stopping { - vmspace: self.common.vmspace, + _vmspace: self.common.vmspace, } } } pub struct VmStatusStopping { - vmspace: VmAddrSpace, + _vmspace: VmAddrSpace, } impl VmMachineStoppingOps for VmStatusStopping {} diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index c1315ee..2414471 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -11,11 +11,6 @@ use crate::{ fdt::FdtBuilder, }; -const VM_ASPACE_BASE: GuestPhysAddr = GuestPhysAddr::from_usize(0); -const VM_ASPACE_SIZE: usize = 0x7fff_ffff_f000; -const VM_ASPACE_END: GuestPhysAddr = - GuestPhysAddr::from_usize(VM_ASPACE_BASE.as_usize() + VM_ASPACE_SIZE); - pub struct VmMachineUninit { config: AxVMConfig, pt_levels: usize, diff --git a/src/vm/addrspace.rs b/src/vm/addrspace.rs index dfc8d87..6a018a3 100644 --- a/src/vm/addrspace.rs +++ b/src/vm/addrspace.rs @@ -364,18 +364,18 @@ impl GuestMemory { self.layout.size() } - pub fn to_vec(&self) -> Vec { - let mut result = vec![]; - let g = self.aspace.lock(); - let hva = g - .translated_byte_buffer(self.gpa.as_usize().into(), self.size()) - .expect("Failed to translate memory region"); - for buff in hva { - result.extend_from_slice(buff); - } - result.resize(self.size(), 0); - result - } + // pub fn to_vec(&self) -> Vec { + // let mut result = vec![]; + // let g = self.aspace.lock(); + // let hva = g + // .translated_byte_buffer(self.gpa.as_usize().into(), self.size()) + // .expect("Failed to translate memory region"); + // for buff in hva { + // result.extend_from_slice(buff); + // } + // result.resize(self.size(), 0); + // result + // } } impl Drop for GuestMemory { diff --git a/src/vm/machine/mod.rs b/src/vm/machine/mod.rs index 1b6ed24..114cc94 100644 --- a/src/vm/machine/mod.rs +++ b/src/vm/machine/mod.rs @@ -11,6 +11,7 @@ mod running; pub(crate) use running::*; +#[allow(unused)] pub trait VmMachineUninitOps { type Inited: VmMachineInitedOps; fn new(config: AxVMConfig) -> Self; @@ -29,6 +30,7 @@ pub trait VmMachineInitedOps { Self: Sized; } +#[allow(unused)] pub trait VmMachineRunningOps { type Stopping: VmMachineStoppingOps; fn stop(self) -> Self::Stopping; @@ -36,15 +38,6 @@ pub trait VmMachineRunningOps { pub trait VmMachineStoppingOps {} -/// A lightweight container that stores the identifier and human readable name -/// for a VM instance. Shared between the public [`Vm`] object and the -/// background machine thread for logging and observability. -#[derive(Debug, Clone)] -pub struct VmCommon { - pub id: VmId, - pub name: String, -} - pub enum VmMachineState { Uninit(VmMachineUninit), Inited(VmMachineInited), From 731be1fd53880ed1f1e9f4f0d195cf304377171f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 16:13:50 +0800 Subject: [PATCH 57/74] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20CPU=20=E5=90=AF=E5=8A=A8=E5=92=8C=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E7=AE=A1=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E6=B8=85?= =?UTF-8?q?=E7=90=86=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 10 +++++-- src/arch/aarch64/vm/inited.rs | 2 +- src/arch/aarch64/vm/running.rs | 22 +++++++++++++-- src/vhal/cpu.rs | 4 --- src/vm/data.rs | 49 ++++++++++++++++++++++------------ src/vm/machine/mod.rs | 7 ----- src/vm/machine/running.rs | 6 +---- 7 files changed, 62 insertions(+), 38 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 0749847..465578f 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -136,12 +136,18 @@ impl VCpuOp for VCpu { arm_vcpu::AxVCpuExitReason::MmioWrite { addr, width, data } => todo!(), arm_vcpu::AxVCpuExitReason::SysRegRead { addr, reg } => todo!(), arm_vcpu::AxVCpuExitReason::SysRegWrite { addr, value } => todo!(), - arm_vcpu::AxVCpuExitReason::ExternalInterrupt => todo!(), + arm_vcpu::AxVCpuExitReason::ExternalInterrupt => { + axhal::irq::irq_handler(0); + } arm_vcpu::AxVCpuExitReason::CpuUp { target_cpu, entry_point, arg, - } => todo!(), + } => { + self.vm()?.with_machine_running_mut(|running| { + running.cpu_up(CpuHardId::new(target_cpu as _), entry_point, arg) + })??; + } arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), arm_vcpu::AxVCpuExitReason::SystemDown => { info!("vCPU {} requested system shutdown", self.bind_id()); diff --git a/src/arch/aarch64/vm/inited.rs b/src/arch/aarch64/vm/inited.rs index 08e8843..410b518 100644 --- a/src/arch/aarch64/vm/inited.rs +++ b/src/arch/aarch64/vm/inited.rs @@ -1,7 +1,7 @@ use std::{string::String, vec::Vec}; use crate::{ - GuestPhysAddr, VmAddrSpace, VmMachineInitedOps, VmMachineRunningCommon, + VmAddrSpace, VmMachineInitedOps, VmMachineRunningCommon, arch::{VmMachineRunning, cpu::VCpu}, data::VmDataWeak, vm::VmId, diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index fbd0749..bf5fe79 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -1,8 +1,8 @@ use fdt_edit::NodeRef; use crate::{ - VmAddrSpace, VmMachineRunningCommon, VmMachineRunningOps, VmMachineStoppingOps, - arch::vm::DevMapConfig, + GuestPhysAddr, VmAddrSpace, VmMachineRunningCommon, VmMachineRunningOps, VmMachineStoppingOps, + arch::vm::DevMapConfig, vhal::cpu::CpuHardId, }; /// Data needed when VM is running @@ -12,6 +12,24 @@ pub struct VmMachineRunning { impl VmMachineRunning { fn handle_node_regs(dev_vec: &mut [DevMapConfig], node: &NodeRef<'_>) {} + pub fn cpu_up( + &mut self, + target_cpu: CpuHardId, + entry_point: GuestPhysAddr, + arg: u64, + ) -> anyhow::Result<()> { + let mut cpu = self + .common + .cpus + .remove(&target_cpu) + .ok_or(anyhow!("No cpu {target_cpu} found"))?; + + cpu.vcpu.set_entry(entry_point.as_usize().into()).unwrap(); + cpu.vcpu.ctx_mut().gpr[0] = arg; + + self.common.run_cpu(cpu)?; + Ok(()) + } } impl VmMachineRunningOps for VmMachineRunning { diff --git a/src/vhal/cpu.rs b/src/vhal/cpu.rs index a68a933..1285a75 100644 --- a/src/vhal/cpu.rs +++ b/src/vhal/cpu.rs @@ -47,10 +47,6 @@ impl HCpuExclusive { panic!("CPU data not found for CPU ID {}", self.0); } - pub fn cpu_id(&self) -> CpuId { - self.0 - } - pub fn hard_id(&self) -> CpuHardId { self.with_cpu(|cpu| cpu.hard_id()) } diff --git a/src/vm/data.rs b/src/vm/data.rs index 28c71a9..ab1b300 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -158,6 +158,38 @@ impl VmData { inner: Arc::downgrade(&self.inner), } } + + pub(crate) fn with_machine_running(&self, f: F) -> Result + where + F: FnOnce(&VmMachineRunning) -> R, + { + let status = self.machine.read(); + let running = match &*status { + VmMachineState::Running(running) => running, + _ => { + return Err(RunError::ExitWithError(anyhow!( + "VM is not in Running state" + ))); + } + }; + Ok(f(running)) + } + + pub(crate) fn with_machine_running_mut(&self, f: F) -> Result + where + F: FnOnce(&mut VmMachineRunning) -> R, + { + let mut status = self.machine.write(); + let running = match &mut *status { + VmMachineState::Running(running) => running, + _ => { + return Err(RunError::ExitWithError(anyhow!( + "VM is not in Running state" + ))); + } + }; + Ok(f(running)) + } } impl From> for VmData { @@ -207,23 +239,6 @@ impl VmDataWeak { inner.status.store(VMStatus::Stopped); } } - - pub(crate) fn with_machine_running(&self, f: F) -> Result - where - F: FnOnce(&VmMachineRunning) -> R, - { - let vmdata = self.try_upgrade()?; - let status = vmdata.machine.read(); - let running = match &*status { - VmMachineState::Running(running) => running, - _ => { - return Err(RunError::ExitWithError(anyhow!( - "VM is not in Running state" - ))); - } - }; - Ok(f(running)) - } } impl Debug for VmDataWeak { diff --git a/src/vm/machine/mod.rs b/src/vm/machine/mod.rs index 114cc94..5c83822 100644 --- a/src/vm/machine/mod.rs +++ b/src/vm/machine/mod.rs @@ -1,4 +1,3 @@ -use alloc::string::String; use core::sync::atomic::{AtomicU8, Ordering}; use crate::{ @@ -48,12 +47,6 @@ pub enum VmMachineState { Stopped, } -impl VmMachineState { - pub fn is_active(&self) -> bool { - !matches!(self, VmMachineState::Stopping(_) | VmMachineState::Stopped) - } -} - /// Auxiliary wrapper that stores the current machine status in an atomically /// readable form so management threads can query it without synchronisation /// overhead. diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs index 4b2edaa..9a2c888 100644 --- a/src/vm/machine/running.rs +++ b/src/vm/machine/running.rs @@ -8,7 +8,7 @@ use std::{ use alloc::vec::Vec; use crate::{ - RunError, TASK_STACK_SIZE, VmAddrSpace, arch::cpu::VCpu, data::VmDataWeak, vcpu::VCpuOp, + TASK_STACK_SIZE, VmAddrSpace, arch::cpu::VCpu, data::VmDataWeak, vcpu::VCpuOp, vhal::cpu::CpuHardId, }; @@ -84,10 +84,6 @@ impl VmMachineRunningCommon { vm: self.vm.clone(), } } - - pub fn vmspace(&self) -> &VmAddrSpace { - &self.vmspace - } } struct Waiter { From bc4914ba001ef265ef10cc9853313a5c9614b887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 16:36:53 +0800 Subject: [PATCH 58/74] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20vCPU=20?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20CPU=20=E5=90=AF=E5=8A=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 1 + src/arch/aarch64/vm/running.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 465578f..9204446 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -118,6 +118,7 @@ impl VCpuOp for VCpu { info!("Starting vCPU {}", self.bind_id()); while self.is_active() { + debug!("vCPU {} entering guest", self.bind_id()); let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; debug!( "vCPU {} exited with reason: {:?}", diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index bf5fe79..3572450 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -12,6 +12,7 @@ pub struct VmMachineRunning { impl VmMachineRunning { fn handle_node_regs(dev_vec: &mut [DevMapConfig], node: &NodeRef<'_>) {} + pub fn cpu_up( &mut self, target_cpu: CpuHardId, @@ -25,7 +26,7 @@ impl VmMachineRunning { .ok_or(anyhow!("No cpu {target_cpu} found"))?; cpu.vcpu.set_entry(entry_point.as_usize().into()).unwrap(); - cpu.vcpu.ctx_mut().gpr[0] = arg; + cpu.vcpu.set_gpr(0, arg as _); self.common.run_cpu(cpu)?; Ok(()) From 323836b74ea97295542fe03d77ee64c9725ce427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Fri, 19 Dec 2025 17:35:05 +0800 Subject: [PATCH 59/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20CPU=20vCPU?= =?UTF-8?q?=20=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=20CPU=20=E5=90=AF=E5=8A=A8=E8=BF=87=E7=A8=8B=E7=9A=84?= =?UTF-8?q?=E5=8F=AF=E8=BF=BD=E8=B8=AA=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/running.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index 3572450..ba0dd3f 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -27,7 +27,7 @@ impl VmMachineRunning { cpu.vcpu.set_entry(entry_point.as_usize().into()).unwrap(); cpu.vcpu.set_gpr(0, arg as _); - + debug!("{:?}", cpu.vcpu); self.common.run_cpu(cpu)?; Ok(()) } From 5a2ad13389272ec32c07a1455feea0a9c6dcab2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 09:32:10 +0800 Subject: [PATCH 60/74] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20vCPU=20?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E8=BF=87=E7=A8=8B=E7=9A=84=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=EF=BC=8C=E4=BC=98=E5=8C=96=20CPU=20=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 2 + src/vm/data.rs | 122 +++++++++++++++++++++++++------------- src/vm/machine/running.rs | 12 +++- 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 9204446..add8c75 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -145,7 +145,9 @@ impl VCpuOp for VCpu { entry_point, arg, } => { + debug!("vCPU {} requested CPU {} up", self.bind_id(), target_cpu); self.vm()?.with_machine_running_mut(|running| { + debug!("vCPU {} is bringing up CPU {}", self.bind_id(), target_cpu); running.cpu_up(CpuHardId::new(target_cpu as _), entry_point, arg) })??; } diff --git a/src/vm/data.rs b/src/vm/data.rs index ab1b300..b4573f3 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -106,51 +106,75 @@ impl VmData { } pub fn init(&self) -> anyhow::Result<()> { - let mut status_guard = self.machine.write(); - match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + let next; + let res; + let next_state; + + match self.replace_status(VmMachineState::Switching) { VmMachineState::Uninit(uninit) => { - let init = match uninit.init(self.downgrade()) { - Ok(inited) => inited, + match uninit.init(self.downgrade()) { + Ok(inited) => { + next_state = Some(VMStatus::Inited); + res = Ok(()); + next = VmMachineState::Inited(inited); + } Err(e) => { self.set_err(RunError::ExitWithError(anyhow!("{e}"))); - *status_guard = VmMachineState::Stopped; - self.status.store(VMStatus::Stopped); - return Err(e); + next_state = Some(VMStatus::Stopped); + next = VmMachineState::Stopped; + res = Err(e); } }; - *status_guard = VmMachineState::Inited(init); - self.status.store(VMStatus::Inited); - Ok(()) } other => { - *status_guard = other; - Err(anyhow::anyhow!("VM is not in Uninit state")) + next = other; + next_state = None; + res = Err(anyhow::anyhow!("VM is not in Uninit state")); } } + self.replace_status(next); + if let Some(status) = next_state { + self.status.store(status); + } + res + } + + fn replace_status(&self, new_status: VmMachineState) -> VmMachineState { + let mut status_guard = self.machine.write(); + core::mem::replace(&mut *status_guard, new_status) } pub fn start(&self) -> anyhow::Result<()> { let data = self.downgrade(); - let mut status_guard = self.machine.write(); - match core::mem::replace(&mut *status_guard, VmMachineState::Switching) { + let next_state; + let res; + let next = match self.replace_status(VmMachineState::Switching) { VmMachineState::Inited(init) => match init.start(data) { Ok(running) => { - *status_guard = VmMachineState::Running(running); - self.status.store(VMStatus::Running); - Ok(()) + next_state = Some(VMStatus::Running); + res = Ok(()); + VmMachineState::Running(running) } Err(e) => { self.set_err(RunError::ExitWithError(anyhow!("{e}"))); - *status_guard = VmMachineState::Stopped; - self.status.store(VMStatus::Stopped); - Err(e) + + next_state = Some(VMStatus::Stopped); + res = Err(e); + VmMachineState::Stopped } }, other => { - *status_guard = other; - Err(anyhow::anyhow!("VM is not in Init state")) + next_state = None; + + res = Err(anyhow::anyhow!("VM is not in Init state")); + other } + }; + self.replace_status(next); + if let Some(status) = next_state { + self.status.store(status); } + res } pub fn downgrade(&self) -> VmDataWeak { @@ -163,32 +187,46 @@ impl VmData { where F: FnOnce(&VmMachineRunning) -> R, { - let status = self.machine.read(); - let running = match &*status { - VmMachineState::Running(running) => running, - _ => { - return Err(RunError::ExitWithError(anyhow!( - "VM is not in Running state" - ))); - } - }; - Ok(f(running)) + loop { + let status = self.machine.read(); + let running = match &*status { + VmMachineState::Running(running) => running, + VmMachineState::Switching => { + drop(status); + std::thread::yield_now(); + continue; + } + _ => { + return Err(RunError::ExitWithError(anyhow!( + "VM is not in Running state" + ))); + } + }; + return Ok(f(running)); + } } pub(crate) fn with_machine_running_mut(&self, f: F) -> Result where F: FnOnce(&mut VmMachineRunning) -> R, { - let mut status = self.machine.write(); - let running = match &mut *status { - VmMachineState::Running(running) => running, - _ => { - return Err(RunError::ExitWithError(anyhow!( - "VM is not in Running state" - ))); - } - }; - Ok(f(running)) + loop { + let mut status = self.machine.write(); + let running = match &mut *status { + VmMachineState::Running(running) => running, + VmMachineState::Switching => { + drop(status); + std::thread::yield_now(); + continue; + } + _ => { + return Err(RunError::ExitWithError(anyhow!( + "VM is not in Running state" + ))); + } + }; + return Ok(f(running)); + } } } diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs index 9a2c888..d94eead 100644 --- a/src/vm/machine/running.rs +++ b/src/vm/machine/running.rs @@ -1,4 +1,4 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{ collections::btree_map::BTreeMap, os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, @@ -47,7 +47,8 @@ impl VmMachineRunningCommon { pub fn run_cpu(&mut self, mut cpu: C) -> anyhow::Result<()> { let waiter = self.new_waiter(); - + let started = Arc::new(AtomicBool::new(false)); + let started_clone = started.clone(); let bind_id = cpu.bind_id(); std::thread::Builder::new() .name(format!("init-cpu-{}", bind_id)) @@ -58,9 +59,11 @@ impl VmMachineRunningCommon { set_current_affinity(AxCpuMask::one_shot(bind_id.raw())), "Initialize CPU affinity failed!" ); + started_clone.store(true, Ordering::SeqCst); info!("Starting VCpu {} on {}", cpu.hard_id(), bind_id); let res = cpu.run(); if let Err(e) = res { + info!("vCPU {} exited with error: {e}", bind_id); if let Some(vm) = waiter.vm.upgrade() { vm.set_err(e); } @@ -72,7 +75,10 @@ impl VmMachineRunningCommon { } }) .map_err(|e| anyhow!("{e:?}"))?; - + debug!("Waiting for CPU {} to start", bind_id); + while !started.load(Ordering::SeqCst) { + std::thread::yield_now(); + } Ok(()) } From a46d06be1d7ed20dd25117bc8513b03f2d3bc27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 09:45:20 +0800 Subject: [PATCH 61/74] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E6=9C=BA=E7=AD=89=E5=BE=85=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20CPU=20=E5=90=AF=E5=8A=A8=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E7=8A=B6=E6=80=81=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vm/data.rs | 15 ++++++++++++++- src/vm/machine/running.rs | 19 +++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/vm/data.rs b/src/vm/data.rs index b4573f3..0098ad0 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -52,8 +52,11 @@ impl VmDataInner { pub fn wait(&self) -> anyhow::Result<()> { while !matches!(self.status(), VMStatus::Stopped) { - std::thread::sleep(std::time::Duration::from_millis(50)); + // TODO: arceos bug, sleep never wakes up + // std::thread::sleep(std::time::Duration::from_millis(50)); + std::thread::yield_now(); } + info!("VM {} ({}) has stopped.", self.id, self.name); self.run_result() } @@ -277,6 +280,16 @@ impl VmDataWeak { inner.status.store(VMStatus::Stopped); } } + + pub(crate) fn wait_for_running(&self) { + while let Some(inner) = self.upgrade() { + let status = inner.status.load(); + if status >= VMStatus::Running { + break; + } + std::thread::yield_now(); + } + } } impl Debug for VmDataWeak { diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs index d94eead..a207312 100644 --- a/src/vm/machine/running.rs +++ b/src/vm/machine/running.rs @@ -47,8 +47,8 @@ impl VmMachineRunningCommon { pub fn run_cpu(&mut self, mut cpu: C) -> anyhow::Result<()> { let waiter = self.new_waiter(); - let started = Arc::new(AtomicBool::new(false)); - let started_clone = started.clone(); + let thread_ok = Arc::new(AtomicBool::new(false)); + let thread_ok_clone = thread_ok.clone(); let bind_id = cpu.bind_id(); std::thread::Builder::new() .name(format!("init-cpu-{}", bind_id)) @@ -59,8 +59,15 @@ impl VmMachineRunningCommon { set_current_affinity(AxCpuMask::one_shot(bind_id.raw())), "Initialize CPU affinity failed!" ); - started_clone.store(true, Ordering::SeqCst); - info!("Starting VCpu {} on {}", cpu.hard_id(), bind_id); + thread_ok_clone.store(true, Ordering::SeqCst); + + info!( + "vCPU {} on {} ready, waiting for running...", + cpu.bind_id(), + bind_id + ); + waiter.vm.wait_for_running(); + info!("VCpu {} on {} run", cpu.hard_id(), bind_id); let res = cpu.run(); if let Err(e) = res { info!("vCPU {} exited with error: {e}", bind_id); @@ -75,8 +82,8 @@ impl VmMachineRunningCommon { } }) .map_err(|e| anyhow!("{e:?}"))?; - debug!("Waiting for CPU {} to start", bind_id); - while !started.load(Ordering::SeqCst) { + debug!("Waiting for CPU {} thread", bind_id); + while !thread_ok.load(Ordering::SeqCst) { std::thread::yield_now(); } Ok(()) From 91852c4a3b838b7f4ae3f3b5930bb774cb3ef4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 10:29:51 +0800 Subject: [PATCH 62/74] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20vCPU=20?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20CPU=20=E8=BF=90=E8=A1=8C=E6=97=B6=E7=9A=84=20VM=20ID=20?= =?UTF-8?q?=E8=BF=BD=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 20 ++++++++++++++++++-- src/arch/aarch64/vm/running.rs | 1 - src/vcpu/mod.rs | 8 ++++++-- src/vm/data.rs | 6 ++++++ src/vm/machine/running.rs | 1 + 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index add8c75..dd19c2e 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -1,4 +1,7 @@ -use core::{fmt::Display, ops::Deref}; +use core::{ + fmt::{self, Debug, Display}, + ops::Deref, +}; use aarch64_cpu::registers::*; use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; @@ -116,7 +119,9 @@ impl VCpuOp for VCpu { fn run(&mut self) -> Result<(), RunError> { info!("Starting vCPU {}", self.bind_id()); - + self.vcpu + .setup_current_cpu(self.vm_id().into()) + .map_err(|e| anyhow!("{e}"))?; while self.is_active() { debug!("vCPU {} entering guest", self.bind_id()); let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; @@ -150,6 +155,7 @@ impl VCpuOp for VCpu { debug!("vCPU {} is bringing up CPU {}", self.bind_id(), target_cpu); running.cpu_up(CpuHardId::new(target_cpu as _), entry_point, arg) })??; + self.vcpu.set_gpr(0, 0); } arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), arm_vcpu::AxVCpuExitReason::SystemDown => { @@ -179,3 +185,13 @@ impl Deref for VCpu { &self.common } } + +impl Debug for VCpu { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VCpu") + .field("bind_id", &self.bind_id()) + .field("hard_id", &self.hard_id()) + .field("vcpu", &self.vcpu) + .finish() + } +} diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index ba0dd3f..d6dbd45 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -27,7 +27,6 @@ impl VmMachineRunning { cpu.vcpu.set_entry(entry_point.as_usize().into()).unwrap(); cpu.vcpu.set_gpr(0, arg as _); - debug!("{:?}", cpu.vcpu); self.common.run_cpu(cpu)?; Ok(()) } diff --git a/src/vcpu/mod.rs b/src/vcpu/mod.rs index b71e744..83ba201 100644 --- a/src/vcpu/mod.rs +++ b/src/vcpu/mod.rs @@ -1,11 +1,11 @@ use crate::{ - CpuId, RunError, + CpuId, RunError, VmId, arch::HCpu, data::{VmData, VmDataWeak}, vhal::cpu::{CpuHardId, HCpuExclusive}, }; -pub trait VCpuOp: Send + 'static{ +pub trait VCpuOp: core::fmt::Debug + Send + 'static { fn bind_id(&self) -> CpuId; fn hard_id(&self) -> CpuHardId; fn run(&mut self) -> Result<(), RunError>; @@ -18,6 +18,10 @@ pub struct VCpuCommon { } impl VCpuCommon { + pub fn vm_id(&self) -> VmId { + self.vm.id() + } + pub fn new_exclusive(bind: Option, vm: VmDataWeak) -> anyhow::Result { let hcpu = HCpuExclusive::try_new(bind) .ok_or_else(|| anyhow!("Failed to allocate cpu with id `{bind:?}`"))?; diff --git a/src/vm/data.rs b/src/vm/data.rs index 0098ad0..07f7964 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -182,6 +182,7 @@ impl VmData { pub fn downgrade(&self) -> VmDataWeak { VmDataWeak { + id: self.id(), inner: Arc::downgrade(&self.inner), } } @@ -249,10 +250,15 @@ impl Deref for VmData { #[derive(Clone)] pub struct VmDataWeak { + id: VmId, inner: Weak, } impl VmDataWeak { + pub fn id(&self) -> VmId { + self.id + } + pub fn upgrade(&self) -> Option { Some(self.inner.upgrade()?.into()) } diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs index a207312..0ab9a17 100644 --- a/src/vm/machine/running.rs +++ b/src/vm/machine/running.rs @@ -68,6 +68,7 @@ impl VmMachineRunningCommon { ); waiter.vm.wait_for_running(); info!("VCpu {} on {} run", cpu.hard_id(), bind_id); + // debug!("\n{:#x?}", cpu); let res = cpu.run(); if let Err(e) = res { info!("vCPU {} exited with error: {e}", bind_id); From 9725b8336cfdf6ceb677f20724f7a83ba23e3dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 11:02:33 +0800 Subject: [PATCH 63/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9=20ini?= =?UTF-8?q?trd=20=E7=9A=84=E6=94=AF=E6=8C=81=EF=BC=8C=E5=A2=9E=E5=BC=BA=20?= =?UTF-8?q?FDT=20=E6=9E=84=E5=BB=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/unint.rs | 6 ++++++ src/fdt/mod.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index 2414471..6e53024 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -73,6 +73,10 @@ impl VmMachineUninit { } } + if self.pt_levels == 3 { + self.pa_max = self.pa_max.min(0x8000000000); + } + debug!( "VM {} ({}) vCPU count: {}, \n Max Guest Page Table Levels: {}\n Max PA: {:#x}", self.config.id, self.config.name, vcpu_count, self.pt_levels, self.pa_max @@ -101,6 +105,8 @@ impl VmMachineUninit { let mut fdt = FdtBuilder::new()?; fdt.setup_cpus(cpus.iter().map(|c| c.deref()))?; fdt.setup_memory(vmspace.memories().iter())?; + fdt.setup_initrd(None)?; + let dtb_data = fdt.build()?; let dtb_addr = vmspace.load_dtb(&dtb_data)?; diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index d4258ae..a6dfbb3 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use fdt_edit::{Fdt, FdtData, Node, Property, RegInfo, Status}; -use crate::{GuestMemory, vcpu::VCpuCommon, vhal::cpu::CpuHardId}; +use crate::{GuestMemory, GuestPhysAddr, vcpu::VCpuCommon, vhal::cpu::CpuHardId}; pub(crate) fn fdt_edit() -> Option { let addr = axhal::dtb::get_bootarg(); @@ -100,4 +100,36 @@ impl FdtBuilder { Ok(()) } + + pub fn setup_initrd(&mut self, initrd: Option<(GuestPhysAddr, usize)>) -> anyhow::Result<()> { + let mut node = self + .fdt + .get_by_path_mut("/chosen") + .ok_or_else(|| anyhow::anyhow!("No /chosen node found"))?; + + let Some(initrd) = initrd else { + node.node.remove_property("linux,initrd-start"); + node.node.remove_property("linux,initrd-end"); + return Ok(()); + }; + + let cells = node.ctx.parent_address_cells(); + let (initrd_start, initrd_end) = (initrd.0.as_usize(), initrd.0.as_usize() + initrd.1); + + let mut prop_s = Property::new("linux,initrd-start", vec![]); + let mut prop_e = Property::new("linux,initrd-end", vec![]); + + if cells == 2 { + prop_s.set_u32_ls(&[initrd_start as u32]); + prop_e.set_u32_ls(&[initrd_end as u32]); + } else { + prop_s.set_u64(initrd_start as _); + prop_e.set_u64(initrd_end as _); + } + + node.node.add_property(prop_s); + node.node.add_property(prop_e); + + Ok(()) + } } From b735d46d3b16f572c2fed98773d5f7f712e65452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 12:33:02 +0800 Subject: [PATCH 64/74] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20FdtBuilder?= =?UTF-8?q?=20=E7=9A=84=20setup=5Finitrd=20=E6=96=B9=E6=B3=95=E4=B8=BA=20s?= =?UTF-8?q?etup=5Fchosen=EF=BC=8C=E5=A2=9E=E5=BC=BA=E5=AF=B9=E5=BC=95?= =?UTF-8?q?=E5=AF=BC=E5=8F=82=E6=95=B0=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/vm/unint.rs | 2 +- src/fdt/mod.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index 6e53024..d83c3d3 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -105,7 +105,7 @@ impl VmMachineUninit { let mut fdt = FdtBuilder::new()?; fdt.setup_cpus(cpus.iter().map(|c| c.deref()))?; fdt.setup_memory(vmspace.memories().iter())?; - fdt.setup_initrd(None)?; + fdt.setup_chosen(None)?; let dtb_data = fdt.build()?; diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index a6dfbb3..844b5ab 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -101,7 +101,7 @@ impl FdtBuilder { Ok(()) } - pub fn setup_initrd(&mut self, initrd: Option<(GuestPhysAddr, usize)>) -> anyhow::Result<()> { + pub fn setup_chosen(&mut self, initrd: Option<(GuestPhysAddr, usize)>) -> anyhow::Result<()> { let mut node = self .fdt .get_by_path_mut("/chosen") @@ -130,6 +130,13 @@ impl FdtBuilder { node.node.add_property(prop_s); node.node.add_property(prop_e); + if let Some(args) = node.node.get_property_mut("bootargs") + && let Some(s) = args.as_str() + { + let bootargs = s.replace(" ro ", " rw "); + args.set_string(&bootargs); + } + Ok(()) } } From ce4ce80569cdf36a6b2622f0d092d3b417dc9c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 12:52:55 +0800 Subject: [PATCH 65/74] =?UTF-8?q?feat:=20=E9=87=8D=E5=91=BD=E5=90=8D=20Mem?= =?UTF-8?q?oryKind=20=E6=9E=9A=E4=B8=BE=E4=B8=AD=E7=9A=84=20Passthrough=20?= =?UTF-8?q?=E4=B8=BA=20Reserved=EF=BC=8C=E5=A2=9E=E5=BC=BA=E5=86=85?= =?UTF-8?q?=E5=AD=98=E5=8C=BA=E5=9F=9F=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.rs | 18 ++++-------------- src/vm/addrspace.rs | 2 +- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/config.rs b/src/config.rs index ac728a0..b59beec 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,16 +13,6 @@ pub use axvmconfig::{ use crate::vhal::cpu::CpuId; -/// A part of `AxVMConfig`, which represents a `VCpu`. -#[derive(Clone, Copy, Debug, Default)] -pub struct AxVCpuConfig { - // pub arch_config: AxArchVCpuConfig, - /// The entry address in GPA for the Bootstrap Processor (BSP). - pub bsp_entry: GuestPhysAddr, - /// The entry address in GPA for the Application Processor (AP). - pub ap_entry: GuestPhysAddr, -} - #[derive(Debug, Default, Clone)] pub struct VMImageConfig { pub gpa: Option, @@ -44,11 +34,11 @@ pub struct VMImagesConfig { #[derive(Debug, Clone)] pub enum MemoryKind { - /// Use identical memory regions + /// Use identical memory regions, i.e., HPA == GPA Identical { size: usize }, - /// Use memory regions mapped from host physical address - Passthrough { hpa: HostPhysAddr, size: usize }, - /// Use fixed memory regions + /// Use host reserved memory regions, i.e., HPA == GPA + Reserved { hpa: HostPhysAddr, size: usize }, + /// Use fixed address memory regions, i.e., HPA != GPA Vmem { gpa: GuestPhysAddr, size: usize }, } diff --git a/src/vm/addrspace.rs b/src/vm/addrspace.rs index 6a018a3..1e4119e 100644 --- a/src/vm/addrspace.rs +++ b/src/vm/addrspace.rs @@ -91,7 +91,7 @@ impl VmAddrSpace { ) .unwrap(); } - MemoryKind::Passthrough { hpa, size } => { + MemoryKind::Reserved { hpa, size } => { hva = phys_to_virt(*hpa); _gpa = GuestPhysAddr::from_usize(hva.as_usize()); _size = *size; From e1d53266465aede70540bb536dbfe4d34becd148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 14:28:31 +0800 Subject: [PATCH 66/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20x86=5F64=20?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E6=94=AF=E6=8C=81=EF=BC=8C=E5=8C=85=E5=90=AB?= =?UTF-8?q?=20CPU=20=E5=92=8C=20HAL=20=E6=A8=A1=E5=9D=97=E7=9A=84=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 6 +- src/arch/aarch64/cpu.rs | 6 ++ src/arch/aarch64/vm/unint.rs | 1 + src/arch/x86_64/cpu.rs | 196 +++++++++++++++++++++++++++++++++++ src/arch/x86_64/hal.rs | 39 +++++++ src/arch/x86_64/mod.rs | 3 +- 6 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 src/arch/x86_64/cpu.rs create mode 100644 src/arch/x86_64/hal.rs diff --git a/Cargo.toml b/Cargo.toml index b695f17..63e5c0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,10 @@ anyhow = {version = "1.0", default-features = false} cfg-if = "1.0" lazyinit = "0.2" log = "0.4" +ranges-ext.workspace = true spin = "0.10" thiserror = {version = "2", default-features = false} timer_list = "0.1" -ranges-ext.workspace = true # System independent crates provided by ArceOS. axerrno = "0.1.0" @@ -31,7 +31,6 @@ percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true vm-fdt.workspace = true - # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" # axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} @@ -45,7 +44,8 @@ axvmconfig = {version = "0.1", default-features = false} fdt-edit = "0.1" [target.'cfg(target_arch = "x86_64")'.dependencies] -# x86_vcpu = "0.1" +x86_vcpu = "0.1" +axplat-x86-qemu-q35.workspace = true [target.'cfg(target_arch = "riscv64")'.dependencies] # riscv_vcpu = "0.1" diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index dd19c2e..38b4825 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -102,10 +102,15 @@ impl VCpu { let vcpu = arm_vcpu::Aarch64VCpu::new(Aarch64VCpuCreateConfig { mpidr_el1: hard_id.raw() as u64, dtb_addr: dtb_addr.as_usize(), + pt_level: 4, }) .unwrap(); Ok(VCpu { vcpu, common }) } + + pub fn set_pt_level(&mut self, level: usize) { + self.vcpu.pt_level = level; + } } impl VCpuOp for VCpu { @@ -119,6 +124,7 @@ impl VCpuOp for VCpu { fn run(&mut self) -> Result<(), RunError> { info!("Starting vCPU {}", self.bind_id()); + self.vcpu .setup_current_cpu(self.vm_id().into()) .map_err(|e| anyhow!("{e}"))?; diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index d83c3d3..a0683e1 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -120,6 +120,7 @@ impl VmMachineUninit { for vcpu in &mut cpus { vcpu.vcpu.set_entry(kernel_entry).unwrap(); vcpu.vcpu.set_dtb_addr(dtb_addr).unwrap(); + vcpu.set_pt_level(self.pt_levels); let setup_config = Aarch64VCpuSetupConfig { passthrough_interrupt: self.config.interrupt_mode() diff --git a/src/arch/x86_64/cpu.rs b/src/arch/x86_64/cpu.rs new file mode 100644 index 0000000..9d91941 --- /dev/null +++ b/src/arch/x86_64/cpu.rs @@ -0,0 +1,196 @@ +use core::{ + fmt::{self, Debug, Display}, + ops::Deref, +}; + +use axvm_types::addr::*; +use x86_vcpu::*; + +use crate::{ + RunError, + data::VmDataWeak, + vcpu::{VCpuCommon, VCpuOp}, + vhal::{ + ArchCpuData, + cpu::{CpuHardId, CpuId}, + }, +}; + +pub struct HCpu { + pub id: CpuId, + pub hard_id: CpuHardId, + vpercpu: VmxArchVCpu, + max_guest_page_table_levels: usize, + pub pa_range: core::ops::Range, +} + +impl HCpu { + pub fn new(id: CpuId) -> Self { + let mpidr = MPIDR_EL1.get() as usize; + let hard_id = mpidr & 0xff_ff_ff; + + let vpercpu = Aarch64PerCpu::new(); + + HCpu { + id, + hard_id: CpuHardId::new(hard_id), + vpercpu, + max_guest_page_table_levels: 0, + pa_range: 0..0, + } + } + + pub fn init(&mut self) -> anyhow::Result<()> { + self.vpercpu.hardware_enable(); + self.max_guest_page_table_levels = self.vpercpu.max_guest_page_table_levels(); + self.pa_range = self.vpercpu.pa_range(); + Ok(()) + } + + pub fn max_guest_page_table_levels(&self) -> usize { + self.max_guest_page_table_levels + } +} + +impl ArchCpuData for HCpu { + fn hard_id(&self) -> CpuHardId { + self.hard_id + } +} + +impl Display for HCpu { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + " +CPU {}: + Hard ID: {} + PT Levels: {}", + self.id, self.hard_id, self.max_guest_page_table_levels + ) + } +} + +pub(super) struct VCpuHal; + +impl arm_vcpu::CpuHal for VCpuHal { + fn irq_hanlder(&self) { + axhal::irq::irq_handler(0); + } + + fn inject_interrupt(&self, irq: usize) { + todo!() + } +} + +pub struct VCpu { + pub vcpu: arm_vcpu::Aarch64VCpu, + common: VCpuCommon, +} + +impl VCpu { + pub fn new( + host_cpuid: Option, + dtb_addr: GuestPhysAddr, + vm: VmDataWeak, + ) -> anyhow::Result { + let common = VCpuCommon::new_exclusive(host_cpuid, vm)?; + + let hard_id = common.hard_id(); + + let vcpu = arm_vcpu::Aarch64VCpu::new(Aarch64VCpuCreateConfig { + mpidr_el1: hard_id.raw() as u64, + dtb_addr: dtb_addr.as_usize(), + }) + .unwrap(); + Ok(VCpu { vcpu, common }) + } +} + +impl VCpuOp for VCpu { + fn bind_id(&self) -> CpuId { + self.common.bind_id() + } + + fn hard_id(&self) -> CpuHardId { + self.common.hard_id() + } + + fn run(&mut self) -> Result<(), RunError> { + info!("Starting vCPU {}", self.bind_id()); + self.vcpu + .setup_current_cpu(self.vm_id().into()) + .map_err(|e| anyhow!("{e}"))?; + while self.is_active() { + debug!("vCPU {} entering guest", self.bind_id()); + let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; + debug!( + "vCPU {} exited with reason: {:?}", + self.bind_id(), + exit_reason + ); + match exit_reason { + arm_vcpu::AxVCpuExitReason::Hypercall { nr, args } => todo!(), + arm_vcpu::AxVCpuExitReason::MmioRead { + addr, + width, + reg, + reg_width, + signed_ext, + } => todo!(), + arm_vcpu::AxVCpuExitReason::MmioWrite { addr, width, data } => todo!(), + arm_vcpu::AxVCpuExitReason::SysRegRead { addr, reg } => todo!(), + arm_vcpu::AxVCpuExitReason::SysRegWrite { addr, value } => todo!(), + arm_vcpu::AxVCpuExitReason::ExternalInterrupt => { + axhal::irq::irq_handler(0); + } + arm_vcpu::AxVCpuExitReason::CpuUp { + target_cpu, + entry_point, + arg, + } => { + debug!("vCPU {} requested CPU {} up", self.bind_id(), target_cpu); + self.vm()?.with_machine_running_mut(|running| { + debug!("vCPU {} is bringing up CPU {}", self.bind_id(), target_cpu); + running.cpu_up(CpuHardId::new(target_cpu as _), entry_point, arg) + })??; + self.vcpu.set_gpr(0, 0); + } + arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), + arm_vcpu::AxVCpuExitReason::SystemDown => { + info!("vCPU {} requested system shutdown", self.bind_id()); + self.vm()?.stop()?; + } + arm_vcpu::AxVCpuExitReason::Nothing => {} + arm_vcpu::AxVCpuExitReason::SendIPI { + target_cpu, + target_cpu_aux, + send_to_all, + send_to_self, + vector, + } => todo!(), + _ => todo!(), + } + } + + Ok(()) + } +} + +impl Deref for VCpu { + type Target = VCpuCommon; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl Debug for VCpu { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VCpu") + .field("bind_id", &self.bind_id()) + .field("hard_id", &self.hard_id()) + .field("vcpu", &self.vcpu) + .finish() + } +} diff --git a/src/arch/x86_64/hal.rs b/src/arch/x86_64/hal.rs new file mode 100644 index 0000000..9e95718 --- /dev/null +++ b/src/arch/x86_64/hal.rs @@ -0,0 +1,39 @@ +use alloc::vec::Vec; + +use crate::vhal::{ + ArchHal, + cpu::{CpuHardId, CpuId}, +}; + +use super::cpu::{HCpu, VCpuHal}; + +pub struct Hal; + +impl ArchHal for Hal { + fn current_cpu_init(id: CpuId) -> anyhow::Result { + info!("Enabling virtualization on cpu {id}"); + let mut cpu = HCpu::new(id); + cpu.init()?; + info!("{cpu}"); + Ok(cpu) + } + + fn init() -> anyhow::Result<()> { + + + Ok(()) + } + + fn cpu_list() -> Vec { + + } + + fn cpu_hard_id() -> CpuHardId { + let mpidr = MPIDR_EL1.get() as usize; + CpuHardId::new(mpidr) + } + + fn cache_flush(vaddr: arm_vcpu::HostVirtAddr, size: usize) { + dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); + } +} diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 8b13789..e6c4859 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -1 +1,2 @@ - +mod cpu; +mod hal; From b80c27e6f1edaecae5d0e12fe3bc901d3986c694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 22 Dec 2025 16:53:51 +0800 Subject: [PATCH 67/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20pa=5Fbits=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=88=B0=20HCpu=20=E5=92=8C=20VmMachineUnini?= =?UTF-8?q?t=20=E7=BB=93=E6=9E=84=E4=BD=93=EF=BC=8C=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E7=89=A9=E7=90=86=E5=9C=B0=E5=9D=80=E5=A4=84=E7=90=86=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 8 +++++++- src/arch/aarch64/vm/unint.rs | 20 ++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 38b4825..26dcbc6 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -23,6 +23,7 @@ pub struct HCpu { vpercpu: Aarch64PerCpu, max_guest_page_table_levels: usize, pub pa_range: core::ops::Range, + pub pa_bits: usize, } impl HCpu { @@ -38,6 +39,7 @@ impl HCpu { vpercpu, max_guest_page_table_levels: 0, pa_range: 0..0, + pa_bits: 0, } } @@ -45,6 +47,7 @@ impl HCpu { self.vpercpu.hardware_enable(); self.max_guest_page_table_levels = self.vpercpu.max_guest_page_table_levels(); self.pa_range = self.vpercpu.pa_range(); + self.pa_bits = self.vpercpu.pa_bits(); Ok(()) } @@ -102,7 +105,6 @@ impl VCpu { let vcpu = arm_vcpu::Aarch64VCpu::new(Aarch64VCpuCreateConfig { mpidr_el1: hard_id.raw() as u64, dtb_addr: dtb_addr.as_usize(), - pt_level: 4, }) .unwrap(); Ok(VCpu { vcpu, common }) @@ -111,6 +113,10 @@ impl VCpu { pub fn set_pt_level(&mut self, level: usize) { self.vcpu.pt_level = level; } + + pub fn set_pa_bits(&mut self, pa_bits: usize) { + self.vcpu.pa_bits = pa_bits; + } } impl VCpuOp for VCpu { diff --git a/src/arch/aarch64/vm/unint.rs b/src/arch/aarch64/vm/unint.rs index a0683e1..00c93dc 100644 --- a/src/arch/aarch64/vm/unint.rs +++ b/src/arch/aarch64/vm/unint.rs @@ -15,6 +15,7 @@ pub struct VmMachineUninit { config: AxVMConfig, pt_levels: usize, pa_max: usize, + pa_bits: usize, } impl VmMachineUninitOps for VmMachineUninit { @@ -25,6 +26,7 @@ impl VmMachineUninitOps for VmMachineUninit { config, pt_levels: 4, pa_max: usize::MAX, + pa_bits: 48, } } @@ -63,14 +65,23 @@ impl VmMachineUninit { let vcpu_count = vcpus.len(); for vcpu in &vcpus { - let (max_levels, max_pa) = - vcpu.with_hcpu(|cpu| (cpu.max_guest_page_table_levels(), cpu.pa_range.end)); + let (max_levels, max_pa, pa_bits) = vcpu.with_hcpu(|cpu| { + ( + cpu.max_guest_page_table_levels(), + cpu.pa_range.end, + cpu.pa_bits, + ) + }); if max_levels < self.pt_levels { self.pt_levels = max_levels; } if max_pa < self.pa_max { self.pa_max = max_pa; } + + if pa_bits < self.pa_bits { + self.pa_bits = pa_bits; + } } if self.pt_levels == 3 { @@ -78,8 +89,8 @@ impl VmMachineUninit { } debug!( - "VM {} ({}) vCPU count: {}, \n Max Guest Page Table Levels: {}\n Max PA: {:#x}", - self.config.id, self.config.name, vcpu_count, self.pt_levels, self.pa_max + "VM {} ({}) vCPU count: {}, \n Max Guest Page Table Levels: {}\n Max PA: {:#x}\n PA Bits: {}", + self.config.id, self.config.name, vcpu_count, self.pt_levels, self.pa_max, self.pa_bits ); Ok(vcpus) } @@ -121,6 +132,7 @@ impl VmMachineUninit { vcpu.vcpu.set_entry(kernel_entry).unwrap(); vcpu.vcpu.set_dtb_addr(dtb_addr).unwrap(); vcpu.set_pt_level(self.pt_levels); + vcpu.set_pa_bits(self.pa_bits); let setup_config = Aarch64VCpuSetupConfig { passthrough_interrupt: self.config.interrupt_mode() From f362f85914ddee34aa3b636e281f86d5758ebb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 23 Dec 2025 10:54:29 +0800 Subject: [PATCH 68/74] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20x86=5F64=20?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E7=9A=84=E8=99=9A=E6=8B=9F=E6=9C=BA=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E6=B7=BB=E5=8A=A0=20VM=20=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E5=92=8C=E8=BF=90=E8=A1=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/arch/x86_64/cpu.rs | 240 ++++++++++++++++++++++++--------- src/arch/x86_64/hal.rs | 46 +++++-- src/arch/x86_64/mod.rs | 7 +- src/arch/x86_64/vm/inited.rs | 46 +++++++ src/arch/x86_64/vm/mod.rs | 24 ++++ src/arch/x86_64/vm/running.rs | 49 +++++++ src/arch/x86_64/vm/stopping.rs | 5 + src/arch/x86_64/vm/unint.rs | 134 ++++++++++++++++++ 9 files changed, 471 insertions(+), 81 deletions(-) create mode 100644 src/arch/x86_64/vm/inited.rs create mode 100644 src/arch/x86_64/vm/mod.rs create mode 100644 src/arch/x86_64/vm/running.rs create mode 100644 src/arch/x86_64/vm/stopping.rs create mode 100644 src/arch/x86_64/vm/unint.rs diff --git a/Cargo.toml b/Cargo.toml index 63e5c0e..c748cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ axvmconfig = {version = "0.1", default-features = false} fdt-edit = "0.1" [target.'cfg(target_arch = "x86_64")'.dependencies] +raw-cpuid = "11" x86_vcpu = "0.1" axplat-x86-qemu-q35.workspace = true diff --git a/src/arch/x86_64/cpu.rs b/src/arch/x86_64/cpu.rs index 9d91941..9aefd36 100644 --- a/src/arch/x86_64/cpu.rs +++ b/src/arch/x86_64/cpu.rs @@ -2,9 +2,10 @@ use core::{ fmt::{self, Debug, Display}, ops::Deref, }; +use std::os::arceos::modules::axalloc; use axvm_types::addr::*; -use x86_vcpu::*; +use memory_addr::{PAGE_SIZE_4K, PhysAddr, VirtAddr}; use crate::{ RunError, @@ -16,34 +17,76 @@ use crate::{ }, }; +// ==================== x86 VCPU HAL 实现 ==================== +// x86_vcpu 现在使用自己的 Hal trait,不需要 AxVCpuHal + +/// x86 VCPU 的 HAL 实现 - 实现 x86_vcpu::Hal trait +pub(super) struct X86VcpuHal; + +impl x86_vcpu::Hal for X86VcpuHal { + fn alloc_frame() -> Option { + axalloc::global_allocator() + .alloc_pages(1, PAGE_SIZE_4K, axalloc::UsageKind::Global) + .ok() + } + + fn dealloc_frame(paddr: usize) { + axalloc::global_allocator().dealloc_pages(paddr, 1, axalloc::UsageKind::Global); + } + + fn phys_to_virt(paddr: usize) -> usize { + axhal::mem::phys_to_virt(PhysAddr::from(paddr)).into() + } + + fn virt_to_phys(vaddr: usize) -> usize { + axhal::mem::virt_to_phys(VirtAddr::from(vaddr)).into() + } +} + +// 使用具体的泛型类型 +type VmxPerCpuState = x86_vcpu::VmxArchPerCpuState; +type VmxVcpu = x86_vcpu::VmxArchVCpu; + pub struct HCpu { pub id: CpuId, pub hard_id: CpuHardId, - vpercpu: VmxArchVCpu, + vpercpu: VmxPerCpuState, max_guest_page_table_levels: usize, pub pa_range: core::ops::Range, + pub pa_bits: usize, } impl HCpu { pub fn new(id: CpuId) -> Self { - let mpidr = MPIDR_EL1.get() as usize; - let hard_id = mpidr & 0xff_ff_ff; + // 使用 raw_cpuid 获取 x86 APIC ID + let apic_id = raw_cpuid::CpuId::new() + .get_feature_info() + .map(|f| f.initial_local_apic_id() as usize) + .unwrap_or(0); + let hard_id = CpuHardId::new(apic_id); - let vpercpu = Aarch64PerCpu::new(); + // 创建 x86 PerCpu 状态 + let vpercpu = VmxPerCpuState::new(id.raw()).expect("Failed to create VmxPerCpuState"); HCpu { id, - hard_id: CpuHardId::new(hard_id), + hard_id, vpercpu, max_guest_page_table_levels: 0, pa_range: 0..0, + pa_bits: 0, } } pub fn init(&mut self) -> anyhow::Result<()> { - self.vpercpu.hardware_enable(); - self.max_guest_page_table_levels = self.vpercpu.max_guest_page_table_levels(); - self.pa_range = self.vpercpu.pa_range(); + // 启用 VMX 硬件虚拟化 + self.vpercpu.hardware_enable()?; + + // x86_64 平台的固定配置 + self.max_guest_page_table_levels = 4; // 4-level page tables (PML4) + self.pa_bits = 48; // 典型的 x86_64 物理地址宽度 + self.pa_range = 0..(1 << self.pa_bits); + Ok(()) } @@ -65,46 +108,54 @@ impl Display for HCpu { " CPU {}: Hard ID: {} - PT Levels: {}", - self.id, self.hard_id, self.max_guest_page_table_levels + PT Levels: {} + PA Bits: {}", + self.id, self.hard_id, self.max_guest_page_table_levels, self.pa_bits ) } } -pub(super) struct VCpuHal; - -impl arm_vcpu::CpuHal for VCpuHal { - fn irq_hanlder(&self) { - axhal::irq::irq_handler(0); - } - - fn inject_interrupt(&self, irq: usize) { - todo!() - } -} - +// x86 特定的 VCPU pub struct VCpu { - pub vcpu: arm_vcpu::Aarch64VCpu, + pub vcpu: VmxVcpu, common: VCpuCommon, } impl VCpu { pub fn new( host_cpuid: Option, - dtb_addr: GuestPhysAddr, + _dtb_addr: GuestPhysAddr, // 参数保留以保持接口兼容性,x86 不使用设备树 vm: VmDataWeak, ) -> anyhow::Result { let common = VCpuCommon::new_exclusive(host_cpuid, vm)?; let hard_id = common.hard_id(); + let vm_id = common.vm_id().into(); + let vcpu_id = common.bind_id().raw(); + + // 使用 x86_vcpu 的新方法创建 VCPU + let vcpu = VmxVcpu::new(vm_id, vcpu_id) + .map_err(|e| anyhow::anyhow!("Failed to create VmxVcpu: {:?}", e))?; + + info!( + "Created x86 VCPU: vm_id={}, vcpu_id={}, hard_id={}", + vm_id, + vcpu_id, + hard_id.raw() + ); - let vcpu = arm_vcpu::Aarch64VCpu::new(Aarch64VCpuCreateConfig { - mpidr_el1: hard_id.raw() as u64, - dtb_addr: dtb_addr.as_usize(), - }) - .unwrap(); Ok(VCpu { vcpu, common }) } + + pub fn set_pt_level(&mut self, level: usize) { + // x86 通过 EPT 配置,此方法预留以保持接口兼容性 + debug!("Setting page table level to {} (no-op on x86)", level); + } + + pub fn set_pa_bits(&mut self, pa_bits: usize) { + // x86 通过 EPT 配置,此方法预留以保持接口兼容性 + debug!("Setting PA bits to {} (no-op on x86)", pa_bits); + } } impl VCpuOp for VCpu { @@ -117,62 +168,119 @@ impl VCpuOp for VCpu { } fn run(&mut self) -> Result<(), RunError> { - info!("Starting vCPU {}", self.bind_id()); - self.vcpu - .setup_current_cpu(self.vm_id().into()) - .map_err(|e| anyhow!("{e}"))?; + info!("Starting x86 vCPU {}", self.bind_id()); + + // 绑定到当前 CPU - 使用 x86_vcpu 的新方法 + self.vcpu.bind().map_err(|e| { + RunError::ExitWithError(anyhow::anyhow!("Failed to bind VCPU: {:?}", e)) + })?; + while self.is_active() { - debug!("vCPU {} entering guest", self.bind_id()); - let exit_reason = self.vcpu.run().map_err(|e| anyhow!("{e}"))?; + debug!("x86 vCPU {} entering guest", self.bind_id()); + + // 使用 x86_vcpu 的 run_arch 方法,返回自己的 VmxExitReason + let exit_reason = self.vcpu.run().map_err(|e| { + RunError::ExitWithError(anyhow::anyhow!("VCPU run failed: {:?}", e)) + })?; + debug!( - "vCPU {} exited with reason: {:?}", + "x86 vCPU {} exited with reason: {:?}", self.bind_id(), exit_reason ); + + // 根据用户优先级处理退出原因 - 使用 x86_vcpu 的 VmxExitReason match exit_reason { - arm_vcpu::AxVCpuExitReason::Hypercall { nr, args } => todo!(), - arm_vcpu::AxVCpuExitReason::MmioRead { - addr, - width, - reg, - reg_width, - signed_ext, - } => todo!(), - arm_vcpu::AxVCpuExitReason::MmioWrite { addr, width, data } => todo!(), - arm_vcpu::AxVCpuExitReason::SysRegRead { addr, reg } => todo!(), - arm_vcpu::AxVCpuExitReason::SysRegWrite { addr, value } => todo!(), - arm_vcpu::AxVCpuExitReason::ExternalInterrupt => { - axhal::irq::irq_handler(0); + // 高优先级:外部中断(必须实现) + x86_vcpu::VmxExitReason::ExternalInterrupt { vector } => { + debug!("Handling external interrupt, vector={}", vector); + axhal::irq::irq_handler(vector); + } + + // 高优先级:系统寄存器访问 (MSR) + x86_vcpu::VmxExitReason::SysRegRead { addr, reg } => { + // TODO: 实现 MSR 读取处理 + // x86_vcpu 的 VmxVcpu 已经处理了 x2APIC MSR 访问 + // 这里需要处理其他 MSR 的读取 + todo!("MSR read: addr={:?}, reg={}", addr, reg); + } + x86_vcpu::VmxExitReason::SysRegWrite { addr, value } => { + // TODO: 实现 MSR 写入处理 + todo!("MSR write: addr={:?}, value={:#x}", addr, value); + } + + // 高优先级:IO 指令 + x86_vcpu::VmxExitReason::IoRead { port, width } => { + // TODO: 实现端口 IO 读取 + // 需要连接到设备模拟层 + todo!("IO read: port={:?}, width={:?}", port, width); + } + x86_vcpu::VmxExitReason::IoWrite { port, width, data } => { + // TODO: 实现端口 IO 写入 + // 需要连接到设备模拟层 + todo!( + "IO write: port={:?}, width={:?}, data={:#x}", + port, + width, + data + ); } - arm_vcpu::AxVCpuExitReason::CpuUp { + + // 中优先级:超级调用 + x86_vcpu::VmxExitReason::Hypercall { nr, args } => { + // TODO: 实现超级调用接口 + todo!("Hypercall: nr={:#x}, args={:?}", nr, args); + } + + // 低优先级:CPU 启动 + x86_vcpu::VmxExitReason::CpuUp { target_cpu, entry_point, arg, } => { - debug!("vCPU {} requested CPU {} up", self.bind_id(), target_cpu); + debug!( + "x86 vCPU {} requested CPU {} up", + self.bind_id(), + target_cpu + ); self.vm()?.with_machine_running_mut(|running| { debug!("vCPU {} is bringing up CPU {}", self.bind_id(), target_cpu); - running.cpu_up(CpuHardId::new(target_cpu as _), entry_point, arg) + // 将 axaddrspace::GuestPhysAddr 转换为 axvm_types::addr::GuestPhysAddr + // 先转为 usize,再转为目标类型 + let entry: GuestPhysAddr = entry_point.as_usize().into(); + running.cpu_up(CpuHardId::new(target_cpu), entry, arg) })??; + // x86 使用 SIPI (Startup IPI) 启动 AP,返回值在 RAX 中 self.vcpu.set_gpr(0, 0); } - arm_vcpu::AxVCpuExitReason::CpuDown { _state } => todo!(), - arm_vcpu::AxVCpuExitReason::SystemDown => { - info!("vCPU {} requested system shutdown", self.bind_id()); + + x86_vcpu::VmxExitReason::CpuDown { state } => { + // TODO: 实现 CPU 关闭 + todo!("CPU down: state={:?}", state); + } + + // 系统关闭 + x86_vcpu::VmxExitReason::SystemDown => { + info!("x86 vCPU {} requested system shutdown", self.bind_id()); self.vm()?.stop()?; + break; + } + + x86_vcpu::VmxExitReason::Nothing => { + // 无操作,继续运行 + } + + _ => { + warn!("Unhandled x86 VCPU exit reason: {:?}", exit_reason); } - arm_vcpu::AxVCpuExitReason::Nothing => {} - arm_vcpu::AxVCpuExitReason::SendIPI { - target_cpu, - target_cpu_aux, - send_to_all, - send_to_self, - vector, - } => todo!(), - _ => todo!(), } } + // 解绑 VCPU - 使用 x86_vcpu 的新方法 + self.vcpu.unbind().map_err(|e| { + RunError::ExitWithError(anyhow::anyhow!("Failed to unbind VCPU: {:?}", e)) + })?; + Ok(()) } } @@ -187,7 +295,7 @@ impl Deref for VCpu { impl Debug for VCpu { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("VCpu") + f.debug_struct("x86::VCpu") .field("bind_id", &self.bind_id()) .field("hard_id", &self.hard_id()) .field("vcpu", &self.vcpu) diff --git a/src/arch/x86_64/hal.rs b/src/arch/x86_64/hal.rs index 9e95718..ad837ff 100644 --- a/src/arch/x86_64/hal.rs +++ b/src/arch/x86_64/hal.rs @@ -4,36 +4,54 @@ use crate::vhal::{ ArchHal, cpu::{CpuHardId, CpuId}, }; +use memory_addr::VirtAddr; -use super::cpu::{HCpu, VCpuHal}; +use super::cpu::HCpu; + +// 使用 x86_vcpus 提供的 raw_cpuid +extern crate raw_cpuid; pub struct Hal; impl ArchHal for Hal { + fn init() -> anyhow::Result<()> { + // x86_vcpu 不需要全局初始化 + // 每个独立的 CPU 在 current_cpu_init 中单独初始化 VMX + info!("x86_64 HAL initialization complete (no global init required)"); + Ok(()) + } + fn current_cpu_init(id: CpuId) -> anyhow::Result { - info!("Enabling virtualization on cpu {id}"); + info!("Enabling virtualization on x86_64 cpu {}", id); let mut cpu = HCpu::new(id); cpu.init()?; - info!("{cpu}"); + info!("{}", cpu); Ok(cpu) } - fn init() -> anyhow::Result<()> { - - - Ok(()) - } - fn cpu_list() -> Vec { - + // 简单实现:从 axruntime 获取 CPU 数量 + // 假设 CPU ID 连续(0, 1, 2, ...) + // TODO: 后续可以从 ACPI/MP 表获取更准确的 APIC ID 映射 + let count = axruntime::cpu_count(); + debug!("x86_64 CPU list: {} CPUs (simple implementation)", count); + (0..count).map(|i| CpuHardId::new(i)).collect() } fn cpu_hard_id() -> CpuHardId { - let mpidr = MPIDR_EL1.get() as usize; - CpuHardId::new(mpidr) + // 使用 raw_cpuid 获取当前 CPU 的 APIC ID + let apic_id = raw_cpuid::CpuId::new() + .get_feature_info() + .map(|f| f.initial_local_apic_id() as usize) + .unwrap_or_else(|| { + warn!("Failed to get APIC ID from CPUID, using fallback"); + 0 + }); + CpuHardId::new(apic_id) } - fn cache_flush(vaddr: arm_vcpu::HostVirtAddr, size: usize) { - dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); + fn cache_flush(_vaddr: VirtAddr, _size: usize) { + // x86 不需要显式的缓存刷新 + // WBINVD 指令会在需要时由硬件自动处理 } } diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index e6c4859..a3c06f0 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -1,2 +1,7 @@ -mod cpu; +pub mod cpu; mod hal; +mod vm; + +pub use cpu::HCpu; +pub use hal::Hal; +pub use vm::*; diff --git a/src/arch/x86_64/vm/inited.rs b/src/arch/x86_64/vm/inited.rs new file mode 100644 index 0000000..948ca2c --- /dev/null +++ b/src/arch/x86_64/vm/inited.rs @@ -0,0 +1,46 @@ +use std::{string::String, vec::Vec}; + + + + +use crate::{ + VmAddrSpace, VmMachineInitedOps, VmMachineRunningCommon, + arch::{VmMachineRunning, cpu::VCpu}, + data::VmDataWeak, + vm::VmId, +}; + +pub struct VmMachineInited { + pub id: VmId, + pub name: String, + pub vcpus: Vec, + pub vmspace: VmAddrSpace, +} + +impl VmMachineInited {} + +impl VmMachineInitedOps for VmMachineInited { + type Running = VmMachineRunning; + + fn id(&self) -> VmId { + self.id + } + + fn name(&self) -> &str { + &self.name + } + + fn start(self, vmdata: VmDataWeak) -> Result { + debug!("Starting VM {} ({})", self.id, self.name); + let mut running = VmMachineRunning { + common: VmMachineRunningCommon::new(self.vmspace, self.vcpus, vmdata), + }; + + let main = running.common.take_cpu()?; + + running.common.run_cpu(main)?; + + info!("VM {} ({}) main cpu started.", self.id, self.name,); + Ok(running) + } +} diff --git a/src/arch/x86_64/vm/mod.rs b/src/arch/x86_64/vm/mod.rs new file mode 100644 index 0000000..0e1f0c2 --- /dev/null +++ b/src/arch/x86_64/vm/mod.rs @@ -0,0 +1,24 @@ +use alloc::string::String; + +use crate::GuestPhysAddr; + +mod inited; +mod running; +mod stopping; +mod unint; + +pub(crate) use inited::*; +pub(crate) use running::*; +pub(crate) use stopping::*; +pub(crate) use unint::*; + +/// Information about a device in the VM +#[derive(Debug, Clone)] +pub struct DeviceInfo {} + +#[derive(Debug, Clone)] +struct DevMapConfig { + gpa: GuestPhysAddr, + size: usize, + name: String, +} diff --git a/src/arch/x86_64/vm/running.rs b/src/arch/x86_64/vm/running.rs new file mode 100644 index 0000000..f293e7c --- /dev/null +++ b/src/arch/x86_64/vm/running.rs @@ -0,0 +1,49 @@ +use core::ops::Deref; + +use crate::{ + GuestPhysAddr, VmMachineRunningCommon, VmMachineRunningOps, VmMachineStoppingOps, + arch::cpu::VCpu, vhal::cpu::CpuHardId, +}; + +pub struct VmMachineRunning { + pub common: VmMachineRunningCommon, +} + +impl VmMachineRunning { + pub fn cpu_up( + &mut self, + target_cpu: CpuHardId, + entry_point: GuestPhysAddr, + arg: u64, + ) -> anyhow::Result<()> { + let mut cpu = self + .common + .cpus + .remove(&target_cpu) + .ok_or(anyhow!("No cpu {target_cpu} found"))?; + + // x86 使用 SIPI (Startup IPI) 来启动 AP + // 这里设置 entry point 和参数 + cpu.vcpu.set_entry(entry_point.as_usize().into())?; + cpu.vcpu.set_gpr(0, arg as _); + self.common.run_cpu(cpu)?; + Ok(()) + } +} + +impl Deref for VmMachineRunning { + type Target = VmMachineRunningCommon; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl VmMachineRunningOps for VmMachineRunning { + type Stopping = super::stopping::VmStatusStopping; + + fn stop(self) -> Self::Stopping { + debug!("Stopping x86_64 VM"); + super::stopping::VmStatusStopping {} + } +} diff --git a/src/arch/x86_64/vm/stopping.rs b/src/arch/x86_64/vm/stopping.rs new file mode 100644 index 0000000..1fb92eb --- /dev/null +++ b/src/arch/x86_64/vm/stopping.rs @@ -0,0 +1,5 @@ +use crate::VmMachineStoppingOps; + +pub struct VmStatusStopping {} + +impl VmMachineStoppingOps for VmStatusStopping {} diff --git a/src/arch/x86_64/vm/unint.rs b/src/arch/x86_64/vm/unint.rs new file mode 100644 index 0000000..d52b769 --- /dev/null +++ b/src/arch/x86_64/vm/unint.rs @@ -0,0 +1,134 @@ +use core::ops::Deref; + +use alloc::vec::Vec; + +use crate::{ + AxVMConfig, GuestPhysAddr, VmAddrSpace, VmMachineUninitOps, + arch::{VmMachineInited, cpu::VCpu}, + config::CpuNumType, + data::VmDataWeak, +}; + +pub struct VmMachineUninit { + config: AxVMConfig, + pt_levels: usize, + pa_max: usize, + pa_bits: usize, +} + +impl VmMachineUninitOps for VmMachineUninit { + type Inited = VmMachineInited; + + fn new(config: AxVMConfig) -> Self { + Self { + config, + pt_levels: 4, // x86_64 使用 4 级页表 (PML4) + pa_max: usize::MAX, + pa_bits: 48, // 典型的 x86_64 物理地址宽度 + } + } + + fn init(mut self, vmdata: VmDataWeak) -> Result + where + Self: Sized, + { + self.init_raw(vmdata) + } +} + +impl VmMachineUninit { + fn new_vcpus(&mut self, vm: &VmDataWeak) -> anyhow::Result> { + // 创建 vCPUs + let mut vcpus = vec![]; + + // x86 不使用设备树,dtb_addr 参数设为 0 + let dtb_addr = GuestPhysAddr::from(0); + + match self.config.cpu_num { + CpuNumType::Alloc(num) => { + for _ in 0..num { + let vcpu = VCpu::new(None, dtb_addr, vm.clone())?; + debug!("Created vCPU with {:?}", vcpu.bind_id()); + vcpus.push(vcpu); + } + } + CpuNumType::Fixed(ref ids) => { + for id in ids { + let vcpu = VCpu::new(Some(*id), dtb_addr, vm.clone())?; + debug!("Created vCPU with {:?}", vcpu.bind_id()); + vcpus.push(vcpu); + } + } + } + + let vcpu_count = vcpus.len(); + + // x86_64 平台的固定配置 + // 从 HCpu 获取页表级别和地址位信息(如果需要) + for vcpu in &vcpus { + // x86_64 固定使用 4 级页表 + // PA bits 可以根据需要调整 + debug!("vCPU bind_id: {:?}", vcpu.bind_id()); + } + + // 如果 pt_levels == 3,需要限制 pa_max + if self.pt_levels == 3 { + self.pa_max = self.pa_max.min(0x8000000000); + } + + debug!( + "VM {} ({}) vCPU count: {}, \n Max Guest Page Table Levels: {}\n Max PA: {:#x}\n PA Bits: {}", + self.config.id, self.config.name, vcpu_count, self.pt_levels, self.pa_max, self.pa_bits + ); + Ok(vcpus) + } + + fn init_raw(&mut self, vmdata: VmDataWeak) -> anyhow::Result { + debug!("Initializing VM {} ({})", self.config.id, self.config.name); + let mut cpus = self.new_vcpus(&vmdata)?; + + let mut vmspace = + VmAddrSpace::new(self.pt_levels, GuestPhysAddr::from(0)..self.pa_max.into())?; + + debug!( + "Mapping memory regions for VM {} ({})", + self.config.id, self.config.name + ); + for memory_cfg in &self.config.memory_regions { + vmspace.new_memory(memory_cfg)?; + } + + vmspace.load_kernel_image(&self.config)?; + + // x86 不使用设备树,而是使用 ACPI 表 + // 这里我们跳过 FDT 创建,直接加载内核 + // 如果需要 ACPI,可以在后续添加 + + vmspace.map_passthrough_regions()?; + + let kernel_entry = vmspace.kernel_entry(); + let gpt_root = vmspace.gpt_root(); + + // 设置 vCPUs + for vcpu in &mut cpus { + vcpu.vcpu + .set_entry(kernel_entry.as_usize().into()) + .map_err(|e| anyhow::anyhow!("Failed to set entry: {:?}", e))?; + + vcpu.vcpu + .set_ept_root(gpt_root) + .map_err(|e| anyhow::anyhow!("Failed to set EPT root: {:?}", e))?; + + // x86 特定的 VCPU 设置 + // 注意:x86_vcpu 的 VmxVcpu 不需要额外的 setup 调用 + // 因为在创建时已经完成基本初始化 + } + + Ok(VmMachineInited { + id: self.config.id.into(), + name: self.config.name.clone(), + vmspace, + vcpus: cpus, + }) + } +} From b87ca8f9e074c187279bda17f73f66ba836dca99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 23 Dec 2025 11:16:16 +0800 Subject: [PATCH 69/74] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20setup=5Fchos?= =?UTF-8?q?en=20=E6=96=B9=E6=B3=95=EF=BC=8C=E7=AE=80=E5=8C=96=20initrd=20?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fdt/mod.rs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index 844b5ab..3216a9d 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -107,29 +107,28 @@ impl FdtBuilder { .get_by_path_mut("/chosen") .ok_or_else(|| anyhow::anyhow!("No /chosen node found"))?; - let Some(initrd) = initrd else { + if let Some(initrd) = initrd { + let cells = node.ctx.parent_address_cells(); + let (initrd_start, initrd_end) = (initrd.0.as_usize(), initrd.0.as_usize() + initrd.1); + + let mut prop_s = Property::new("linux,initrd-start", vec![]); + let mut prop_e = Property::new("linux,initrd-end", vec![]); + + if cells == 2 { + prop_s.set_u32_ls(&[initrd_start as u32]); + prop_e.set_u32_ls(&[initrd_end as u32]); + } else { + prop_s.set_u64(initrd_start as _); + prop_e.set_u64(initrd_end as _); + } + + node.node.add_property(prop_s); + node.node.add_property(prop_e); + } else { node.node.remove_property("linux,initrd-start"); node.node.remove_property("linux,initrd-end"); - return Ok(()); }; - let cells = node.ctx.parent_address_cells(); - let (initrd_start, initrd_end) = (initrd.0.as_usize(), initrd.0.as_usize() + initrd.1); - - let mut prop_s = Property::new("linux,initrd-start", vec![]); - let mut prop_e = Property::new("linux,initrd-end", vec![]); - - if cells == 2 { - prop_s.set_u32_ls(&[initrd_start as u32]); - prop_e.set_u32_ls(&[initrd_end as u32]); - } else { - prop_s.set_u64(initrd_start as _); - prop_e.set_u64(initrd_end as _); - } - - node.node.add_property(prop_s); - node.node.add_property(prop_e); - if let Some(args) = node.node.get_property_mut("bootargs") && let Some(s) = args.as_str() { From bce19d92ee18ee6e38d480acbd6c90bc958a1681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 23 Dec 2025 15:18:47 +0800 Subject: [PATCH 70/74] fmt --- Cargo.toml | 1 - src/arch/x86_64/vm/inited.rs | 3 --- src/lib.rs | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c748cfd..2958f9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true -vm-fdt.workspace = true # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" diff --git a/src/arch/x86_64/vm/inited.rs b/src/arch/x86_64/vm/inited.rs index 948ca2c..410b518 100644 --- a/src/arch/x86_64/vm/inited.rs +++ b/src/arch/x86_64/vm/inited.rs @@ -1,8 +1,5 @@ use std::{string::String, vec::Vec}; - - - use crate::{ VmAddrSpace, VmMachineInitedOps, VmMachineRunningCommon, arch::{VmMachineRunning, cpu::VCpu}, diff --git a/src/lib.rs b/src/lib.rs index d4a9b06..180cc3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,8 @@ const TASK_STACK_SIZE: usize = 0x40000; // 256 KB pub(crate) mod arch; mod fdt; -mod vm; mod vcpu; +mod vm; pub mod config; pub mod vhal; From b4b88e06703b20b2f3b3eef409fafbf191cea679 Mon Sep 17 00:00:00 2001 From: TQ <128586861+YanLien@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:05:23 +0800 Subject: [PATCH 71/74] feat: add memory size and vCPU count to VmDataInner and expose methods in Vm (#38) --- src/vm/data.rs | 18 ++++++++++++++++++ src/vm/mod.rs | 12 +++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/vm/data.rs b/src/vm/data.rs index 07f7964..7214e68 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -20,17 +20,35 @@ pub(crate) struct VmDataInner { pub name: String, pub machine: RwLock, pub status: AtomicState, + pub memory_size: usize, + pub vcpu_num: usize, error: RwLock>, } impl VmDataInner { pub fn new(config: AxVMConfig) -> Self { + // Calculate total memory size + let memory_size = config + .memory_regions + .iter() + .map(|region| match region { + crate::config::MemoryKind::Identical { size } => *size, + crate::config::MemoryKind::Reserved { size, .. } => *size, + crate::config::MemoryKind::Vmem { size, .. } => *size, + }) + .sum(); + + // Get vCPU count + let vcpu_num = config.cpu_num.num(); + Self { id: config.id.into(), name: config.name.clone(), machine: RwLock::new(VmMachineState::Uninit(VmMachineUninit::new(config))), status: AtomicState::new(VMStatus::Uninit), error: RwLock::new(None), + memory_size, + vcpu_num, } } diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 3281d27..f80c08b 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -7,7 +7,7 @@ mod machine; pub(crate) use addrspace::*; pub use define::*; -pub(crate) use machine::*; +pub use machine::*; pub struct Vm { data: VmData, @@ -44,4 +44,14 @@ impl Vm { pub fn wait(&self) -> anyhow::Result<()> { self.data.wait() } + + /// Get total memory size in bytes. + pub fn memory_size(&self) -> usize { + self.data.memory_size + } + + /// Get vCPU count. + pub fn vcpu_num(&self) -> usize { + self.data.vcpu_num + } } From fb0b01ad150e7ca83b9e9852d542b4b5b9cceea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 16:46:50 +0800 Subject: [PATCH 72/74] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=20CPU=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=A8=A1=E5=9D=97=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20HAL=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=A2=9E=E5=BC=BA=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E5=8C=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 5 +- src/arch/aarch64/cpu.rs | 18 +++---- src/arch/aarch64/hal.rs | 43 ++++++++--------- src/arch/aarch64/vm/running.rs | 2 +- src/config.rs | 2 +- src/fdt/mod.rs | 2 +- src/{vhal => hal}/cpu.rs | 67 ++++++++++++++++++--------- src/{vhal => hal}/mod.rs | 59 +++++++++++------------ src/{vhal/precpu.rs => hal/percpu.rs} | 35 ++++++++++---- src/{vhal => hal}/timer.rs | 35 ++++++-------- src/lib.rs | 6 +-- src/vcpu/mod.rs | 2 +- src/vm/addrspace.rs | 2 +- src/vm/machine/running.rs | 4 +- 14 files changed, 152 insertions(+), 130 deletions(-) rename src/{vhal => hal}/cpu.rs (64%) rename src/{vhal => hal}/mod.rs (77%) rename src/{vhal/precpu.rs => hal/percpu.rs} (57%) rename src/{vhal => hal}/timer.rs (75%) diff --git a/Cargo.toml b/Cargo.toml index 2958f9a..9094f37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,12 +29,11 @@ page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true +derive_more = { version = "2", default-features = false, features = ["full"] } # System dependent modules provided by ArceOS-Hypervisor. axaddrspace = "0.2" -# axdevice = {git = "https://github.com/arceos-hypervisor/axdevice.git"} -# axdevice_base = "0.1" -# axvcpu = "0.1" + axhal.workspace = true axruntime.workspace = true axstd.workspace = true diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index 26dcbc6..fea2278 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -9,10 +9,10 @@ use axvm_types::addr::*; use crate::{ RunError, - data::VmDataWeak, vcpu::{VCpuCommon, VCpuOp}, - vhal::{ - ArchCpuData, + data::VmDataWeak, + hal::{ + HCpuOp, cpu::{CpuHardId, CpuId}, }, }; @@ -26,6 +26,12 @@ pub struct HCpu { pub pa_bits: usize, } +impl HCpuOp for HCpu { + fn hard_id(&self) -> CpuHardId { + self.hard_id + } +} + impl HCpu { pub fn new(id: CpuId) -> Self { let mpidr = MPIDR_EL1.get() as usize; @@ -56,12 +62,6 @@ impl HCpu { } } -impl ArchCpuData for HCpu { - fn hard_id(&self) -> CpuHardId { - self.hard_id - } -} - impl Display for HCpu { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( diff --git a/src/arch/aarch64/hal.rs b/src/arch/aarch64/hal.rs index 4c7c316..f4760c0 100644 --- a/src/arch/aarch64/hal.rs +++ b/src/arch/aarch64/hal.rs @@ -3,37 +3,22 @@ use alloc::vec::Vec; use aarch64_cpu::registers::*; use aarch64_cpu_ext::cache::{CacheOp, dcache_range}; -use crate::fdt; -use crate::vhal::{ - ArchHal, - cpu::{CpuHardId, CpuId}, -}; - use super::cpu::{HCpu, VCpuHal}; +use crate::fdt; +use crate::hal::cpu::{CpuHardId, CpuId}; pub struct Hal; -impl ArchHal for Hal { - fn current_cpu_init(id: CpuId) -> anyhow::Result { - info!("Enabling virtualization on cpu {id}"); - let mut cpu = HCpu::new(id); - cpu.init()?; - info!("{cpu}"); - Ok(cpu) - } +impl crate::hal::ArchOp for Hal { + type HCPU = HCpu; fn init() -> anyhow::Result<()> { arm_vcpu::init_hal(&VCpuHal); - Ok(()) } - fn cpu_list() -> Vec { - fdt::cpu_list() - .unwrap() - .into_iter() - .map(CpuHardId::new) - .collect() + fn cache_flush(vaddr: arm_vcpu::HostVirtAddr, size: usize) { + dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); } fn cpu_hard_id() -> CpuHardId { @@ -41,7 +26,19 @@ impl ArchHal for Hal { CpuHardId::new(mpidr) } - fn cache_flush(vaddr: arm_vcpu::HostVirtAddr, size: usize) { - dcache_range(CacheOp::CleanAndInvalidate, vaddr.as_usize(), size); + fn cpu_list() -> Vec { + fdt::cpu_list() + .unwrap() + .into_iter() + .map(CpuHardId::from) + .collect() + } + + fn current_cpu_init(id: crate::hal::cpu::CpuId) -> anyhow::Result { + info!("Enabling virtualization on cpu {id}"); + let mut cpu = HCpu::new(id); + cpu.init()?; + info!("{cpu}"); + Ok(cpu) } } diff --git a/src/arch/aarch64/vm/running.rs b/src/arch/aarch64/vm/running.rs index d6dbd45..944eb94 100644 --- a/src/arch/aarch64/vm/running.rs +++ b/src/arch/aarch64/vm/running.rs @@ -2,7 +2,7 @@ use fdt_edit::NodeRef; use crate::{ GuestPhysAddr, VmAddrSpace, VmMachineRunningCommon, VmMachineRunningOps, VmMachineStoppingOps, - arch::vm::DevMapConfig, vhal::cpu::CpuHardId, + arch::vm::DevMapConfig, hal::cpu::CpuHardId, }; /// Data needed when VM is running diff --git a/src/config.rs b/src/config.rs index b59beec..d6215f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,7 +11,7 @@ pub use axvmconfig::{ VMInterruptMode, VMType, VmMemConfig, VmMemMappingType, }; -use crate::vhal::cpu::CpuId; +use crate::hal::cpu::CpuId; #[derive(Debug, Default, Clone)] pub struct VMImageConfig { diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index 3216a9d..9aa1e9b 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use fdt_edit::{Fdt, FdtData, Node, Property, RegInfo, Status}; -use crate::{GuestMemory, GuestPhysAddr, vcpu::VCpuCommon, vhal::cpu::CpuHardId}; +use crate::{GuestMemory, GuestPhysAddr, vcpu::VCpuCommon, hal::cpu::CpuHardId}; pub(crate) fn fdt_edit() -> Option { let addr = axhal::dtb::get_bootarg(); diff --git a/src/vhal/cpu.rs b/src/hal/cpu.rs similarity index 64% rename from src/vhal/cpu.rs rename to src/hal/cpu.rs index 1285a75..706e770 100644 --- a/src/vhal/cpu.rs +++ b/src/hal/cpu.rs @@ -1,15 +1,26 @@ -use core::fmt::Display; +use alloc::vec::Vec; use bitmap_allocator::{BitAlloc, BitAlloc4K}; +use derive_more::From; use spin::Mutex; +use super::percpu::PerCpuSet; use crate::{ arch::HCpu, - vhal::{ArchCpuData, precpu::PreCpuSet}, + hal::{ArchOp, HCpuOp}, }; -pub(super) static PRE_CPU: PreCpuSet = PreCpuSet::new(); +pub(super) static PRE_CPU: PerCpuSet = PerCpuSet::new(); pub(super) static HCPU_ALLOC: Mutex = Mutex::new(BitAlloc4K::DEFAULT); +static CPU_LIST: spin::Once> = spin::Once::new(); + +pub fn count() -> usize { + list().len() +} + +pub fn list() -> Vec { + CPU_LIST.call_once(|| crate::arch::Hal::cpu_list()).clone() +} #[derive(Debug)] pub struct HCpuExclusive(CpuId); @@ -59,13 +70,26 @@ impl Drop for HCpuExclusive { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive( + derive_more::Debug, + derive_more::Display, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + From, +)] +#[debug("CPU Hard({_0:#x})")] +#[display("CPU Hard({_0:#x})")] #[repr(transparent)] pub struct CpuHardId(usize); impl CpuHardId { - pub fn new(id: usize) -> Self { - CpuHardId(id) + pub const fn new(raw: usize) -> Self { + Self(raw) } pub fn raw(&self) -> usize { @@ -73,28 +97,29 @@ impl CpuHardId { } } -impl Display for CpuHardId { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "CPU Hard({:#x})", self.0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive( + derive_more::Debug, + derive_more::Display, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + From, +)] +#[debug("CPU({_0:#x})")] +#[display("CPU({_0:#x})")] #[repr(transparent)] pub struct CpuId(usize); impl CpuId { - pub fn new(id: usize) -> Self { - CpuId(id) + pub const fn new(raw: usize) -> Self { + Self(raw) } pub fn raw(&self) -> usize { self.0 } } - -impl Display for CpuId { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "CPU({})", self.0) - } -} diff --git a/src/vhal/mod.rs b/src/hal/mod.rs similarity index 77% rename from src/vhal/mod.rs rename to src/hal/mod.rs index a2be9a7..9ef2327 100644 --- a/src/vhal/mod.rs +++ b/src/hal/mod.rs @@ -1,30 +1,43 @@ use alloc::vec::Vec; -use axstd::{ +use bitmap_allocator::BitAlloc; +use core::sync::atomic::{AtomicUsize, Ordering}; +use std::{ os::arceos::{api::task::AxCpuMask, modules::axtask::set_current_affinity}, thread::yield_now, }; -use bitmap_allocator::BitAlloc; -use core::sync::atomic::{AtomicUsize, Ordering}; -use crate::{ - HostPhysAddr, HostVirtAddr, TASK_STACK_SIZE, - arch::{HCpu, Hal}, - vhal::cpu::{CpuHardId, CpuId}, -}; +pub mod cpu; +pub mod percpu; +pub mod timer; + +use cpu::{CpuHardId, CpuId}; + +use crate::{HostPhysAddr, HostVirtAddr, TASK_STACK_SIZE, arch::Hal}; -pub(crate) mod cpu; -pub(crate) mod precpu; -mod timer; +pub trait ArchOp { + type HCPU: HCpuOp; + + fn init() -> anyhow::Result<()>; + fn cache_flush(vaddr: HostVirtAddr, size: usize); + fn cpu_hard_id() -> CpuHardId; + fn cpu_list() -> Vec; + fn current_cpu_init(id: CpuId) -> anyhow::Result; +} + +pub trait HCpuOp { + fn hard_id(&self) -> CpuHardId; +} pub fn init() -> anyhow::Result<()> { Hal::init()?; static CORES: AtomicUsize = AtomicUsize::new(0); - let cpu_count = cpu_count(); + let cpu_count = cpu::count(); info!("Initializing VHal for {cpu_count} CPUs..."); - cpu::PRE_CPU.init(); + cpu::PRE_CPU.init_empty(); + timer::init(); for cpu_id in 0..cpu_count { let id = CpuId::new(cpu_id); @@ -38,11 +51,9 @@ pub fn init() -> anyhow::Result<()> { set_current_affinity(AxCpuMask::one_shot(cpu_id)), "Initialize CPU affinity failed!" ); - info!("Enabling hardware virtualization support on core {id}"); - timer::init_percpu(); let cpu_data = Hal::current_cpu_init(id).expect("Enable virtualization failed!"); - unsafe { cpu::PRE_CPU.set(cpu_data.hard_id(), cpu_data) }; + unsafe { cpu::PRE_CPU.set(cpu_data.hard_id, cpu_data) }; let _ = CORES.fetch_add(1, Ordering::Release); }) .map_err(|e| anyhow!("{e:?}"))?; @@ -62,22 +73,6 @@ pub fn init() -> anyhow::Result<()> { Ok(()) } -pub fn cpu_count() -> usize { - axruntime::cpu_count() -} - -pub(crate) trait ArchHal { - fn init() -> anyhow::Result<()>; - fn cache_flush(vaddr: HostVirtAddr, size: usize); - fn cpu_hard_id() -> CpuHardId; - fn cpu_list() -> Vec; - fn current_cpu_init(id: CpuId) -> anyhow::Result; -} - -pub(crate) trait ArchCpuData { - fn hard_id(&self) -> CpuHardId; -} - pub fn phys_to_virt(paddr: HostPhysAddr) -> HostVirtAddr { axhal::mem::phys_to_virt(paddr.as_usize().into()) .as_usize() diff --git a/src/vhal/precpu.rs b/src/hal/percpu.rs similarity index 57% rename from src/vhal/precpu.rs rename to src/hal/percpu.rs index 3d1b270..7b5dca0 100644 --- a/src/vhal/precpu.rs +++ b/src/hal/percpu.rs @@ -3,17 +3,20 @@ use core::{cell::UnsafeCell, ops::Deref}; use crate::{ arch::Hal, - vhal::{ArchHal, cpu::CpuHardId}, + hal::{ + ArchOp, + cpu::{self, CpuHardId}, + }, }; -pub(crate) struct PreCpuSet(UnsafeCell>>); +pub(crate) struct PerCpuSet(UnsafeCell>>); -unsafe impl Sync for PreCpuSet {} -unsafe impl Send for PreCpuSet {} +unsafe impl Sync for PerCpuSet {} +unsafe impl Send for PerCpuSet {} -impl PreCpuSet { +impl PerCpuSet { pub const fn new() -> Self { - PreCpuSet(UnsafeCell::new(BTreeMap::new())) + PerCpuSet(UnsafeCell::new(BTreeMap::new())) } pub unsafe fn set(&self, cpu_id: CpuHardId, val: T) { @@ -21,23 +24,35 @@ impl PreCpuSet { pre_cpu_map.insert(cpu_id, Some(val)); } - pub fn init(&self) { - let cpu_list = Hal::cpu_list(); - debug!("Initializing PreCpuSet for CPUs: {:?}", cpu_list); + pub fn init_empty(&self) { + let cpu_list = cpu::list(); for cpu_id in cpu_list { let v = unsafe { &mut *self.0.get() }; v.insert(cpu_id, None); } } + pub fn init_with_value(&self, f: impl Fn(CpuHardId) -> T) { + let cpu_list = cpu::list(); + for cpu_id in cpu_list { + let v = unsafe { &mut *self.0.get() }; + v.insert(cpu_id, Some(f(cpu_id))); + } + } + pub fn iter(&self) -> impl Iterator { let set = unsafe { &*self.0.get() }; set.iter() .map(|(k, v)| (*k, v.as_ref().expect("CPU data not initialized!"))) } + + pub fn cpu_count(&self) -> usize { + let set = unsafe { &*self.0.get() }; + set.len() + } } -impl Deref for PreCpuSet { +impl Deref for PerCpuSet { type Target = T; fn deref(&self) -> &Self::Target { diff --git a/src/vhal/timer.rs b/src/hal/timer.rs similarity index 75% rename from src/vhal/timer.rs rename to src/hal/timer.rs index d8a6bfe..58beb33 100644 --- a/src/vhal/timer.rs +++ b/src/hal/timer.rs @@ -1,13 +1,13 @@ -use core::sync::atomic::AtomicUsize; -use core::sync::atomic::Ordering; - -use axhal; +use core::sync::atomic::{AtomicUsize, Ordering}; use alloc::boxed::Box; + +use axhal; use kspin::SpinNoIrq; -use lazyinit::LazyInit; use timer_list::{TimeValue, TimerEvent, TimerList}; +use crate::hal::percpu::PerCpuSet; + static TOKEN: AtomicUsize = AtomicUsize::new(0); // const PERIODIC_INTERVAL_NANOS: u64 = axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64; @@ -40,8 +40,7 @@ impl TimerEvent for VmmTimerEvent { } } -#[percpu::def_percpu] -static TIMER_LIST: LazyInit>> = LazyInit::new(); +static TIMER_LIST: PerCpuSet>> = PerCpuSet::new(); /// Registers a new timer that will execute at the specified deadline /// @@ -61,8 +60,7 @@ where deadline, TimeValue::from_nanos(deadline) ); - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - let mut timers = timer_list.lock(); + let mut timers = TIMER_LIST.lock(); let token = TOKEN.fetch_add(1, Ordering::Release); let event = VmmTimerEvent::new(token, handler); timers.set(TimeValue::from_nanos(deadline), event); @@ -74,19 +72,15 @@ where /// # Parameters /// - `token`: The unique token of the timer to cancel. pub fn cancel_timer(token: usize) { - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - let mut timers = timer_list.lock(); + let mut timers = TIMER_LIST.lock(); timers.cancel(|event| event.token == token); } /// Check and process any pending timer events pub fn check_events() { - // info!("Checking timer events..."); - // info!("now is {:#?}", axhal::time::wall_time()); - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; loop { let now = axhal::time::wall_time(); - let event = timer_list.lock().expire_one(now); + let event = TIMER_LIST.lock().expire_one(now); if let Some((_deadline, event)) = event { trace!("pick one {_deadline:#?} to handle!!!"); event.callback(now); @@ -96,6 +90,10 @@ pub fn check_events() { } } +pub fn init() { + TIMER_LIST.init_with_value(|_| SpinNoIrq::new(TimerList::new())); +} + // /// Schedule the next timer event based on the periodic interval // pub fn scheduler_next_event() { // trace!("Scheduling next event..."); @@ -104,10 +102,3 @@ pub fn check_events() { // debug!("PHY deadline {} !!!", deadline); // axhal::time::set_oneshot_timer(deadline); // } - -/// Initialize the hypervisor timer system -pub fn init_percpu() { - info!("Initing HV Timer..."); - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - timer_list.init_once(SpinNoIrq::new(TimerList::new())); -} diff --git a/src/lib.rs b/src/lib.rs index 180cc3e..7726e3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,15 +26,15 @@ mod vcpu; mod vm; pub mod config; -pub mod vhal; +pub(crate) mod hal; pub use axvm_types::addr::*; pub use config::AxVMConfig; -pub use vhal::cpu::CpuId; +pub use hal::cpu::{CpuHardId, CpuId}; pub use vm::*; /// Enable hardware virtualization support. pub fn enable_viretualization() -> anyhow::Result<()> { - vhal::init()?; + hal::init()?; Ok(()) } diff --git a/src/vcpu/mod.rs b/src/vcpu/mod.rs index 83ba201..5ec5892 100644 --- a/src/vcpu/mod.rs +++ b/src/vcpu/mod.rs @@ -2,7 +2,7 @@ use crate::{ CpuId, RunError, VmId, arch::HCpu, data::{VmData, VmDataWeak}, - vhal::cpu::{CpuHardId, HCpuExclusive}, + hal::cpu::{CpuHardId, HCpuExclusive}, }; pub trait VCpuOp: core::fmt::Debug + Send + 'static { diff --git a/src/vm/addrspace.rs b/src/vm/addrspace.rs index 1e4119e..9497f10 100644 --- a/src/vm/addrspace.rs +++ b/src/vm/addrspace.rs @@ -12,7 +12,7 @@ use ranges_ext::RangeInfo; use crate::{ AxVMConfig, GuestPhysAddr, HostPhysAddr, HostVirtAddr, config::MemoryKind, - vhal::{ArchHal, phys_to_virt, virt_to_phys}, + hal::{ArchOp, phys_to_virt, virt_to_phys}, }; const ALIGN: usize = 1024 * 1024 * 2; diff --git a/src/vm/machine/running.rs b/src/vm/machine/running.rs index 0ab9a17..15958eb 100644 --- a/src/vm/machine/running.rs +++ b/src/vm/machine/running.rs @@ -8,8 +8,8 @@ use std::{ use alloc::vec::Vec; use crate::{ - TASK_STACK_SIZE, VmAddrSpace, arch::cpu::VCpu, data::VmDataWeak, vcpu::VCpuOp, - vhal::cpu::CpuHardId, + TASK_STACK_SIZE, VmAddrSpace, arch::cpu::VCpu, vcpu::VCpuOp, data::VmDataWeak, + hal::cpu::CpuHardId, }; pub struct VmMachineRunningCommon { From c826ffdbc68c7fee91c22b0397d5d94fa185b494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 17:02:58 +0800 Subject: [PATCH 73/74] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20Cargo.toml?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E9=A1=B9=EF=BC=9B=E8=B0=83=E6=95=B4=20VmData?= =?UTF-8?q?Weak=20=E7=BB=93=E6=9E=84=E4=BD=93=E7=9A=84=E5=8F=AF=E8=A7=81?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 - src/hal/cpu.rs | 1 + src/vm/data.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9094f37..12e3a45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ kspin = "0.1" memory_addr = "0.4" page_table_entry = {version = "0.5", features = ["arm-el2"]} page_table_multiarch = "0.5" -percpu = {version = "0.2", features = ["arm-el2"]} vm-allocator.workspace = true derive_more = { version = "2", default-features = false, features = ["full"] } diff --git a/src/hal/cpu.rs b/src/hal/cpu.rs index 706e770..414e758 100644 --- a/src/hal/cpu.rs +++ b/src/hal/cpu.rs @@ -22,6 +22,7 @@ pub fn list() -> Vec { CPU_LIST.call_once(|| crate::arch::Hal::cpu_list()).clone() } +/// Exclusive access to a hardware CPU #[derive(Debug)] pub struct HCpuExclusive(CpuId); diff --git a/src/vm/data.rs b/src/vm/data.rs index 7214e68..722ff92 100644 --- a/src/vm/data.rs +++ b/src/vm/data.rs @@ -267,7 +267,7 @@ impl Deref for VmData { } #[derive(Clone)] -pub struct VmDataWeak { +pub(crate) struct VmDataWeak { id: VmId, inner: Weak, } From afd3ff04aff59dba6a58020c2baa69353c815457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 29 Jan 2026 17:34:14 +0800 Subject: [PATCH 74/74] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20cpu=5Flist?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95=E4=BB=A5=E8=8E=B7=E5=8F=96=20CPU=20?= =?UTF-8?q?=E5=88=97=E8=A1=A8=EF=BC=9B=E4=BF=AE=E5=A4=8D=20cpu=5Fhard=5Fid?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95=E4=B8=AD=E7=9A=84=E4=BD=8D=E6=8E=A9?= =?UTF-8?q?=E7=A0=81=E9=80=BB=E8=BE=91=EF=BC=9B=E4=BC=98=E5=8C=96=20RunErr?= =?UTF-8?q?or=20=E7=9A=84=E9=94=99=E8=AF=AF=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/arch/aarch64/cpu.rs | 10 +++++++++- src/arch/aarch64/hal.rs | 2 +- src/vm/define.rs | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/arch/aarch64/cpu.rs b/src/arch/aarch64/cpu.rs index fea2278..faa2f79 100644 --- a/src/arch/aarch64/cpu.rs +++ b/src/arch/aarch64/cpu.rs @@ -2,6 +2,7 @@ use core::{ fmt::{self, Debug, Display}, ops::Deref, }; +use alloc::vec::Vec; use aarch64_cpu::registers::*; use arm_vcpu::{Aarch64PerCpu, Aarch64VCpuCreateConfig}; @@ -9,12 +10,12 @@ use axvm_types::addr::*; use crate::{ RunError, - vcpu::{VCpuCommon, VCpuOp}, data::VmDataWeak, hal::{ HCpuOp, cpu::{CpuHardId, CpuId}, }, + vcpu::{VCpuCommon, VCpuOp}, }; pub struct HCpu { @@ -85,6 +86,13 @@ impl arm_vcpu::CpuHal for VCpuHal { fn inject_interrupt(&self, irq: usize) { todo!() } + + fn cpu_list(&self) -> Vec { + crate::hal::cpu::list() + .into_iter() + .map(|id| id.raw()) + .collect() + } } pub struct VCpu { diff --git a/src/arch/aarch64/hal.rs b/src/arch/aarch64/hal.rs index f4760c0..accae38 100644 --- a/src/arch/aarch64/hal.rs +++ b/src/arch/aarch64/hal.rs @@ -22,7 +22,7 @@ impl crate::hal::ArchOp for Hal { } fn cpu_hard_id() -> CpuHardId { - let mpidr = MPIDR_EL1.get() as usize; + let mpidr = MPIDR_EL1.get() as usize & 0xffffff; CpuHardId::new(mpidr) } diff --git a/src/vm/define.rs b/src/vm/define.rs index 00e878f..9a6b6f0 100644 --- a/src/vm/define.rs +++ b/src/vm/define.rs @@ -62,7 +62,7 @@ impl Clone for RunError { match self { RunError::Exit => RunError::Exit, RunError::ExitWithError(err) => { - RunError::ExitWithError(anyhow::anyhow!(format!("{err}"))) + RunError::ExitWithError(anyhow::anyhow!("{err}")) } } }